diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 4f5a921b..865a9f37 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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 { diff --git a/higan/gba/ppu/background.cpp b/higan/gba/ppu/background.cpp index 1ee28bc1..b01c51da 100644 --- a/higan/gba/ppu/background.cpp +++ b/higan/gba/ppu/background.cpp @@ -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)); +} diff --git a/higan/gba/ppu/io.cpp b/higan/gba/ppu/io.cpp index 3a2aeeb1..ce7e9dca 100644 --- a/higan/gba/ppu/io.cpp +++ b/higan/gba/ppu/io.cpp @@ -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; } diff --git a/higan/gba/ppu/memory.cpp b/higan/gba/ppu/memory.cpp index e22e8533..5d8c23c9 100644 --- a/higan/gba/ppu/memory.cpp +++ b/higan/gba/ppu/memory.cpp @@ -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)]; +} diff --git a/higan/gba/ppu/mosaic.cpp b/higan/gba/ppu/mosaic.cpp index e30c809b..e6a01302 100644 --- a/higan/gba/ppu/mosaic.cpp +++ b/higan/gba/ppu/mosaic.cpp @@ -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++; } } +*/ diff --git a/higan/gba/ppu/object.cpp b/higan/gba/ppu/object.cpp index 79d4ef75..e7fee8bb 100644 --- a/higan/gba/ppu/object.cpp +++ b/higan/gba/ppu/object.cpp @@ -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)); } diff --git a/higan/gba/ppu/ppu.cpp b/higan/gba/ppu/ppu.cpp index 7b926d0c..9b6a8b43 100644 --- a/higan/gba/ppu/ppu.cpp +++ b/higan/gba/ppu/ppu.cpp @@ -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(); - } 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(); + 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 { + 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(); +} + } diff --git a/higan/gba/ppu/ppu.hpp b/higan/gba/ppu/ppu.hpp index d25a3791..115bcaff 100644 --- a/higan/gba/ppu/ppu.hpp +++ b/higan/gba/ppu/ppu.hpp @@ -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; diff --git a/higan/gba/ppu/registers.hpp b/higan/gba/ppu/registers.hpp deleted file mode 100644 index 3aabf5c2..00000000 --- a/higan/gba/ppu/registers.hpp +++ /dev/null @@ -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; diff --git a/higan/gba/ppu/screen.cpp b/higan/gba/ppu/screen.cpp index e83baa5e..217da669 100644 --- a/higan/gba/ppu/screen.cpp +++ b/higan/gba/ppu/screen.cpp @@ -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)); } diff --git a/higan/gba/ppu/serialization.cpp b/higan/gba/ppu/serialization.cpp index 0155b612..955e6c72 100644 --- a/higan/gba/ppu/serialization.cpp +++ b/higan/gba/ppu/serialization.cpp @@ -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); } diff --git a/higan/gba/ppu/state.hpp b/higan/gba/ppu/state.hpp deleted file mode 100644 index 856db3b3..00000000 --- a/higan/gba/ppu/state.hpp +++ /dev/null @@ -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; -}; diff --git a/higan/gba/ppu/window.cpp b/higan/gba/ppu/window.cpp new file mode 100644 index 00000000..b4a00071 --- /dev/null +++ b/higan/gba/ppu/window.cpp @@ -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)); +} diff --git a/ruby/video/gdi.cpp b/ruby/video/gdi.cpp index dfee776c..530f49cd 100644 --- a/ruby/video/gdi.cpp +++ b/ruby/video/gdi.cpp @@ -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); }