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:
Tim Allen 2017-06-04 13:16:44 +10:00
parent 1ca4609079
commit 2461293ff0
14 changed files with 1021 additions and 627 deletions

View File

@ -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 {

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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)];
}

View File

@ -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++;
}
}
*/

View File

@ -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));
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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;
};

33
higan/gba/ppu/window.cpp Normal file
View File

@ -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));
}

View File

@ -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);
}