mirror of https://github.com/bsnes-emu/bsnes.git
Update to v102r19 release.
byuu says: Note: add `#undef OUT` to the top of higan/gba/ppu/ppu.hpp to compile on Windows (ugh ...) Now to await posts about this in four more threads again ;) Changelog: - GBA: rewrote PPU from a scanline-based renderer to a pixel-based renderer - ruby: fixed video/gdi bugs Note that there's an approximately 21% speed penalty compared to v102r18 for the pixel-based renderer. Also, horizontal mosaic effects are not yet implemented. But they should be prior to v103. This one is a little tricky as it currently works on fully rendered scanlines. I need to roll the mosaic into the background renderers, and then for sprites, well ... see below. The trickiest part by far of this new renderer is the object (sprite) system. Unlike every other system I emulate, the GBA supports affine rendering of its sprites. Or in other words, rotation effects. And it also has a very complex priority system. Right now, I can't see any way that the GBA PPU could render pixels in real-time like this. My belief is that there's a 240-entry buffer that fills up the next scanline's row of pixels. Which means it probably also runs on the last scanline of Vblank so that the first scanline has sprite data. However, I didn't design my object renderer like this just yet. For now, it creates a buffer of all 240 pixels right away at the start of the scanline. I know\!\! That's technically scanline-based. But it's only for fetching object tiledata, and it's only temporary. What needs to happen is I need a way to run something like a "mini libco thread" inside of the main thread, so that the object renderer can run in parallel with the rest of the PPU, yet not be a hideous abomination of a state machine, yet also not be horrendously slow as a full libco thread would be. I'm envisioning some kind of stackless yielding coroutine. But I'll need to think through how to design that, given the absence of coroutines even in C++17.
This commit is contained in:
parent
1ca4609079
commit
2461293ff0
|
@ -12,13 +12,13 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "102.18";
|
||||
static const string Version = "102.19";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
||||
//incremented only when serialization format changes
|
||||
static const string SerializerVersion = "102.17";
|
||||
static const string SerializerVersion = "102.19";
|
||||
|
||||
namespace Constants {
|
||||
namespace Colorburst {
|
||||
|
|
|
@ -1,26 +1,38 @@
|
|||
auto PPU::renderBackgrounds() -> void {
|
||||
switch(regs.control.bgmode) {
|
||||
case 0:
|
||||
renderBackgroundLinear(regs.bg[3]);
|
||||
renderBackgroundLinear(regs.bg[2]);
|
||||
renderBackgroundLinear(regs.bg[1]);
|
||||
renderBackgroundLinear(regs.bg[0]);
|
||||
uint3 PPU::Background::IO::mode;
|
||||
uint1 PPU::Background::IO::frame;
|
||||
uint5 PPU::Background::IO::mosaicWidth;
|
||||
uint5 PPU::Background::IO::mosaicHeight;
|
||||
|
||||
auto PPU::Background::scanline(uint y) -> void {
|
||||
}
|
||||
|
||||
auto PPU::Background::run(uint x, uint y) -> void {
|
||||
output = {};
|
||||
if(ppu.blank() || !io.enable) return;
|
||||
|
||||
switch(id) {
|
||||
case PPU::BG0:
|
||||
if(io.mode <= 1) return linear(x, y);
|
||||
break;
|
||||
case 1:
|
||||
renderBackgroundAffine(regs.bg[2]);
|
||||
renderBackgroundLinear(regs.bg[1]);
|
||||
renderBackgroundLinear(regs.bg[0]);
|
||||
|
||||
case PPU::BG1:
|
||||
if(io.mode <= 1) return linear(x, y);
|
||||
break;
|
||||
case 2:
|
||||
renderBackgroundAffine(regs.bg[3]);
|
||||
renderBackgroundAffine(regs.bg[2]);
|
||||
|
||||
case PPU::BG2:
|
||||
if(io.mode == 0) return linear(x, y);
|
||||
if(io.mode <= 2) return affine(x, y);
|
||||
if(io.mode <= 5) return bitmap(x, y);
|
||||
break;
|
||||
case 3: case 4: case 5:
|
||||
renderBackgroundBitmap(regs.bg[2]);
|
||||
|
||||
case PPU::BG3:
|
||||
if(io.mode == 0) return linear(x, y);
|
||||
if(io.mode == 2) return affine(x, y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
auto PPU::renderBackgroundLinear(Registers::Background& bg) -> void {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
@ -77,7 +89,59 @@ auto PPU::renderBackgroundLinear(Registers::Background& bg) -> void {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::Background::linear(uint x, uint y) -> void {
|
||||
if(x == 0) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
vmosaic = y;
|
||||
}
|
||||
|
||||
voffset = vmosaic + io.voffset;
|
||||
hoffset = io.hoffset;
|
||||
}
|
||||
|
||||
uint px = hoffset & 7;
|
||||
uint py = voffset & 7;
|
||||
|
||||
uint tx = hoffset >> 3;
|
||||
uint ty = voffset >> 3;
|
||||
|
||||
uint offset = (ty & 31) * 32 + (tx & 31);
|
||||
if(io.screenSize.bit(0) && (tx & 32)) offset += 32 * 32;
|
||||
if(io.screenSize.bit(1) && (ty & 32)) offset += 32 * 32 * (1 + (io.screenSize.bit(0)));
|
||||
offset = (io.screenBase << 11) + offset * 2;
|
||||
|
||||
uint16 tilemap = ppu.readVRAM(Half, offset);
|
||||
uint10 character = tilemap.bits( 0, 9);
|
||||
uint1 hflip = tilemap.bit (10);
|
||||
uint1 vflip = tilemap.bit (11);
|
||||
uint4 palette = tilemap.bits(12,15);
|
||||
|
||||
if(io.colorMode == 0) {
|
||||
offset = (io.characterBase << 14) + character * 32;
|
||||
offset += (py ^ (vflip ? 7 : 0)) * 4;
|
||||
offset += (px ^ (hflip ? 7 : 0)) / 2;
|
||||
if(uint4 color = ppu.readVRAM(Byte, offset) >> (px & 1 ? 4 : 0)) {
|
||||
output.enable = true;
|
||||
output.priority = io.priority;
|
||||
output.color = ppu.pram[palette * 16 + color];
|
||||
}
|
||||
} else {
|
||||
offset = (io.characterBase << 14) + character * 64;
|
||||
offset += (py ^ (vflip ? 7 : 0)) * 8;
|
||||
offset += (px ^ (hflip ? 7 : 0)) / 1;
|
||||
if(uint8 color = ppu.readVRAM(Byte, offset)) {
|
||||
output.enable = true;
|
||||
output.priority = io.priority;
|
||||
output.color = ppu.pram[color];
|
||||
}
|
||||
}
|
||||
|
||||
hoffset++;
|
||||
}
|
||||
|
||||
/*
|
||||
auto PPU::renderBackgroundAffine(Registers::Background& bg) -> void {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
@ -112,7 +176,44 @@ auto PPU::renderBackgroundAffine(Registers::Background& bg) -> void {
|
|||
bg.lx += bg.pb;
|
||||
bg.ly += bg.pd;
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::Background::affine(uint x, uint y) -> void {
|
||||
if(x == 0) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
hmosaic = io.lx;
|
||||
vmosaic = io.ly;
|
||||
}
|
||||
|
||||
fx = hmosaic;
|
||||
fy = vmosaic;
|
||||
}
|
||||
|
||||
uint screenSize = 16 << io.screenSize;
|
||||
uint screenWrap = (1 << (io.affineWrap ? 7 + io.screenSize : 20)) - 1;
|
||||
|
||||
uint cx = (fx >> 8) & screenWrap, tx = cx >> 3, px = cx & 7;
|
||||
uint cy = (fy >> 8) & screenWrap, ty = cy >> 3, py = cy & 7;
|
||||
|
||||
if(tx < screenSize && ty < screenSize) {
|
||||
uint8 character = ppu.vram[(io.screenBase << 11) + ty * screenSize + tx];
|
||||
if(uint8 color = ppu.vram[(io.characterBase << 14) + character * 64 + py * 8 + px]) {
|
||||
output.enable = true;
|
||||
output.priority = io.priority;
|
||||
output.color = ppu.pram[color];
|
||||
}
|
||||
}
|
||||
|
||||
fx += io.pa;
|
||||
fy += io.pc;
|
||||
|
||||
if(x == 239) {
|
||||
io.lx += io.pb;
|
||||
io.ly += io.pd;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
auto PPU::renderBackgroundBitmap(Registers::Background& bg) -> void {
|
||||
if(regs.control.enable[bg.id] == false) return;
|
||||
auto& output = layer[bg.id];
|
||||
|
@ -154,3 +255,52 @@ auto PPU::renderBackgroundBitmap(Registers::Background& bg) -> void {
|
|||
bg.lx += bg.pb;
|
||||
bg.ly += bg.pd;
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::Background::bitmap(uint x, uint y) -> void {
|
||||
if(x == 0) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
hmosaic = io.lx;
|
||||
vmosaic = io.ly;
|
||||
}
|
||||
|
||||
fx = hmosaic;
|
||||
fy = vmosaic;
|
||||
}
|
||||
|
||||
uint1 depth = io.mode != 4; //0 = 8-bit (mode 4); 1 = 15-bit (mode 3, mode 5)
|
||||
uint width = io.mode == 5 ? 160 : 240;
|
||||
uint height = io.mode == 5 ? 128 : 160;
|
||||
uint mode = depth ? Half : Byte;
|
||||
|
||||
uint baseAddress = io.mode == 3 ? 0 : 0xa000 * io.frame;
|
||||
|
||||
uint px = fx >> 8;
|
||||
uint py = fy >> 8;
|
||||
|
||||
if(px < width && py < height) {
|
||||
uint offset = py * width + px;
|
||||
uint15 color = ppu.readVRAM(mode, baseAddress + (offset << depth));
|
||||
|
||||
if(depth || color) { //8bpp color 0 is transparent; 15bpp color is always opaque
|
||||
if(depth == 0) color = ppu.pram[color];
|
||||
output.enable = true;
|
||||
output.priority = io.priority;
|
||||
output.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
fx += io.pa;
|
||||
fy += io.pc;
|
||||
|
||||
if(x == 239) {
|
||||
io.lx += io.pb;
|
||||
io.ly += io.pd;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Background::power(uint id) -> void {
|
||||
this->id = id;
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
||||
|
|
|
@ -1,101 +1,101 @@
|
|||
auto PPU::readIO(uint32 addr) -> uint8 {
|
||||
auto bgcnt = [&]() -> Registers::Background::Control& { return regs.bg[addr.bits(1,2)].control; };
|
||||
auto wf = [&]() -> Registers::WindowFlags& {
|
||||
static uint id[] = {In0, In1, Out, Obj};
|
||||
return regs.windowflags[id[addr.bits(0,1)]];
|
||||
};
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x0400'0000: return (
|
||||
regs.control.bgmode << 0
|
||||
| regs.control.cgbmode << 3
|
||||
| regs.control.frame << 4
|
||||
| regs.control.hblank << 5
|
||||
| regs.control.objmapping << 6
|
||||
| regs.control.forceblank << 7
|
||||
Background::IO::mode << 0
|
||||
| io.gameBoyColorMode << 3
|
||||
| Background::IO::frame << 4
|
||||
| objects.io.hblank << 5
|
||||
| objects.io.mapping << 6
|
||||
| io.forceBlank << 7
|
||||
);
|
||||
case 0x0400'0001: return (
|
||||
regs.control.enable[BG0] << 0
|
||||
| regs.control.enable[BG1] << 1
|
||||
| regs.control.enable[BG2] << 2
|
||||
| regs.control.enable[BG3] << 3
|
||||
| regs.control.enable[OBJ] << 4
|
||||
| regs.control.enablewindow[In0] << 5
|
||||
| regs.control.enablewindow[In1] << 6
|
||||
| regs.control.enablewindow[Obj] << 7
|
||||
bg0.io.enable << 0
|
||||
| bg1.io.enable << 1
|
||||
| bg2.io.enable << 2
|
||||
| bg3.io.enable << 3
|
||||
| objects.io.enable << 4
|
||||
| window0.io.enable << 5
|
||||
| window1.io.enable << 6
|
||||
| window2.io.enable << 7
|
||||
);
|
||||
|
||||
//GRSWP
|
||||
case 0x0400'0002: return regs.greenswap;
|
||||
case 0x0400'0002: return io.greenSwap;
|
||||
case 0x0400'0003: return 0;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x0400'0004: return (
|
||||
regs.status.vblank << 0
|
||||
| regs.status.hblank << 1
|
||||
| regs.status.vcoincidence << 2
|
||||
| regs.status.irqvblank << 3
|
||||
| regs.status.irqhblank << 4
|
||||
| regs.status.irqvcoincidence << 5
|
||||
io.vblank << 0
|
||||
| io.hblank << 1
|
||||
| io.vcoincidence << 2
|
||||
| io.irqvblank << 3
|
||||
| io.irqhblank << 4
|
||||
| io.irqvcoincidence << 5
|
||||
);
|
||||
case 0x0400'0005: return (
|
||||
regs.status.vcompare
|
||||
io.vcompare
|
||||
);
|
||||
|
||||
//VCOUNT
|
||||
case 0x0400'0006: return regs.vcounter.byte(0);
|
||||
case 0x0400'0007: return regs.vcounter.byte(1);
|
||||
case 0x0400'0006: return io.vcounter.byte(0);
|
||||
case 0x0400'0007: return io.vcounter.byte(1);
|
||||
|
||||
//BG0CNT, BG1CNT, BG2CNT, BG3CNT
|
||||
case 0x0400'0008: case 0x0400'000a: case 0x0400'000c: case 0x0400'000e: return (
|
||||
bgcnt().priority << 0
|
||||
| bgcnt().characterbaseblock << 2
|
||||
| bgcnt().unused << 4
|
||||
| bgcnt().mosaic << 6
|
||||
| bgcnt().colormode << 7
|
||||
);
|
||||
case 0x0400'0009: case 0x0400'000b: case 0x0400'000d: case 0x0400'000f: return (
|
||||
bgcnt().screenbaseblock << 0
|
||||
| bgcnt().affinewrap << 5
|
||||
| bgcnt().screensize << 6
|
||||
);
|
||||
//BG0CNT
|
||||
case 0x0400'0008: return bg0.io.priority << 0 | bg0.io.characterBase << 2 | bg0.io.unused << 4 | bg0.io.mosaic << 6 | bg0.io.colorMode << 7;
|
||||
case 0x0400'0009: return bg0.io.screenBase << 0 | bg0.io.affineWrap << 5 | bg0.io.screenSize << 6;
|
||||
|
||||
//WININ, WINOUT
|
||||
case 0x0400'0048: case 0x0400'0049: case 0x0400'004a: case 0x0400'004b: return (
|
||||
wf().enable[BG0] << 0
|
||||
| wf().enable[BG1] << 1
|
||||
| wf().enable[BG2] << 2
|
||||
| wf().enable[BG3] << 3
|
||||
| wf().enable[OBJ] << 4
|
||||
| wf().enable[SFX] << 4
|
||||
);
|
||||
//BG1CNT
|
||||
case 0x0400'000a: return bg1.io.priority << 0 | bg1.io.characterBase << 2 | bg1.io.unused << 4 | bg1.io.mosaic << 6 | bg1.io.colorMode << 7;
|
||||
case 0x0400'000b: return bg1.io.screenBase << 0 | bg1.io.affineWrap << 5 | bg1.io.screenSize << 6;
|
||||
|
||||
//BG2CNT
|
||||
case 0x0400'000c: return bg2.io.priority << 0 | bg2.io.characterBase << 2 | bg2.io.unused << 4 | bg2.io.mosaic << 6 | bg2.io.colorMode << 7;
|
||||
case 0x0400'000d: return bg2.io.screenBase << 0 | bg2.io.affineWrap << 5 | bg2.io.screenSize << 6;
|
||||
|
||||
//BG3CNT
|
||||
case 0x0400'000e: return bg3.io.priority << 0 | bg3.io.characterBase << 2 | bg3.io.unused << 4 | bg3.io.mosaic << 6 | bg3.io.colorMode << 7;
|
||||
case 0x0400'000f: return bg3.io.screenBase << 0 | bg3.io.affineWrap << 5 | bg3.io.screenSize << 6;
|
||||
|
||||
//WININ0
|
||||
case 0x0400'0048: return window0.io.active[BG0] << 0 | window0.io.active[BG1] << 1 | window0.io.active[BG2] << 2 | window0.io.active[BG3] << 3 | window0.io.active[OBJ] << 4 | window0.io.active[SFX] << 5;
|
||||
|
||||
//WININ1
|
||||
case 0x0400'0049: return window1.io.active[BG0] << 0 | window1.io.active[BG1] << 1 | window1.io.active[BG2] << 2 | window1.io.active[BG3] << 3 | window1.io.active[OBJ] << 4 | window1.io.active[SFX] << 5;
|
||||
|
||||
//WINOUT
|
||||
case 0x0400'004a: return window3.io.active[BG0] << 0 | window3.io.active[BG1] << 1 | window3.io.active[BG2] << 2 | window3.io.active[BG3] << 3 | window3.io.active[OBJ] << 4 | window3.io.active[SFX] << 5;
|
||||
|
||||
//WININ2
|
||||
case 0x0400'004b: return window2.io.active[BG0] << 0 | window2.io.active[BG1] << 1 | window2.io.active[BG2] << 3 | window2.io.active[BG3] << 3 | window2.io.active[OBJ] << 4 | window2.io.active[SFX] << 5;
|
||||
|
||||
//MOSAIC (write-only)
|
||||
|
||||
//BLTCNT
|
||||
case 0x0400'0050: return (
|
||||
regs.blend.control.above[BG0] << 0
|
||||
| regs.blend.control.above[BG1] << 1
|
||||
| regs.blend.control.above[BG2] << 2
|
||||
| regs.blend.control.above[BG3] << 3
|
||||
| regs.blend.control.above[OBJ] << 4
|
||||
| regs.blend.control.above[SFX] << 5
|
||||
| regs.blend.control.mode << 6
|
||||
screen.io.blendAbove[BG0] << 0
|
||||
| screen.io.blendAbove[BG1] << 1
|
||||
| screen.io.blendAbove[BG2] << 2
|
||||
| screen.io.blendAbove[BG3] << 3
|
||||
| screen.io.blendAbove[OBJ] << 4
|
||||
| screen.io.blendAbove[SFX] << 5
|
||||
| screen.io.blendMode << 6
|
||||
);
|
||||
case 0x0400'0051: return (
|
||||
regs.blend.control.below[BG0] << 0
|
||||
| regs.blend.control.below[BG1] << 1
|
||||
| regs.blend.control.below[BG2] << 2
|
||||
| regs.blend.control.below[BG3] << 3
|
||||
| regs.blend.control.below[OBJ] << 4
|
||||
| regs.blend.control.below[SFX] << 5
|
||||
screen.io.blendBelow[BG0] << 0
|
||||
| screen.io.blendBelow[BG1] << 1
|
||||
| screen.io.blendBelow[BG2] << 2
|
||||
| screen.io.blendBelow[BG3] << 3
|
||||
| screen.io.blendBelow[OBJ] << 4
|
||||
| screen.io.blendBelow[SFX] << 5
|
||||
);
|
||||
|
||||
//BLDALPHA
|
||||
case 0x0400'0052: return regs.blend.eva;
|
||||
case 0x0400'0053: return regs.blend.evb;
|
||||
case 0x0400'0052: return screen.io.blendEVA;
|
||||
case 0x0400'0053: return screen.io.blendEVB;
|
||||
|
||||
//BLDY is write-only
|
||||
//BLDY (write-only)
|
||||
|
||||
}
|
||||
|
||||
|
@ -103,172 +103,282 @@ auto PPU::readIO(uint32 addr) -> uint8 {
|
|||
}
|
||||
|
||||
auto PPU::writeIO(uint32 addr, uint8 data) -> void {
|
||||
auto bgcnt = [&]() -> Registers::Background::Control& { return regs.bg[addr.bits(1,2)].control; };
|
||||
auto bgofs = [&]() -> Registers::Background& { return regs.bg[addr.bits(2,3)]; };
|
||||
auto bg = [&]() -> Registers::Background& { return regs.bg[addr.bits(4,5)]; };
|
||||
auto wf = [&]() -> Registers::WindowFlags& {
|
||||
static uint id[] = {In0, In1, Out, Obj};
|
||||
return regs.windowflags[id[addr.bits(0,1)]];
|
||||
};
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DISPCNT
|
||||
case 0x0400'0000:
|
||||
regs.control.bgmode = data.bits(0,2);
|
||||
regs.control.cgbmode = data.bit (3);
|
||||
regs.control.frame = data.bit (4);
|
||||
regs.control.hblank = data.bit (5);
|
||||
regs.control.objmapping = data.bit (6);
|
||||
regs.control.forceblank = data.bit (7);
|
||||
Background::IO::mode = data.bits(0,2);
|
||||
io.gameBoyColorMode = data.bit (3);
|
||||
Background::IO::frame = data.bit (4);
|
||||
objects.io.hblank = data.bit (5);
|
||||
objects.io.mapping = data.bit (6);
|
||||
io.forceBlank = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0001:
|
||||
regs.control.enable[BG0] = data.bit(0);
|
||||
regs.control.enable[BG1] = data.bit(1);
|
||||
regs.control.enable[BG2] = data.bit(2);
|
||||
regs.control.enable[BG3] = data.bit(3);
|
||||
regs.control.enable[OBJ] = data.bit(4);
|
||||
regs.control.enablewindow[In0] = data.bit(5);
|
||||
regs.control.enablewindow[In1] = data.bit(6);
|
||||
regs.control.enablewindow[Obj] = data.bit(7);
|
||||
bg0.io.enable = data.bit(0);
|
||||
bg1.io.enable = data.bit(1);
|
||||
bg2.io.enable = data.bit(2);
|
||||
bg3.io.enable = data.bit(3);
|
||||
objects.io.enable = data.bit(4);
|
||||
window0.io.enable = data.bit(5);
|
||||
window1.io.enable = data.bit(6);
|
||||
window2.io.enable = data.bit(7);
|
||||
//outside window is enabled whenever any inside window is enabled
|
||||
window3.io.enable = (bool)data.bits(5,7);
|
||||
return;
|
||||
|
||||
//GRSWP
|
||||
case 0x0400'0002:
|
||||
regs.greenswap = data.bit(0);
|
||||
io.greenSwap = data.bit(0);
|
||||
return;
|
||||
case 0x0400'0003:
|
||||
return;
|
||||
case 0x0400'0003: return;
|
||||
|
||||
//DISPSTAT
|
||||
case 0x0400'0004:
|
||||
regs.status.irqvblank = data.bit(3);
|
||||
regs.status.irqhblank = data.bit(4);
|
||||
regs.status.irqvcoincidence = data.bit(5);
|
||||
io.irqvblank = data.bit(3);
|
||||
io.irqhblank = data.bit(4);
|
||||
io.irqvcoincidence = data.bit(5);
|
||||
return;
|
||||
case 0x0400'0005:
|
||||
regs.status.vcompare = data;
|
||||
io.vcompare = data;
|
||||
return;
|
||||
|
||||
//BG0CNT, BG1CNT, BG2CNT, BG3CNT
|
||||
case 0x0400'0008: case 0x0400'000a: case 0x0400'000c: case 0x0400'000e:
|
||||
bgcnt().priority = data.bits(0,1);
|
||||
bgcnt().characterbaseblock = data.bits(2,3);
|
||||
bgcnt().unused = data.bits(4,5);
|
||||
bgcnt().mosaic = data.bit (6);
|
||||
bgcnt().colormode = data.bit (7);
|
||||
//BG0CNT
|
||||
case 0x0400'0008:
|
||||
bg0.io.priority = data.bits(0,1);
|
||||
bg0.io.characterBase = data.bits(2,3);
|
||||
bg0.io.unused = data.bits(4,5);
|
||||
bg0.io.mosaic = data.bit (6);
|
||||
bg0.io.colorMode = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0009: case 0x0400'000b: case 0x0400'000d: case 0x0400'000f:
|
||||
if(addr.bits(1,2) <= 1) data.bit(5) = 0; //clear affine wrap for BG0, BG1
|
||||
bgcnt().screenbaseblock = data.bits(0,4);
|
||||
bgcnt().affinewrap = data.bit (5);
|
||||
bgcnt().screensize = data.bits(6,7);
|
||||
case 0x0400'0009:
|
||||
bg0.io.screenBase = data.bits(0,4);
|
||||
bg0.io.affineWrap = false;
|
||||
bg0.io.screenSize = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//BG0HOFS, BG1HOFS, BG2BOFS, BG3HOFS
|
||||
case 0x0400'0010: case 0x0400'0014: case 0x0400'0018: case 0x0400'001c:
|
||||
bgofs().hoffset.bits(0,7) = data;
|
||||
//BG1CNT
|
||||
case 0x0400'000a:
|
||||
bg1.io.priority = data.bits(0,1);
|
||||
bg1.io.characterBase = data.bits(2,3);
|
||||
bg1.io.unused = data.bits(4,5);
|
||||
bg1.io.mosaic = data.bit (6);
|
||||
bg1.io.colorMode = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0011: case 0x0400'0015: case 0x0400'0019: case 0x0400'001d:
|
||||
bgofs().hoffset.bit(8) = data.bit(0);
|
||||
case 0x0400'000b:
|
||||
bg1.io.screenBase = data.bits(0,4);
|
||||
bg1.io.affineWrap = false;
|
||||
bg1.io.screenSize = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//BG0VOFS, BG1VOFS, BG2VOFS, BG3VOFS
|
||||
case 0x0400'0012: case 0x0400'0016: case 0x0400'001a: case 0x0400'001e:
|
||||
bgofs().voffset.bits(0,7) = data;
|
||||
//BG2CNT
|
||||
case 0x0400'000c:
|
||||
bg2.io.priority = data.bits(0,1);
|
||||
bg2.io.characterBase = data.bits(2,3);
|
||||
bg2.io.unused = data.bits(4,5);
|
||||
bg2.io.mosaic = data.bit (6);
|
||||
bg2.io.colorMode = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0013: case 0x0400'0017: case 0x0400'001b: case 0x0400'001f:
|
||||
bgofs().voffset.bit(8) = data.bit(0);
|
||||
case 0x0400'000d:
|
||||
bg2.io.screenBase = data.bits(0,4);
|
||||
bg2.io.affineWrap = data.bit (5);
|
||||
bg2.io.screenSize = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//BG2PA, BG3PA
|
||||
case 0x0400'0020: case 0x0400'0030: bg().pa.byte(0) = data; return;
|
||||
case 0x0400'0021: case 0x0400'0031: bg().pa.byte(1) = data; return;
|
||||
//BG3CNT
|
||||
case 0x0400'000e:
|
||||
bg3.io.priority = data.bits(0,1);
|
||||
bg3.io.characterBase = data.bits(2,3);
|
||||
bg3.io.unused = data.bits(4,5);
|
||||
bg3.io.mosaic = data.bit (6);
|
||||
bg3.io.colorMode = data.bit (7);
|
||||
return;
|
||||
case 0x0400'000f:
|
||||
bg3.io.screenBase = data.bits(0,4);
|
||||
bg3.io.affineWrap = data.bit (5);
|
||||
bg3.io.screenSize = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//BG2PB, BG3PB
|
||||
case 0x0400'0022: case 0x0400'0032: bg().pb.byte(0) = data; return;
|
||||
case 0x0400'0023: case 0x0400'0033: bg().pb.byte(1) = data; return;
|
||||
//BG0HOFS
|
||||
case 0x0400'0010: bg0.io.hoffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'0011: bg0.io.hoffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2PC, BG3PC
|
||||
case 0x0400'0024: case 0x0400'0034: bg().pc.byte(0) = data; return;
|
||||
case 0x0400'0025: case 0x0400'0035: bg().pc.byte(1) = data; return;
|
||||
//BG0VOFS
|
||||
case 0x0400'0012: bg0.io.voffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'0013: bg0.io.voffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2PD, BG3PD
|
||||
case 0x0400'0026: case 0x0400'0036: bg().pd.byte(0) = data; return;
|
||||
case 0x0400'0027: case 0x0400'0037: bg().pd.byte(1) = data; return;
|
||||
//BG1HOFS
|
||||
case 0x0400'0014: bg1.io.hoffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'0015: bg1.io.hoffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2X_L, BG2X_H, BG3X_L, BG3X_H
|
||||
case 0x0400'0028: case 0x0400'0038: bg().x.bits( 0, 7) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'0029: case 0x0400'0039: bg().x.bits( 8,15) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'002a: case 0x0400'003a: bg().x.bits(16,23) = data.bits(0,7); bg().lx = bg().x; return;
|
||||
case 0x0400'002b: case 0x0400'003b: bg().x.bits(24,27) = data.bits(0,3); bg().lx = bg().x; return;
|
||||
//BG1VOFS
|
||||
case 0x0400'0016: bg1.io.voffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'0017: bg1.io.voffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2Y_L, BG2Y_H, BG3Y_L, BG3Y_H
|
||||
case 0x0400'002c: case 0x0400'003c: bg().y.bits( 0, 7) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002d: case 0x0400'003d: bg().y.bits( 8,15) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002e: case 0x0400'003e: bg().y.bits(16,23) = data.bits(0,7); bg().ly = bg().y; return;
|
||||
case 0x0400'002f: case 0x0400'003f: bg().y.bits(24,27) = data.bits(0,3); bg().ly = bg().y; return;
|
||||
//BG2HOFS
|
||||
case 0x0400'0018: bg2.io.hoffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'0019: bg2.io.hoffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2VOFS
|
||||
case 0x0400'001a: bg2.io.voffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'001b: bg2.io.voffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG3HOFS
|
||||
case 0x0400'001c: bg3.io.hoffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'001d: bg3.io.hoffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG3VOFS
|
||||
case 0x0400'001e: bg3.io.voffset.bits(0,7) = data.bits(0,7); return;
|
||||
case 0x0400'001f: bg3.io.voffset.bit (8) = data.bit (8); return;
|
||||
|
||||
//BG2PA
|
||||
case 0x0400'0020: bg2.io.pa.byte(0) = data; return;
|
||||
case 0x0400'0021: bg2.io.pa.byte(1) = data; return;
|
||||
|
||||
//BG2PB
|
||||
case 0x0400'0022: bg2.io.pb.byte(0) = data; return;
|
||||
case 0x0400'0023: bg2.io.pb.byte(1) = data; return;
|
||||
|
||||
//BG2PC
|
||||
case 0x0400'0024: bg2.io.pc.byte(0) = data; return;
|
||||
case 0x0400'0025: bg2.io.pc.byte(1) = data; return;
|
||||
|
||||
//BG2PD
|
||||
case 0x0400'0026: bg2.io.pd.byte(0) = data; return;
|
||||
case 0x0400'0027: bg2.io.pd.byte(1) = data; return;
|
||||
|
||||
//BG2X
|
||||
case 0x0400'0028: bg2.io.x.bits( 0, 7) = data.bits(0,7); bg2.io.lx = bg2.io.x; return;
|
||||
case 0x0400'0029: bg2.io.x.bits( 8,15) = data.bits(0,7); bg2.io.lx = bg2.io.x; return;
|
||||
case 0x0400'002a: bg2.io.x.bits(16,23) = data.bits(0,7); bg2.io.lx = bg2.io.x; return;
|
||||
case 0x0400'002b: bg2.io.x.bits(24,27) = data.bits(0,3); bg2.io.lx = bg2.io.x; return;
|
||||
|
||||
//BG2Y
|
||||
case 0x0400'002c: bg2.io.y.bits( 0, 7) = data.bits(0,7); bg2.io.ly = bg2.io.y; return;
|
||||
case 0x0400'002d: bg2.io.y.bits( 8,15) = data.bits(0,7); bg2.io.ly = bg2.io.y; return;
|
||||
case 0x0400'002e: bg2.io.y.bits(16,23) = data.bits(0,7); bg2.io.ly = bg2.io.y; return;
|
||||
case 0x0400'002f: bg2.io.y.bits(24,27) = data.bits(0,3); bg2.io.ly = bg2.io.y; return;
|
||||
|
||||
//BG3PA
|
||||
case 0x0400'0030: bg3.io.pa.byte(0) = data; return;
|
||||
case 0x0400'0031: bg3.io.pa.byte(1) = data; return;
|
||||
|
||||
//BG3PB
|
||||
case 0x0400'0032: bg3.io.pb.byte(0) = data; return;
|
||||
case 0x0400'0033: bg3.io.pb.byte(0) = data; return;
|
||||
|
||||
//BG3PC
|
||||
case 0x0400'0034: bg3.io.pc.byte(0) = data; return;
|
||||
case 0x0400'0035: bg3.io.pc.byte(1) = data; return;
|
||||
|
||||
//BG3PD
|
||||
case 0x0400'0036: bg3.io.pd.byte(0) = data; return;
|
||||
case 0x0400'0037: bg3.io.pd.byte(1) = data; return;
|
||||
|
||||
//BG3X
|
||||
case 0x0400'0038: bg3.io.x.bits( 0, 7) = data.bits(0,7); bg3.io.lx = bg3.io.x; return;
|
||||
case 0x0400'0039: bg3.io.x.bits( 8,15) = data.bits(0,7); bg3.io.lx = bg3.io.x; return;
|
||||
case 0x0400'003a: bg3.io.x.bits(16,23) = data.bits(0,7); bg3.io.lx = bg3.io.x; return;
|
||||
case 0x0400'003b: bg3.io.x.bits(24,27) = data.bits(0,3); bg3.io.lx = bg3.io.x; return;
|
||||
|
||||
//BG3Y
|
||||
case 0x0400'003c: bg3.io.y.bits( 0, 7) = data.bits(0,7); bg3.io.ly = bg3.io.y; return;
|
||||
case 0x0400'003d: bg3.io.y.bits( 8,15) = data.bits(0,7); bg3.io.ly = bg3.io.y; return;
|
||||
case 0x0400'003e: bg3.io.y.bits(16,23) = data.bits(0,7); bg3.io.ly = bg3.io.y; return;
|
||||
case 0x0400'003f: bg3.io.y.bits(24,27) = data.bits(0,3); bg3.io.ly = bg3.io.y; return;
|
||||
|
||||
//WIN0H
|
||||
case 0x0400'0040: regs.window[0].x2 = data; return;
|
||||
case 0x0400'0041: regs.window[0].x1 = data; return;
|
||||
case 0x0400'0040: window0.io.x2 = data; return;
|
||||
case 0x0400'0041: window0.io.x1 = data; return;
|
||||
|
||||
//WIN1H
|
||||
case 0x0400'0042: regs.window[1].x2 = data; return;
|
||||
case 0x0400'0043: regs.window[1].x1 = data; return;
|
||||
case 0x0400'0042: window1.io.x2 = data; return;
|
||||
case 0x0400'0043: window1.io.x1 = data; return;
|
||||
|
||||
//WIN0V
|
||||
case 0x0400'0044: regs.window[0].y2 = data; return;
|
||||
case 0x0400'0045: regs.window[0].y1 = data; return;
|
||||
case 0x0400'0044: window0.io.y2 = data; return;
|
||||
case 0x0400'0045: window0.io.y1 = data; return;
|
||||
|
||||
//WIN1V
|
||||
case 0x0400'0046: regs.window[1].y2 = data; return;
|
||||
case 0x0400'0047: regs.window[1].y1 = data; return;
|
||||
case 0x0400'0046: window1.io.y2 = data; return;
|
||||
case 0x0400'0047: window1.io.y1 = data; return;
|
||||
|
||||
//WININ, WINOUT
|
||||
case 0x0400'0048: case 0x0400'0049: case 0x0400'004a: case 0x0400'004b:
|
||||
wf().enable[BG0] = data.bit(0);
|
||||
wf().enable[BG1] = data.bit(1);
|
||||
wf().enable[BG2] = data.bit(2);
|
||||
wf().enable[BG3] = data.bit(3);
|
||||
wf().enable[OBJ] = data.bit(4);
|
||||
wf().enable[SFX] = data.bit(5);
|
||||
//WININ0
|
||||
case 0x0400'0048:
|
||||
window0.io.active[BG0] = data.bit(0);
|
||||
window0.io.active[BG1] = data.bit(1);
|
||||
window0.io.active[BG2] = data.bit(2);
|
||||
window0.io.active[BG3] = data.bit(3);
|
||||
window0.io.active[OBJ] = data.bit(4);
|
||||
window0.io.active[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//WININ1
|
||||
case 0x0400'0049:
|
||||
window1.io.active[BG0] = data.bit(0);
|
||||
window1.io.active[BG1] = data.bit(1);
|
||||
window1.io.active[BG2] = data.bit(2);
|
||||
window1.io.active[BG3] = data.bit(3);
|
||||
window1.io.active[OBJ] = data.bit(4);
|
||||
window1.io.active[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//WINOUT
|
||||
case 0x0400'004a:
|
||||
window3.io.active[BG0] = data.bit(0);
|
||||
window3.io.active[BG1] = data.bit(1);
|
||||
window3.io.active[BG2] = data.bit(2);
|
||||
window3.io.active[BG3] = data.bit(3);
|
||||
window3.io.active[OBJ] = data.bit(4);
|
||||
window3.io.active[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//WININ2
|
||||
case 0x0400'004b:
|
||||
window2.io.active[BG0] = data.bit(0);
|
||||
window2.io.active[BG1] = data.bit(1);
|
||||
window2.io.active[BG2] = data.bit(2);
|
||||
window2.io.active[BG3] = data.bit(3);
|
||||
window2.io.active[OBJ] = data.bit(4);
|
||||
window2.io.active[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//MOSAIC
|
||||
case 0x0400'004c:
|
||||
regs.mosaic.bghsize = data.bits(0,3);
|
||||
regs.mosaic.bgvsize = data.bits(4,7);
|
||||
Background::IO::mosaicWidth = data.bits(0,3);
|
||||
Background::IO::mosaicHeight = data.bits(4,7);
|
||||
return;
|
||||
case 0x0400'004d:
|
||||
regs.mosaic.objhsize = data.bits(0,3);
|
||||
regs.mosaic.objvsize = data.bits(4,7);
|
||||
objects.io.mosaicWidth = data.bits(0,3);
|
||||
objects.io.mosaicHeight = data.bits(4,7);
|
||||
return;
|
||||
|
||||
//BLDCNT
|
||||
case 0x0400'0050:
|
||||
regs.blend.control.above[BG0] = data.bit (0);
|
||||
regs.blend.control.above[BG1] = data.bit (1);
|
||||
regs.blend.control.above[BG2] = data.bit (2);
|
||||
regs.blend.control.above[BG3] = data.bit (3);
|
||||
regs.blend.control.above[OBJ] = data.bit (4);
|
||||
regs.blend.control.above[SFX] = data.bit (5);
|
||||
regs.blend.control.mode = data.bits(6,7);
|
||||
screen.io.blendAbove[BG0] = data.bit (0);
|
||||
screen.io.blendAbove[BG1] = data.bit (1);
|
||||
screen.io.blendAbove[BG2] = data.bit (2);
|
||||
screen.io.blendAbove[BG3] = data.bit (3);
|
||||
screen.io.blendAbove[OBJ] = data.bit (4);
|
||||
screen.io.blendAbove[SFX] = data.bit (5);
|
||||
screen.io.blendMode = data.bits(6,7);
|
||||
return;
|
||||
case 0x0400'0051:
|
||||
regs.blend.control.below[BG0] = data.bit(0);
|
||||
regs.blend.control.below[BG1] = data.bit(1);
|
||||
regs.blend.control.below[BG2] = data.bit(2);
|
||||
regs.blend.control.below[BG3] = data.bit(3);
|
||||
regs.blend.control.below[OBJ] = data.bit(4);
|
||||
regs.blend.control.below[SFX] = data.bit(5);
|
||||
screen.io.blendBelow[BG0] = data.bit(0);
|
||||
screen.io.blendBelow[BG1] = data.bit(1);
|
||||
screen.io.blendBelow[BG2] = data.bit(2);
|
||||
screen.io.blendBelow[BG3] = data.bit(3);
|
||||
screen.io.blendBelow[OBJ] = data.bit(4);
|
||||
screen.io.blendBelow[SFX] = data.bit(5);
|
||||
return;
|
||||
|
||||
//BLDALPHA
|
||||
case 0x0400'0052: regs.blend.eva = data.bits(0,4); return;
|
||||
case 0x0400'0053: regs.blend.evb = data.bits(0,4); return;
|
||||
case 0x0400'0052: screen.io.blendEVA = data.bits(0,4); return;
|
||||
case 0x0400'0053: screen.io.blendEVB = data.bits(0,4); return;
|
||||
|
||||
//BLDY
|
||||
case 0x0400'0054: regs.blend.evy = data.bits(0,4); return;
|
||||
case 0x0400'0054: screen.io.blendEVY = data.bits(0,4); return;
|
||||
case 0x0400'0055: return;
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ auto PPU::readVRAM(uint mode, uint32 addr) -> uint32 {
|
|||
return vram[addr];
|
||||
}
|
||||
|
||||
return 0; //should never occur
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto PPU::writeVRAM(uint mode, uint32 addr, uint32 word) -> void {
|
||||
|
@ -29,8 +29,8 @@ auto PPU::writeVRAM(uint mode, uint32 addr, uint32 word) -> void {
|
|||
vram[addr + 1] = word >> 8;
|
||||
} else if(mode & Byte) {
|
||||
//8-bit writes to OBJ section of VRAM are ignored
|
||||
if(regs.control.bgmode <= 2 && addr >= 0x10000) return;
|
||||
if(regs.control.bgmode <= 5 && addr >= 0x14000) return;
|
||||
if(Background::IO::mode <= 2 && addr >= 0x10000) return;
|
||||
if(Background::IO::mode <= 5 && addr >= 0x14000) return;
|
||||
|
||||
addr &= ~1;
|
||||
vram[addr + 0] = (uint8)word;
|
||||
|
@ -64,14 +64,14 @@ auto PPU::readOAM(uint mode, uint32 addr) -> uint32 {
|
|||
if(mode & Byte) return readOAM(Half, addr) >> ((addr & 1) * 8);
|
||||
|
||||
auto& obj = object[addr >> 3 & 127];
|
||||
auto& par = objectparam[addr >> 5 & 31];
|
||||
auto& par = objectParam[addr >> 5 & 31];
|
||||
|
||||
switch(addr & 6) {
|
||||
|
||||
case 0: return (
|
||||
(obj.y << 0)
|
||||
| (obj.affine << 8)
|
||||
| (obj.affinesize << 9)
|
||||
| (obj.affineSize << 9)
|
||||
| (obj.mode << 10)
|
||||
| (obj.mosaic << 12)
|
||||
| (obj.colors << 13)
|
||||
|
@ -80,7 +80,7 @@ auto PPU::readOAM(uint mode, uint32 addr) -> uint32 {
|
|||
|
||||
case 2: return (
|
||||
(obj.x << 0)
|
||||
| (obj.affineparam << 9)
|
||||
| (obj.affineParam << 9)
|
||||
| (obj.hflip << 12)
|
||||
| (obj.vflip << 13)
|
||||
| (obj.size << 14)
|
||||
|
@ -113,13 +113,13 @@ auto PPU::writeOAM(uint mode, uint32 addr, uint32 word) -> void {
|
|||
if(mode & Byte) return; //8-bit writes to OAM are ignored
|
||||
|
||||
auto& obj = object[addr >> 3 & 127];
|
||||
auto& par = objectparam[addr >> 5 & 31];
|
||||
auto& par = objectParam[addr >> 5 & 31];
|
||||
switch(addr & 6) {
|
||||
|
||||
case 0:
|
||||
obj.y = word >> 0;
|
||||
obj.affine = word >> 8;
|
||||
obj.affinesize = word >> 9;
|
||||
obj.affineSize = word >> 9;
|
||||
obj.mode = word >> 10;
|
||||
obj.mosaic = word >> 12;
|
||||
obj.colors = word >> 13;
|
||||
|
@ -128,7 +128,7 @@ auto PPU::writeOAM(uint mode, uint32 addr, uint32 word) -> void {
|
|||
|
||||
case 2:
|
||||
obj.x = word >> 0;
|
||||
obj.affineparam = word >> 9;
|
||||
obj.affineParam = word >> 9;
|
||||
obj.hflip = word >> 12;
|
||||
obj.vflip = word >> 13;
|
||||
obj.size = word >> 14;
|
||||
|
@ -167,3 +167,10 @@ auto PPU::writeOAM(uint mode, uint32 addr, uint32 word) -> void {
|
|||
obj.width = widths [obj.shape * 4 + obj.size];
|
||||
obj.height = heights[obj.shape * 4 + obj.size];
|
||||
}
|
||||
|
||||
auto PPU::readObjectVRAM(uint addr) const -> uint8 {
|
||||
if(Background::IO::mode == 3 || Background::IO::mode == 4 || Background::IO::mode == 5) {
|
||||
if(addr <= 0x3fff) return 0u;
|
||||
}
|
||||
return vram[0x10000 + (addr & 0x7fff)];
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/*
|
||||
auto PPU::renderMosaicBackground(uint id) -> void {
|
||||
if(regs.mosaic.bghsize == 0) return;
|
||||
uint width = 1 + regs.mosaic.bghsize;
|
||||
|
@ -17,7 +18,7 @@ auto PPU::renderMosaicObject() -> void {
|
|||
uint width = 1 + regs.mosaic.objhsize;
|
||||
auto& buffer = layer[OBJ];
|
||||
|
||||
Pixel mosaicPixel;
|
||||
PixelData mosaicPixel;
|
||||
mosaicPixel.mosaic = false;
|
||||
uint counter = 0;
|
||||
|
||||
|
@ -31,3 +32,4 @@ auto PPU::renderMosaicObject() -> void {
|
|||
counter++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
auto PPU::renderObjects() -> void {
|
||||
if(regs.control.enable[OBJ] == false) return;
|
||||
for(auto n : range(128)) renderObject(object[n]);
|
||||
}
|
||||
|
||||
/*
|
||||
//px,py = pixel coordinates within sprite [0,0 - width,height)
|
||||
//fx,fy = affine pixel coordinates
|
||||
//pa,pb,pc,pd = affine pixel adjustments
|
||||
//x,y = adjusted coordinates within sprite (linear = vflip/hflip, affine = rotation/zoom)
|
||||
auto PPU::renderObject(Object& obj) -> void {
|
||||
auto PPU::renderObject(ObjectInfo& obj) -> void {
|
||||
uint8 py = regs.vcounter - obj.y;
|
||||
if(obj.affine == 0 && obj.affinesize == 1) return; //hidden
|
||||
if(py >= obj.height << obj.affinesize) return; //offscreen
|
||||
|
@ -71,10 +67,86 @@ auto PPU::renderObject(Object& obj) -> void {
|
|||
fy += pc;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::readObjectVRAM(uint addr) const -> uint8 {
|
||||
if(regs.control.bgmode == 3 || regs.control.bgmode == 4 || regs.control.bgmode == 5) {
|
||||
if(addr <= 0x3fff) return 0u;
|
||||
auto PPU::Objects::scanline(uint y) -> void {
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
if(ppu.blank() || !io.enable) return;
|
||||
|
||||
for(auto& object : ppu.object) {
|
||||
uint8 py = y - object.y;
|
||||
if(object.affine == 0 && object.affineSize == 1) continue; //hidden
|
||||
if(py >= object.height << object.affineSize) continue; //offscreen
|
||||
|
||||
uint rowSize = io.mapping == 0 ? 32 >> object.colors : object.width >> 3;
|
||||
uint baseAddress = object.character << 5;
|
||||
|
||||
if(object.mosaic && io.mosaicHeight) {
|
||||
int mosaicY = (y / (1 + io.mosaicHeight)) * (1 + io.mosaicHeight);
|
||||
py = object.y >= 160 || mosaicY - object.y >= 0 ? mosaicY - object.y : 0;
|
||||
}
|
||||
|
||||
int16 pa = ppu.objectParam[object.affineParam].pa;
|
||||
int16 pb = ppu.objectParam[object.affineParam].pb;
|
||||
int16 pc = ppu.objectParam[object.affineParam].pc;
|
||||
int16 pd = ppu.objectParam[object.affineParam].pd;
|
||||
|
||||
//center-of-sprite coordinates
|
||||
int16 centerX = object.width >> 1;
|
||||
int16 centerY = object.height >> 1;
|
||||
|
||||
//origin coordinates (top-left of sprite)
|
||||
int28 originX = -(centerX << object.affineSize);
|
||||
int28 originY = -(centerY << object.affineSize) + py;
|
||||
|
||||
//fractional pixel coordinates
|
||||
int28 fx = originX * pa + originY * pb;
|
||||
int28 fy = originX * pc + originY * pd;
|
||||
|
||||
for(uint px : range(object.width << object.affineSize)) {
|
||||
uint sx, sy;
|
||||
if(!object.affine) {
|
||||
sx = px ^ (object.hflip ? object.width - 1 : 0);
|
||||
sy = py ^ (object.vflip ? object.height - 1 : 0);
|
||||
} else {
|
||||
sx = (fx >> 8) + centerX;
|
||||
sy = (fy >> 8) + centerY;
|
||||
}
|
||||
|
||||
uint9 bx = object.x + px;
|
||||
if(bx < 240 && sx < object.width && sy < object.height) {
|
||||
uint offset = (sy >> 3) * rowSize + (sx >> 3);
|
||||
offset = offset * 64 + (sy & 7) * 8 + (sx & 7);
|
||||
|
||||
uint8 color = ppu.readObjectVRAM(baseAddress + (offset >> !object.colors));
|
||||
if(object.colors == 0) color = sx & 1 ? color >> 4 : color & 15;
|
||||
if(color) {
|
||||
if(object.mode & 2) {
|
||||
buffer[bx].window = true;
|
||||
} else if(!buffer[bx].enable || object.priority < buffer[bx].priority) {
|
||||
if(object.colors == 0) color = object.palette * 16 + color;
|
||||
buffer[bx].enable = true;
|
||||
buffer[bx].priority = object.priority;
|
||||
buffer[bx].color = ppu.pram[256 + color];
|
||||
buffer[bx].translucent = object.mode == 1;
|
||||
buffer[bx].mosaic = object.mosaic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fx += pa;
|
||||
fy += pc;
|
||||
}
|
||||
}
|
||||
return vram[0x10000 + (addr & 0x7fff)];
|
||||
}
|
||||
|
||||
auto PPU::Objects::run(uint x, uint y) -> void {
|
||||
output = {};
|
||||
if(ppu.blank() || !io.enable) return;
|
||||
|
||||
output = buffer[x];
|
||||
}
|
||||
|
||||
auto PPU::Objects::power() -> void {
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
||||
|
|
|
@ -15,19 +15,19 @@ namespace GameBoyAdvance {
|
|||
PPU ppu;
|
||||
#include "background.cpp"
|
||||
#include "object.cpp"
|
||||
#include "window.cpp"
|
||||
#include "mosaic.cpp"
|
||||
#include "screen.cpp"
|
||||
#include "io.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::blank() -> bool {
|
||||
return io.forceBlank || cpu.regs.mode == CPU::Registers::Mode::Stop;
|
||||
}
|
||||
|
||||
PPU::PPU() {
|
||||
output = new uint32[240 * 160];
|
||||
|
||||
regs.bg[0].id = BG0;
|
||||
regs.bg[1].id = BG1;
|
||||
regs.bg[2].id = BG2;
|
||||
regs.bg[3].id = BG3;
|
||||
}
|
||||
|
||||
PPU::~PPU() {
|
||||
|
@ -38,142 +38,71 @@ auto PPU::Enter() -> void {
|
|||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
scanline();
|
||||
}
|
||||
|
||||
auto PPU::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(PPU::Enter, 16'777'216);
|
||||
|
||||
for(uint n = 0; n < 240 * 160; n++) output[n] = 0;
|
||||
|
||||
for(uint n = 0; n < 1024; n += 2) writePRAM(n, Half, 0x0000);
|
||||
for(uint n = 0; n < 1024; n += 2) writeOAM(n, Half, 0x0000);
|
||||
|
||||
regs.control.bgmode = 0;
|
||||
regs.control.cgbmode = 0;
|
||||
regs.control.frame = 0;
|
||||
regs.control.hblank = 0;
|
||||
regs.control.objmapping = 0;
|
||||
regs.control.forceblank = 0;
|
||||
for(auto& enable : regs.control.enable) enable = 0;
|
||||
for(auto& enablewindow : regs.control.enablewindow) enablewindow = 0;
|
||||
regs.greenswap = 0;
|
||||
regs.status.vblank = 0;
|
||||
regs.status.hblank = 0;
|
||||
regs.status.vcoincidence = 0;
|
||||
regs.status.irqvblank = 0;
|
||||
regs.status.irqhblank = 0;
|
||||
regs.status.irqvcoincidence = 0;
|
||||
regs.status.vcompare = 0;
|
||||
regs.vcounter = 0;
|
||||
for(auto& bg : regs.bg) {
|
||||
bg.control.priority = 0;
|
||||
bg.control.characterbaseblock = 0;
|
||||
bg.control.unused = 0;
|
||||
bg.control.mosaic = 0;
|
||||
bg.control.colormode = 0;
|
||||
bg.control.screenbaseblock = 0;
|
||||
bg.control.affinewrap = 0;
|
||||
bg.control.screensize = 0;
|
||||
bg.hoffset = 0;
|
||||
bg.voffset = 0;
|
||||
bg.pa = 0;
|
||||
bg.pb = 0;
|
||||
bg.pc = 0;
|
||||
bg.pd = 0;
|
||||
bg.x = 0;
|
||||
bg.y = 0;
|
||||
bg.lx = 0;
|
||||
bg.ly = 0;
|
||||
}
|
||||
for(auto& w : regs.window) {
|
||||
w.x1 = 0;
|
||||
w.x2 = 0;
|
||||
w.y1 = 0;
|
||||
w.y2 = 0;
|
||||
}
|
||||
for(auto& flags : regs.windowflags) {
|
||||
for(auto& enable : flags.enable) enable = 0;
|
||||
}
|
||||
regs.mosaic.bghsize = 0;
|
||||
regs.mosaic.bgvsize = 0;
|
||||
regs.mosaic.objhsize = 0;
|
||||
regs.mosaic.objvsize = 0;
|
||||
for(auto& above : regs.blend.control.above) above = 0;
|
||||
regs.blend.control.mode = 0;
|
||||
for(auto& below : regs.blend.control.below) below = 0;
|
||||
regs.blend.eva = 0;
|
||||
regs.blend.evb = 0;
|
||||
regs.blend.evy = 0;
|
||||
|
||||
for(uint n = 0x000; n <= 0x055; n++) bus.io[n] = this;
|
||||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
auto PPU::main() -> void {
|
||||
cpu.keypadRun();
|
||||
|
||||
regs.status.vblank = regs.vcounter >= 160 && regs.vcounter <= 226;
|
||||
regs.status.vcoincidence = regs.vcounter == regs.status.vcompare;
|
||||
io.vblank = io.vcounter >= 160 && io.vcounter <= 226;
|
||||
io.vcoincidence = io.vcounter == io.vcompare;
|
||||
|
||||
if(regs.vcounter == 0) {
|
||||
if(io.vcounter == 0) {
|
||||
frame();
|
||||
|
||||
regs.bg[2].lx = regs.bg[2].x;
|
||||
regs.bg[2].ly = regs.bg[2].y;
|
||||
bg2.io.lx = bg2.io.x;
|
||||
bg2.io.ly = bg2.io.y;
|
||||
|
||||
regs.bg[3].lx = regs.bg[3].x;
|
||||
regs.bg[3].ly = regs.bg[3].y;
|
||||
bg3.io.lx = bg3.io.x;
|
||||
bg3.io.ly = bg3.io.y;
|
||||
}
|
||||
|
||||
if(regs.vcounter == 160) {
|
||||
if(regs.status.irqvblank) cpu.regs.irq.flag |= CPU::Interrupt::VBlank;
|
||||
if(io.vcounter == 160) {
|
||||
if(io.irqvblank) cpu.regs.irq.flag |= CPU::Interrupt::VBlank;
|
||||
cpu.dmaVblank();
|
||||
}
|
||||
|
||||
if(regs.status.irqvcoincidence) {
|
||||
if(regs.status.vcoincidence) cpu.regs.irq.flag |= CPU::Interrupt::VCoincidence;
|
||||
if(io.irqvcoincidence) {
|
||||
if(io.vcoincidence) cpu.regs.irq.flag |= CPU::Interrupt::VCoincidence;
|
||||
}
|
||||
|
||||
if(regs.vcounter < 160) {
|
||||
if(regs.control.forceblank || cpu.regs.mode == CPU::Registers::Mode::Stop) {
|
||||
renderForceBlank();
|
||||
if(io.vcounter < 160) {
|
||||
uint y = io.vcounter;
|
||||
bg0.scanline(y);
|
||||
bg1.scanline(y);
|
||||
bg2.scanline(y);
|
||||
bg3.scanline(y);
|
||||
objects.scanline(y);
|
||||
for(uint x : range(240)) {
|
||||
bg0.run(x, y);
|
||||
bg1.run(x, y);
|
||||
bg2.run(x, y);
|
||||
bg3.run(x, y);
|
||||
objects.run(x, y);
|
||||
window0.run(x, y);
|
||||
window1.run(x, y);
|
||||
window2.output = objects.output.window;
|
||||
window3.output = true;
|
||||
uint15 color = screen.run(x, y);
|
||||
output[y * 240 + x] = color;
|
||||
step(4);
|
||||
}
|
||||
} else {
|
||||
for(auto x : range(240)) {
|
||||
windowmask[0][x] = false;
|
||||
windowmask[1][x] = false;
|
||||
windowmask[2][x] = false;
|
||||
layer[OBJ][x].write(false);
|
||||
layer[BG0][x].write(false);
|
||||
layer[BG1][x].write(false);
|
||||
layer[BG2][x].write(false);
|
||||
layer[BG3][x].write(false);
|
||||
layer[SFX][x].write(true, 3, pram[0]);
|
||||
}
|
||||
renderWindow(0);
|
||||
renderWindow(1);
|
||||
renderObjects();
|
||||
renderBackgrounds();
|
||||
renderScreen();
|
||||
}
|
||||
step(960);
|
||||
}
|
||||
|
||||
step(960);
|
||||
regs.status.hblank = 1;
|
||||
if(regs.status.irqhblank) cpu.regs.irq.flag |= CPU::Interrupt::HBlank;
|
||||
if(regs.vcounter < 160) cpu.dmaHblank();
|
||||
io.hblank = 1;
|
||||
if(io.irqhblank) cpu.regs.irq.flag |= CPU::Interrupt::HBlank;
|
||||
if(io.vcounter < 160) cpu.dmaHblank();
|
||||
|
||||
step(240);
|
||||
regs.status.hblank = 0;
|
||||
if(regs.vcounter < 160) cpu.dmaHDMA();
|
||||
io.hblank = 0;
|
||||
if(io.vcounter < 160) cpu.dmaHDMA();
|
||||
|
||||
step(32);
|
||||
if(++regs.vcounter == 228) regs.vcounter = 0;
|
||||
if(++io.vcounter == 228) io.vcounter = 0;
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
|
@ -185,4 +114,26 @@ auto PPU::refresh() -> void {
|
|||
Emulator::video.refresh(output, 240 * sizeof(uint32), 240, 160);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(PPU::Enter, 16'777'216);
|
||||
|
||||
for(uint n = 0x000; n <= 0x055; n++) bus.io[n] = this;
|
||||
|
||||
for(uint n = 0; n < 240 * 160; n++) output[n] = 0;
|
||||
|
||||
for(uint n = 0; n < 1024; n += 2) writePRAM(n, Half, 0x0000);
|
||||
for(uint n = 0; n < 1024; n += 2) writeOAM(n, Half, 0x0000);
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
bg0.power(BG0);
|
||||
bg1.power(BG1);
|
||||
bg2.power(BG2);
|
||||
bg3.power(BG3);
|
||||
window0.power(IN0);
|
||||
window1.power(IN1);
|
||||
window2.power(IN2);
|
||||
window3.power(OUT);
|
||||
screen.power();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
struct PPU : Thread, IO {
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
PPU();
|
||||
~PPU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
inline auto blank() -> bool;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
auto readIO(uint32 addr) -> uint8;
|
||||
auto writeIO(uint32 addr, uint8 byte) -> void;
|
||||
|
@ -26,28 +24,196 @@ struct PPU : Thread, IO {
|
|||
auto readOAM(uint mode, uint32 addr) -> uint32;
|
||||
auto writeOAM(uint mode, uint32 addr, uint32 word) -> void;
|
||||
|
||||
auto renderBackgrounds() -> void;
|
||||
auto renderBackgroundLinear(Registers::Background&) -> void;
|
||||
auto renderBackgroundAffine(Registers::Background&) -> void;
|
||||
auto renderBackgroundBitmap(Registers::Background&) -> void;
|
||||
|
||||
auto renderObjects() -> void;
|
||||
auto renderObject(Object&) -> void;
|
||||
auto readObjectVRAM(uint addr) const -> uint8;
|
||||
|
||||
auto renderMosaicBackground(uint id) -> void;
|
||||
auto renderMosaicObject() -> void;
|
||||
|
||||
auto renderForceBlank() -> void;
|
||||
auto renderScreen() -> void;
|
||||
auto renderWindow(uint window) -> void;
|
||||
auto blend(uint above, uint eva, uint below, uint evb) -> uint;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 vram[96 * 1024];
|
||||
uint16 pram[512];
|
||||
uint32* output;
|
||||
|
||||
//private:
|
||||
enum : uint { OBJ = 0, BG0 = 1, BG1 = 2, BG2 = 3, BG3 = 4, SFX = 5 };
|
||||
enum : uint { IN0 = 0, IN1 = 1, IN2 = 2, OUT = 3 };
|
||||
|
||||
struct IO {
|
||||
uint1 gameBoyColorMode;
|
||||
uint1 forceBlank;
|
||||
uint1 greenSwap;
|
||||
|
||||
uint1 vblank;
|
||||
uint1 hblank;
|
||||
uint1 vcoincidence;
|
||||
uint1 irqvblank;
|
||||
uint1 irqhblank;
|
||||
uint1 irqvcoincidence;
|
||||
uint8 vcompare;
|
||||
|
||||
uint16 vcounter;
|
||||
} io;
|
||||
|
||||
struct Pixel {
|
||||
uint1 enable;
|
||||
uint2 priority;
|
||||
uint15 color;
|
||||
|
||||
//OBJ only
|
||||
uint1 translucent;
|
||||
uint1 mosaic;
|
||||
uint1 window; //IN2
|
||||
};
|
||||
|
||||
struct Background {
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
auto linear(uint x, uint y) -> void;
|
||||
auto affine(uint x, uint y) -> void;
|
||||
auto bitmap(uint x, uint y) -> void;
|
||||
|
||||
auto power(uint id) -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint id; //BG0, BG1, BG2, BG3
|
||||
|
||||
struct IO {
|
||||
static uint3 mode;
|
||||
static uint1 frame;
|
||||
static uint5 mosaicWidth;
|
||||
static uint5 mosaicHeight;
|
||||
|
||||
uint1 enable;
|
||||
|
||||
uint2 priority;
|
||||
uint2 characterBase;
|
||||
uint2 unused;
|
||||
uint1 mosaic;
|
||||
uint1 colorMode;
|
||||
uint5 screenBase;
|
||||
uint1 affineWrap; //BG2, BG3 only
|
||||
uint2 screenSize;
|
||||
|
||||
uint9 hoffset;
|
||||
uint9 voffset;
|
||||
|
||||
//BG2, BG3 only
|
||||
int16 pa;
|
||||
int16 pb;
|
||||
int16 pc;
|
||||
int16 pd;
|
||||
int28 x;
|
||||
int28 y;
|
||||
|
||||
//internal
|
||||
int28 lx;
|
||||
int28 ly;
|
||||
} io;
|
||||
|
||||
Pixel output;
|
||||
|
||||
uint hmosaic;
|
||||
uint vmosaic;
|
||||
|
||||
uint9 hoffset;
|
||||
uint9 voffset;
|
||||
|
||||
int28 fx;
|
||||
int28 fy;
|
||||
} bg0, bg1, bg2, bg3;
|
||||
|
||||
struct Objects {
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct IO {
|
||||
uint1 enable;
|
||||
|
||||
uint1 hblank; //1 = allow access to OAM during Hblank
|
||||
uint1 mapping; //0 = two-dimensional, 1 = one-dimensional
|
||||
uint5 mosaicWidth;
|
||||
uint5 mosaicHeight;
|
||||
} io;
|
||||
|
||||
Pixel buffer[240];
|
||||
Pixel output;
|
||||
} objects;
|
||||
|
||||
struct Window {
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power(uint id) -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint id; //IN0, IN1, IN2, OUT
|
||||
|
||||
struct IO {
|
||||
uint1 enable;
|
||||
uint1 active[6];
|
||||
|
||||
//IN0, IN1 only
|
||||
uint8 x1;
|
||||
uint8 x2;
|
||||
uint8 y1;
|
||||
uint8 y2;
|
||||
} io;
|
||||
|
||||
uint1 output; //IN0, IN1, IN2 only
|
||||
} window0, window1, window2, window3;
|
||||
|
||||
struct Screen {
|
||||
auto run(uint x, uint y) -> uint15;
|
||||
auto blend(uint15 above, uint eva, uint15 below, uint evb) -> uint15;
|
||||
|
||||
auto power() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct IO {
|
||||
uint2 blendMode;
|
||||
uint1 blendAbove[6];
|
||||
uint1 blendBelow[6];
|
||||
|
||||
uint5 blendEVA;
|
||||
uint5 blendEVB;
|
||||
uint5 blendEVY;
|
||||
} io;
|
||||
} screen;
|
||||
|
||||
struct Object {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint8 y;
|
||||
uint1 affine;
|
||||
uint1 affineSize;
|
||||
uint2 mode;
|
||||
uint1 mosaic;
|
||||
uint1 colors; //0 = 16, 1 = 256
|
||||
uint2 shape; //0 = square, 1 = horizontal, 2 = vertical
|
||||
|
||||
uint9 x;
|
||||
uint5 affineParam;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint2 size;
|
||||
|
||||
uint10 character;
|
||||
uint2 priority;
|
||||
uint4 palette;
|
||||
|
||||
//ancillary data
|
||||
uint width;
|
||||
uint height;
|
||||
} object[128];
|
||||
|
||||
struct ObjectParam {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
int16 pa;
|
||||
int16 pb;
|
||||
int16 pc;
|
||||
int16 pd;
|
||||
} objectParam[32];
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
enum : uint { OBJ = 0, BG0 = 1, BG1 = 2, BG2 = 3, BG3 = 4, SFX = 5 };
|
||||
enum : uint { In0 = 0, In1 = 1, Obj = 2, Out = 3 };
|
||||
|
||||
struct Registers {
|
||||
struct Control {
|
||||
uint3 bgmode;
|
||||
uint1 cgbmode;
|
||||
uint1 frame;
|
||||
uint1 hblank;
|
||||
uint1 objmapping;
|
||||
uint1 forceblank;
|
||||
uint1 enable[5];
|
||||
uint1 enablewindow[3];
|
||||
} control;
|
||||
|
||||
uint1 greenswap;
|
||||
|
||||
struct Status {
|
||||
uint1 vblank;
|
||||
uint1 hblank;
|
||||
uint1 vcoincidence;
|
||||
uint1 irqvblank;
|
||||
uint1 irqhblank;
|
||||
uint1 irqvcoincidence;
|
||||
uint8 vcompare;
|
||||
} status;
|
||||
|
||||
uint16 vcounter;
|
||||
|
||||
struct Background {
|
||||
struct Control {
|
||||
uint2 priority;
|
||||
uint2 characterbaseblock;
|
||||
uint2 unused;
|
||||
uint1 mosaic;
|
||||
uint1 colormode;
|
||||
uint5 screenbaseblock;
|
||||
uint1 affinewrap; //BG2,3 only
|
||||
uint2 screensize;
|
||||
} control;
|
||||
uint9 hoffset;
|
||||
uint9 voffset;
|
||||
|
||||
//BG2,3 only
|
||||
int16 pa, pb, pc, pd;
|
||||
int28 x, y;
|
||||
|
||||
//internal
|
||||
int28 lx, ly;
|
||||
uint vmosaic;
|
||||
uint hmosaic;
|
||||
uint id;
|
||||
} bg[4];
|
||||
|
||||
struct Window {
|
||||
uint8 x1, x2;
|
||||
uint8 y1, y2;
|
||||
} window[2];
|
||||
|
||||
struct WindowFlags {
|
||||
uint1 enable[6];
|
||||
} windowflags[4];
|
||||
|
||||
struct Mosaic {
|
||||
uint4 bghsize;
|
||||
uint4 bgvsize;
|
||||
uint4 objhsize;
|
||||
uint4 objvsize;
|
||||
} mosaic;
|
||||
|
||||
struct Blend {
|
||||
struct Control {
|
||||
uint1 above[6];
|
||||
uint2 mode;
|
||||
uint1 below[6];
|
||||
} control;
|
||||
uint5 eva;
|
||||
uint5 evb;
|
||||
uint5 evy;
|
||||
} blend;
|
||||
} regs;
|
|
@ -1,15 +1,11 @@
|
|||
auto PPU::renderForceBlank() -> void {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
for(auto x : range(240)) line[x] = 0x7fff;
|
||||
}
|
||||
|
||||
/*
|
||||
auto PPU::renderScreen() -> void {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
|
||||
if(regs.bg[0].control.mosaic) renderMosaicBackground(BG0);
|
||||
if(regs.bg[1].control.mosaic) renderMosaicBackground(BG1);
|
||||
if(regs.bg[2].control.mosaic) renderMosaicBackground(BG2);
|
||||
if(regs.bg[3].control.mosaic) renderMosaicBackground(BG3);
|
||||
if(bg0.io.mosaic) renderMosaicBackground(BG0);
|
||||
if(bg1.io.mosaic) renderMosaicBackground(BG1);
|
||||
if(bg2.io.mosaic) renderMosaicBackground(BG2);
|
||||
if(bg3.io.mosaic) renderMosaicBackground(BG3);
|
||||
renderMosaicObject();
|
||||
|
||||
for(auto x : range(240)) {
|
||||
|
@ -65,24 +61,64 @@ auto PPU::renderScreen() -> void {
|
|||
line[x] = color;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::renderWindow(uint w) -> void {
|
||||
uint y = regs.vcounter;
|
||||
auto PPU::Screen::run(uint x, uint y) -> uint15 {
|
||||
if(ppu.blank()) return 0x7fff;
|
||||
|
||||
uint y1 = regs.window[w].y1, y2 = regs.window[w].y2;
|
||||
uint x1 = regs.window[w].x1, x2 = regs.window[w].x2;
|
||||
//determine active window
|
||||
uint1 active[6] = {true, true, true, true, true, true}; //enable all layers if no windows are enabled
|
||||
if(ppu.window0.io.enable || ppu.window1.io.enable || ppu.window2.io.enable) {
|
||||
memory::copy(&active, &ppu.window3.io.active, sizeof(active));
|
||||
if(ppu.window2.io.enable && ppu.window2.output) memory::copy(&active, &ppu.window2.io.active, sizeof(active));
|
||||
if(ppu.window1.io.enable && ppu.window1.output) memory::copy(&active, &ppu.window1.io.active, sizeof(active));
|
||||
if(ppu.window0.io.enable && ppu.window0.output) memory::copy(&active, &ppu.window0.io.active, sizeof(active));
|
||||
}
|
||||
|
||||
if(y2 < y1 || y2 > 160) y2 = 160;
|
||||
if(x2 < x1 || x2 > 240) x2 = 240;
|
||||
//priority sorting: find topmost two pixels
|
||||
Pixel layers[6] = {
|
||||
ppu.objects.output,
|
||||
ppu.bg0.output,
|
||||
ppu.bg1.output,
|
||||
ppu.bg2.output,
|
||||
ppu.bg3.output,
|
||||
{true, 3, ppu.pram[0]},
|
||||
};
|
||||
|
||||
if(y >= y1 && y < y2) {
|
||||
for(uint x = x1; x < x2; x++) {
|
||||
windowmask[w][x] = true;
|
||||
uint aboveLayer = 5, belowLayer = 5;
|
||||
for(int priority = 3; priority >= 0; priority--) {
|
||||
for(int layer = 5; layer >= 0; layer--) {
|
||||
if(layers[layer].enable && layers[layer].priority == priority && active[layer]) {
|
||||
belowLayer = aboveLayer;
|
||||
aboveLayer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto above = layers[aboveLayer];
|
||||
auto below = layers[belowLayer];
|
||||
auto eva = min(16u, (uint)io.blendEVA);
|
||||
auto evb = min(16u, (uint)io.blendEVB);
|
||||
auto evy = min(16u, (uint)io.blendEVY);
|
||||
uint15 color = above.color;
|
||||
|
||||
//color blending
|
||||
if(active[SFX]) {
|
||||
if(above.translucent && io.blendBelow[belowLayer]) {
|
||||
color = blend(above.color, eva, below.color, evb);
|
||||
} else if(io.blendMode == 1 && io.blendAbove[aboveLayer] && io.blendBelow[belowLayer]) {
|
||||
color = blend(above.color, eva, below.color, evb);
|
||||
} else if(io.blendMode == 2 && io.blendAbove[aboveLayer]) {
|
||||
color = blend(above.color, 16 - evy, 0x7fff, evy);
|
||||
} else if(io.blendMode == 3 && io.blendAbove[aboveLayer]) {
|
||||
color = blend(above.color, 16 - evy, 0x0000, evy);
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
auto PPU::blend(uint above, uint eva, uint below, uint evb) -> uint {
|
||||
auto PPU::Screen::blend(uint15 above, uint eva, uint15 below, uint evb) -> uint15 {
|
||||
uint5 ar = above >> 0, ag = above >> 5, ab = above >> 10;
|
||||
uint5 br = below >> 0, bg = below >> 5, bb = below >> 10;
|
||||
|
||||
|
@ -90,5 +126,9 @@ auto PPU::blend(uint above, uint eva, uint below, uint evb) -> uint {
|
|||
uint g = (ag * eva + bg * evb) >> 4;
|
||||
uint b = (ab * eva + bb * evb) >> 4;
|
||||
|
||||
return min(31, r) << 0 | min(31, g) << 5 | min(31, b) << 10;
|
||||
return min(31u, r) << 0 | min(31u, g) << 5 | min(31u, b) << 10;
|
||||
}
|
||||
|
||||
auto PPU::Screen::power() -> void {
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
||||
|
|
|
@ -4,121 +4,120 @@ auto PPU::serialize(serializer& s) -> void {
|
|||
s.array(vram, 96 * 1024);
|
||||
s.array(pram, 512);
|
||||
|
||||
s.integer(regs.control.bgmode);
|
||||
s.integer(regs.control.cgbmode);
|
||||
s.integer(regs.control.frame);
|
||||
s.integer(regs.control.hblank);
|
||||
s.integer(regs.control.objmapping);
|
||||
s.integer(regs.control.forceblank);
|
||||
for(auto& flag : regs.control.enable) s.integer(flag);
|
||||
for(auto& flag : regs.control.enablewindow) s.integer(flag);
|
||||
s.integer(io.gameBoyColorMode);
|
||||
s.integer(io.forceBlank);
|
||||
s.integer(io.greenSwap);
|
||||
s.integer(io.vblank);
|
||||
s.integer(io.hblank);
|
||||
s.integer(io.vcoincidence);
|
||||
s.integer(io.irqvblank);
|
||||
s.integer(io.irqhblank);
|
||||
s.integer(io.irqvcoincidence);
|
||||
s.integer(io.vcompare);
|
||||
s.integer(io.vcounter);
|
||||
|
||||
s.integer(regs.greenswap);
|
||||
|
||||
s.integer(regs.status.vblank);
|
||||
s.integer(regs.status.hblank);
|
||||
s.integer(regs.status.vcoincidence);
|
||||
s.integer(regs.status.irqvblank);
|
||||
s.integer(regs.status.irqhblank);
|
||||
s.integer(regs.status.irqvcoincidence);
|
||||
s.integer(regs.status.vcompare);
|
||||
|
||||
s.integer(regs.vcounter);
|
||||
|
||||
for(auto& bg : regs.bg) {
|
||||
s.integer(bg.control.priority);
|
||||
s.integer(bg.control.characterbaseblock);
|
||||
s.integer(bg.control.unused);
|
||||
s.integer(bg.control.mosaic);
|
||||
s.integer(bg.control.colormode);
|
||||
s.integer(bg.control.screenbaseblock);
|
||||
s.integer(bg.control.affinewrap);
|
||||
s.integer(bg.control.screensize);
|
||||
s.integer(bg.hoffset);
|
||||
s.integer(bg.voffset);
|
||||
s.integer(bg.pa);
|
||||
s.integer(bg.pb);
|
||||
s.integer(bg.pc);
|
||||
s.integer(bg.pd);
|
||||
s.integer(bg.x);
|
||||
s.integer(bg.y);
|
||||
s.integer(bg.lx);
|
||||
s.integer(bg.ly);
|
||||
s.integer(bg.vmosaic);
|
||||
s.integer(bg.hmosaic);
|
||||
s.integer(bg.id);
|
||||
}
|
||||
|
||||
for(auto& window : regs.window) {
|
||||
s.integer(window.x1);
|
||||
s.integer(window.x2);
|
||||
s.integer(window.y1);
|
||||
s.integer(window.y2);
|
||||
}
|
||||
|
||||
for(auto& windowflags : regs.windowflags) {
|
||||
for(auto& flag : windowflags.enable) s.integer(flag);
|
||||
}
|
||||
|
||||
s.integer(regs.mosaic.bghsize);
|
||||
s.integer(regs.mosaic.bgvsize);
|
||||
s.integer(regs.mosaic.objhsize);
|
||||
s.integer(regs.mosaic.objvsize);
|
||||
|
||||
for(auto& flag : regs.blend.control.above) s.integer(flag);
|
||||
for(auto& flag : regs.blend.control.below) s.integer(flag);
|
||||
s.integer(regs.blend.control.mode);
|
||||
s.integer(regs.blend.eva);
|
||||
s.integer(regs.blend.evb);
|
||||
s.integer(regs.blend.evy);
|
||||
|
||||
for(auto l : range(6)) {
|
||||
for(auto p : range(240)) {
|
||||
auto& pixel = layer[l][p];
|
||||
s.integer(pixel.enable);
|
||||
s.integer(pixel.priority);
|
||||
s.integer(pixel.color);
|
||||
s.integer(pixel.translucent);
|
||||
s.integer(pixel.mosaic);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto w : range(3)) {
|
||||
for(auto p : range(240)) {
|
||||
s.integer(windowmask[w][p]);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& value : vmosaic) s.integer(value);
|
||||
for(auto& value : hmosaic) s.integer(value);
|
||||
|
||||
for(auto& obj : object) {
|
||||
s.integer(obj.y);
|
||||
s.integer(obj.affine);
|
||||
s.integer(obj.affinesize);
|
||||
s.integer(obj.mode);
|
||||
s.integer(obj.mosaic);
|
||||
s.integer(obj.colors);
|
||||
s.integer(obj.shape);
|
||||
|
||||
s.integer(obj.x);
|
||||
s.integer(obj.affineparam);
|
||||
s.integer(obj.hflip);
|
||||
s.integer(obj.vflip);
|
||||
s.integer(obj.size);
|
||||
|
||||
s.integer(obj.character);
|
||||
s.integer(obj.priority);
|
||||
s.integer(obj.palette);
|
||||
|
||||
s.integer(obj.width);
|
||||
s.integer(obj.height);
|
||||
}
|
||||
|
||||
for(auto& par : objectparam) {
|
||||
s.integer(par.pa);
|
||||
s.integer(par.pb);
|
||||
s.integer(par.pc);
|
||||
s.integer(par.pd);
|
||||
}
|
||||
s.integer(Background::IO::mode);
|
||||
s.integer(Background::IO::frame);
|
||||
s.integer(Background::IO::mosaicWidth);
|
||||
s.integer(Background::IO::mosaicHeight);
|
||||
bg0.serialize(s);
|
||||
bg1.serialize(s);
|
||||
bg2.serialize(s);
|
||||
bg3.serialize(s);
|
||||
objects.serialize(s);
|
||||
window0.serialize(s);
|
||||
window1.serialize(s);
|
||||
window2.serialize(s);
|
||||
window3.serialize(s);
|
||||
screen.serialize(s);
|
||||
for(auto& object : this->object) object.serialize(s);
|
||||
for(auto& param : this->objectParam) param.serialize(s);
|
||||
}
|
||||
|
||||
auto PPU::Background::serialize(serializer& s) -> void {
|
||||
s.integer(id);
|
||||
|
||||
s.integer(io.enable);
|
||||
s.integer(io.priority);
|
||||
s.integer(io.characterBase);
|
||||
s.integer(io.unused);
|
||||
s.integer(io.mosaic);
|
||||
s.integer(io.colorMode);
|
||||
s.integer(io.screenBase);
|
||||
s.integer(io.affineWrap);
|
||||
s.integer(io.screenSize);
|
||||
s.integer(io.hoffset);
|
||||
s.integer(io.voffset);
|
||||
s.integer(io.pa);
|
||||
s.integer(io.pb);
|
||||
s.integer(io.pc);
|
||||
s.integer(io.pd);
|
||||
s.integer(io.x);
|
||||
s.integer(io.y);
|
||||
s.integer(io.lx);
|
||||
s.integer(io.ly);
|
||||
|
||||
s.integer(hmosaic);
|
||||
s.integer(vmosaic);
|
||||
s.integer(hoffset);
|
||||
s.integer(voffset);
|
||||
s.integer(fx);
|
||||
s.integer(fy);
|
||||
}
|
||||
|
||||
auto PPU::Objects::serialize(serializer& s) -> void {
|
||||
s.integer(io.enable);
|
||||
s.integer(io.hblank);
|
||||
s.integer(io.mapping);
|
||||
s.integer(io.mosaicWidth);
|
||||
s.integer(io.mosaicHeight);
|
||||
}
|
||||
|
||||
auto PPU::Window::serialize(serializer& s) -> void {
|
||||
s.integer(id);
|
||||
|
||||
s.integer(io.enable);
|
||||
s.array(io.active);
|
||||
s.integer(io.x1);
|
||||
s.integer(io.x2);
|
||||
s.integer(io.y1);
|
||||
s.integer(io.y2);
|
||||
|
||||
s.integer(output);
|
||||
}
|
||||
|
||||
auto PPU::Screen::serialize(serializer& s) -> void {
|
||||
s.integer(io.blendMode);
|
||||
s.array(io.blendAbove);
|
||||
s.array(io.blendBelow);
|
||||
s.integer(io.blendEVA);
|
||||
s.integer(io.blendEVB);
|
||||
s.integer(io.blendEVY);
|
||||
}
|
||||
|
||||
auto PPU::Object::serialize(serializer& s) -> void {
|
||||
s.integer(y);
|
||||
s.integer(affine);
|
||||
s.integer(affineSize);
|
||||
s.integer(mode);
|
||||
s.integer(mosaic);
|
||||
s.integer(colors);
|
||||
s.integer(shape);
|
||||
s.integer(x);
|
||||
s.integer(affineParam);
|
||||
s.integer(hflip);
|
||||
s.integer(vflip);
|
||||
s.integer(size);
|
||||
s.integer(character);
|
||||
s.integer(priority);
|
||||
s.integer(palette);
|
||||
s.integer(width);
|
||||
s.integer(height);
|
||||
}
|
||||
|
||||
auto PPU::ObjectParam::serialize(serializer& s) -> void {
|
||||
s.integer(pa);
|
||||
s.integer(pb);
|
||||
s.integer(pc);
|
||||
s.integer(pd);
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
struct Pixel {
|
||||
bool enable;
|
||||
uint2 priority;
|
||||
uint15 color;
|
||||
|
||||
//objects only
|
||||
bool translucent;
|
||||
bool mosaic;
|
||||
|
||||
alwaysinline auto write(bool e) { enable = e; }
|
||||
alwaysinline auto write(bool e, uint p, uint c) { enable = e; priority = p; color = c; }
|
||||
alwaysinline auto write(bool e, uint p, uint c, bool t, bool m) { enable = e; priority = p; color = c; translucent = t; mosaic = m; }
|
||||
} layer[6][240];
|
||||
|
||||
bool windowmask[3][240];
|
||||
uint vmosaic[5];
|
||||
uint hmosaic[5];
|
||||
|
||||
struct Object {
|
||||
uint8 y;
|
||||
uint1 affine;
|
||||
uint1 affinesize;
|
||||
uint2 mode;
|
||||
uint1 mosaic;
|
||||
uint1 colors; //0 = 16, 1 = 256
|
||||
uint2 shape; //0 = square, 1 = horizontal, 2 = vertical
|
||||
|
||||
uint9 x;
|
||||
uint5 affineparam;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint2 size;
|
||||
|
||||
uint10 character;
|
||||
uint2 priority;
|
||||
uint4 palette;
|
||||
|
||||
//ancillary data
|
||||
uint width;
|
||||
uint height;
|
||||
} object[128];
|
||||
|
||||
struct ObjectParam {
|
||||
int16 pa;
|
||||
int16 pb;
|
||||
int16 pc;
|
||||
int16 pd;
|
||||
} objectparam[32];
|
||||
|
||||
struct Tile {
|
||||
uint10 character;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint4 palette;
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
auto PPU::renderWindow(uint w) -> void {
|
||||
uint y = regs.vcounter;
|
||||
|
||||
uint y1 = regs.window[w].y1, y2 = regs.window[w].y2;
|
||||
uint x1 = regs.window[w].x1, x2 = regs.window[w].x2;
|
||||
|
||||
if(y2 < y1 || y2 > 160) y2 = 160;
|
||||
if(x2 < x1 || x2 > 240) x2 = 240;
|
||||
|
||||
if(y >= y1 && y < y2) {
|
||||
for(uint x = x1; x < x2; x++) {
|
||||
windowmask[w][x] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
auto PPU::Window::run(uint x, uint y) -> void {
|
||||
auto x1 = io.x1, x2 = io.x2;
|
||||
auto y1 = io.y1, y2 = io.y2;
|
||||
|
||||
if(x2 < x1 || x2 > 240) x2 = 240;
|
||||
if(y2 < y1 || y2 > 160) y2 = 160;
|
||||
|
||||
output = (x >= x1 && x < x2 && y >= y1 && y < y2);
|
||||
}
|
||||
|
||||
auto PPU::Window::power(uint id) -> void {
|
||||
this->id = id;
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
|
@ -39,7 +39,7 @@ struct VideoGDI : Video {
|
|||
auto lock(uint32_t*& data, uint& pitch, uint width, uint height) -> bool {
|
||||
if(!settings.buffer || settings.width != width || settings.height != height) {
|
||||
if(settings.buffer) {
|
||||
delete] settings.buffer;
|
||||
delete[] settings.buffer;
|
||||
DeleteObject(device.bitmap);
|
||||
DeleteObject(device.dc);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ struct VideoGDI : Video {
|
|||
|
||||
SetDIBits(device.dc, device.bitmap, 0, settings.height, (void*)settings.buffer, &device.info, DIB_RGB_COLORS);
|
||||
HDC hdc = GetDC(settings.handle);
|
||||
StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, device.dc, 0, -settings.height, settings.width, settings.height, SRCCOPY);
|
||||
StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, device.dc, 0, 0, settings.width, settings.height, SRCCOPY);
|
||||
ReleaseDC(settings.handle, hdc);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue