diff --git a/bsnes-sppu.exe b/bsnes-sppu.exe new file mode 100644 index 00000000..834deacd Binary files /dev/null and b/bsnes-sppu.exe differ diff --git a/bsnes.exe b/bsnes.exe deleted file mode 100644 index 65a78ab8..00000000 Binary files a/bsnes.exe and /dev/null differ diff --git a/src/Makefile b/src/Makefile index 78a0b790..9062cd34 100644 --- a/src/Makefile +++ b/src/Makefile @@ -13,7 +13,7 @@ objects := # link += -lgcov # profile-guided optimization -# flags += -fprofile-use +flags += -fprofile-use # platform ifeq ($(platform),x) diff --git a/src/snes/ppu/sppu/background/background.cpp b/src/snes/ppu/sppu/background/background.cpp index 57aa7794..4124eeea 100644 --- a/src/snes/ppu/sppu/background/background.cpp +++ b/src/snes/ppu/sppu/background/background.cpp @@ -1,5 +1,7 @@ #ifdef SPPU_CPP +#include "mode7.cpp" + void sPPU::Background::scanline() { if(self.vcounter() == 1) { regs.mosaic_y = 1; @@ -9,20 +11,26 @@ void sPPU::Background::scanline() { if(!regs.mosaic_countdown) regs.mosaic_countdown = regs.mosaic + 1; regs.mosaic_countdown--; } + + state.x = 0; } void sPPU::Background::run() { - output.main.valid = false; - output.sub.valid = false; + bool hires = (self.regs.bgmode == 5 || self.regs.bgmode == 6); + + if((self.hcounter() & 2) == 0) { + output.main.valid = false; + output.sub.valid = false; + } else if(hires == false) { + return; + } + if(regs.mode == Mode::Inactive) return; if(regs.main_enabled == false && regs.sub_enabled == false) return; - if(self.hcounter() < 88) return; - if(self.hcounter() >= 1112) return; - if(self.hcounter() & 2) return; - - unsigned x = (self.hcounter() - 88) >> 2; + unsigned x = state.x++; unsigned y = regs.mosaic_y; + if(regs.mode == Mode::Mode7) return run_mode7(x, y); unsigned color_depth = (regs.mode == Mode::BPP2 ? 0 : regs.mode == Mode::BPP4 ? 1 : 2); unsigned palette_offset = (self.regs.bgmode == 0 ? (id << 5) : 0); @@ -30,12 +38,10 @@ void sPPU::Background::run() { unsigned tile_mask = 0x0fff >> color_depth; unsigned tiledata_index = regs.tiledata_addr >> (4 + color_depth); - bool hires = (self.regs.bgmode == 5 || self.regs.bgmode == 6); - unsigned width = (!hires ? 256 : 512); - unsigned tile_height = (regs.tile_size == TileSize::Size8x8 ? 3 : 4); unsigned tile_width = (!hires ? tile_height : 4); + unsigned width = (!hires ? 256 : 512); unsigned mask_x = (tile_height == 3 ? width : (width << 1)); unsigned mask_y = mask_x; if(regs.screen_size & 1) mask_x <<= 1; @@ -51,11 +57,68 @@ void sPPU::Background::run() { unsigned vscroll = regs.voffset; if(hires) { hscroll <<= 1; + if(self.regs.interlace) y = (y << 1) + self.field(); } unsigned hoffset = hscroll + mosaic_table[regs.mosaic][x]; unsigned voffset = vscroll + y; + if(self.regs.bgmode == 2 || self.regs.bgmode == 4 || self.regs.bgmode == 6) { + uint16 opt_x = (x + (hscroll & 7)); + + //tile 0 is unaffected by offset-per-tile mode + if(opt_x >= 8) { + uint16 hval; { + unsigned px = (opt_x - 8) + (self.bg3.regs.hoffset & ~7); + unsigned py = self.bg3.regs.voffset; + + unsigned tx = (px & mask_x) >> tile_width; + unsigned ty = (py & mask_y) >> tile_height; + + uint16 pos = ((ty & 0x1f) << 5) + (tx & 0x1f); + if(ty & 0x20) pos += screen_y; + if(tx & 0x20) pos += screen_x; + + uint16 addr = self.bg3.regs.screen_addr + (pos << 1); + hval = memory::vram[addr + 0] + (memory::vram[addr + 1] << 8); + } + + uint16 vval; if(self.regs.bgmode != 4) { + unsigned px = (opt_x - 8) + (self.bg3.regs.hoffset & ~7); + unsigned py = self.bg3.regs.voffset + 8; + + unsigned tx = (px & mask_x) >> tile_width; + unsigned ty = (py & mask_y) >> tile_height; + + uint16 pos = ((ty & 0x1f) << 5) + (tx & 0x1f); + if(ty & 0x20) pos += screen_y; + if(tx & 0x20) pos += screen_x; + + uint16 addr = self.bg3.regs.screen_addr + (pos << 1); + vval = memory::vram[addr + 0] + (memory::vram[addr + 1] << 8); + } + + unsigned opt_valid_bit = (id == ID::BG1 ? 0x2000 : id == ID::BG2 ? 0x4000 : 0x0000); + + if(self.regs.bgmode == 4) { + if(hval & opt_valid_bit) { + if(!(hval & 0x8000)) { + hoffset = opt_x + (hval & ~7); + } else { + voffset = y + hval; + } + } + } else { + if(hval & opt_valid_bit) { + hoffset = opt_x + (hval & ~7); + } + if(vval & opt_valid_bit) { + voffset = y + vval; + } + } + } + } + hoffset &= mask_x; voffset &= mask_y; @@ -89,19 +152,38 @@ void sPPU::Background::run() { unsigned color = get_color(hoffset, voffset, tile_number); if(color == 0) return; - unsigned palette_addr = (palette_index + color) << 1; - unsigned output_color = memory::cgram[palette_addr + 0] + (memory::cgram[palette_addr + 1] << 8); + color += palette_index; - if(regs.main_enabled) { - output.main.valid = true; - output.main.color = output_color; - output.main.priority = priority; - } + if(hires == false) { + if(regs.main_enabled) { + output.main.valid = true; + output.main.palette = color; + output.main.palette_number = palette_number; + output.main.priority = priority; + } - if(regs.sub_enabled) { - output.sub.valid = true; - output.sub.color = output_color; - output.sub.priority = priority; + if(regs.sub_enabled) { + output.sub.valid = true; + output.sub.palette = color; + output.sub.palette_number = palette_number; + output.sub.priority = priority; + } + } else { + if(x & 1) { + if(regs.main_enabled) { + output.main.valid = true; + output.main.palette = color; + output.main.palette_number = palette_number; + output.main.priority = priority; + } + } else { + if(regs.sub_enabled) { + output.sub.valid = true; + output.sub.palette = color; + output.sub.palette_number = palette_number; + output.sub.priority = priority; + } + } } } @@ -157,6 +239,32 @@ unsigned sPPU::Background::get_color(unsigned x, unsigned y, uint16 offset) { }; } +void sPPU::Background::reset() { + state.x = 0; + regs.tiledata_addr = 0; + regs.screen_addr = 0; + regs.screen_size = 0; + regs.mosaic = 0; + regs.mosaic_y = 0; + regs.mosaic_countdown = 0; + regs.tile_size = 0; + regs.mode = 0; + regs.priority0 = 0; + regs.priority1 = 0; + regs.main_enabled = 0; + regs.sub_enabled = 0; + regs.hoffset = 0; + regs.voffset = 0; + output.main.valid = 0; + output.main.palette = 0; + output.main.palette_number = 0; + output.main.priority = 0; + output.sub.valid = 0; + output.sub.palette = 0; + output.sub.palette_number = 0; + output.sub.priority = 0; +} + sPPU::Background::Background(sPPU &self, unsigned id) : self(self), id(id) { //generate mosaic table for(unsigned m = 0; m < 16; m++) { diff --git a/src/snes/ppu/sppu/background/background.hpp b/src/snes/ppu/sppu/background/background.hpp index 0f5c7f96..91121e0f 100644 --- a/src/snes/ppu/sppu/background/background.hpp +++ b/src/snes/ppu/sppu/background/background.hpp @@ -8,6 +8,10 @@ public: struct ScreenSize { enum { Size32x32, Size32x64, Size64x32, Size64x64 }; }; struct TileSize { enum { Size8x8, Size16x16 }; }; + struct { + unsigned x; + } state; + struct { unsigned tiledata_addr; unsigned screen_addr; @@ -31,17 +35,22 @@ public: struct { struct { bool valid; - uint16 color; - unsigned priority; + uint8 palette; + uint8 palette_number; + uint8 priority; } main, sub; } output; void scanline(); void run(); unsigned get_color(unsigned x, unsigned y, uint16 offset); - + void reset(); Background(sPPU &self, unsigned id); private: static uint16 mosaic_table[16][4096]; + + //mode7.cpp + signed clip(signed n); + void run_mode7(unsigned x, unsigned y); }; diff --git a/src/snes/ppu/sppu/background/mode7.cpp b/src/snes/ppu/sppu/background/mode7.cpp new file mode 100644 index 00000000..9337eea4 --- /dev/null +++ b/src/snes/ppu/sppu/background/mode7.cpp @@ -0,0 +1,104 @@ +#ifdef SPPU_CPP + +signed sPPU::Background::clip(signed n) { + //13-bit sign extend: --s---nnnnnnnnnn -> ssssssnnnnnnnnnn + return n & 0x2000 ? (n | ~1023) : (n & 1023); +} + +void sPPU::Background::run_mode7(unsigned x, unsigned y) { + signed a = sclip<16>(self.regs.m7a); + signed b = sclip<16>(self.regs.m7b); + signed c = sclip<16>(self.regs.m7c); + signed d = sclip<16>(self.regs.m7d); + + signed cx = sclip<13>(self.regs.m7x); + signed cy = sclip<13>(self.regs.m7y); + signed hofs = sclip<13>(self.regs.m7hofs); + signed vofs = sclip<13>(self.regs.m7vofs); + + if(self.regs.mode7_hflip) x = 255 - x; + if(self.regs.mode7_vflip) y = 255 - y; + + unsigned mosaic_x; + unsigned mosaic_y; + if(id == ID::BG1) { + mosaic_x = mosaic_table[self.bg1.regs.mosaic][x]; + mosaic_y = mosaic_table[self.bg1.regs.mosaic][y]; + } else if(id == ID::BG2) { + mosaic_x = mosaic_table[self.bg2.regs.mosaic][x]; + mosaic_y = mosaic_table[self.bg1.regs.mosaic][y]; //BG2 vertical mosaic uses BG1 mosaic size + } + + signed psx = ((a * clip(hofs - cx)) & ~63) + ((b * clip(vofs - cy)) & ~63) + ((b * mosaic_y) & ~63) + (cx << 8); + signed psy = ((c * clip(hofs - cx)) & ~63) + ((d * clip(vofs - cy)) & ~63) + ((d * mosaic_y) & ~63) + (cy << 8); + + signed px = psx + (a * mosaic_x); + signed py = psy + (c * mosaic_x); + + //mask pseudo-FP bits + px >>= 8; + py >>= 8; + + unsigned tile; + unsigned palette; + switch(self.regs.mode7_repeat) { + //screen repetition outside of screen area + case 0: + case 1: { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } break; + + //palette color 0 outside of screen area + case 2: { + if(px < 0 || px > 1023 || py < 0 || py > 1023) { + palette = 0; + } else { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } + } break; + + //character 0 repetition outside of screen area + case 3: { + if(px < 0 || px > 1023 || py < 0 || py > 1023) { + tile = 0; + } else { + px &= 1023; + py &= 1023; + tile = memory::vram[((py >> 3) * 128 + (px >> 3)) << 1]; + } + palette = memory::vram[(((tile << 6) + ((py & 7) << 3) + (px & 7)) << 1) + 1]; + } break; + } + + unsigned priority; + if(id == ID::BG1) { + priority = regs.priority0; + } else if(id == ID::BG2) { + priority = (palette & 0x80 ? regs.priority1 : regs.priority0); + palette &= 0x7f; + } + + if(palette == 0) return; + + if(regs.main_enabled) { + output.main.valid = true; + output.main.palette = palette; + output.main.palette_number = 0; + output.main.priority = priority; + } + + if(regs.sub_enabled) { + output.sub.valid = true; + output.sub.palette = palette; + output.sub.palette_number = 0; + output.sub.priority = priority; + } +} + +#endif diff --git a/src/snes/ppu/sppu/debugger/debugger.cpp b/src/snes/ppu/sppu/debugger/debugger.cpp new file mode 100644 index 00000000..c31bc18e --- /dev/null +++ b/src/snes/ppu/sppu/debugger/debugger.cpp @@ -0,0 +1,6 @@ +#ifdef SPPU_CPP + +bool sPPUDebugger::interlace() { return false; } +bool sPPUDebugger::overscan() { return false; } + +#endif diff --git a/src/snes/ppu/sppu/debugger/debugger.hpp b/src/snes/ppu/sppu/debugger/debugger.hpp new file mode 100644 index 00000000..a0df354e --- /dev/null +++ b/src/snes/ppu/sppu/debugger/debugger.hpp @@ -0,0 +1,11 @@ +class sPPUDebugger : public sPPU, public PPUDebugger { +public: + bool bg1_enabled[2]; + bool bg2_enabled[2]; + bool bg3_enabled[2]; + bool bg4_enabled[2]; + bool oam_enabled[4]; + + bool interlace(); + bool overscan(); +}; diff --git a/src/snes/ppu/sppu/mmio/mmio.cpp b/src/snes/ppu/sppu/mmio/mmio.cpp index 841f5d9e..e5e635bb 100644 --- a/src/snes/ppu/sppu/mmio/mmio.cpp +++ b/src/snes/ppu/sppu/mmio/mmio.cpp @@ -18,20 +18,27 @@ uint16 sPPU::get_vram_address() { } uint8 sPPU::vram_read(unsigned addr) { - return memory::vram[addr]; + if(regs.display_disabled || vcounter() >= (!regs.overscan ? 225 : 240)) { + return memory::vram[addr]; + } + return 0x00; } void sPPU::vram_write(unsigned addr, uint8 data) { - memory::vram[addr] = data; + if(regs.display_disabled || vcounter() >= (!regs.overscan ? 225 : 240)) { + memory::vram[addr] = data; + } } uint8 sPPU::oam_read(unsigned addr) { if(addr & 0x0200) addr &= 0x021f; + if(!regs.display_disabled && vcounter() < (!regs.overscan ? 225 : 240)) addr = 0x0218; return memory::oam[addr]; } void sPPU::oam_write(unsigned addr, uint8 data) { if(addr & 0x0200) addr &= 0x021f; + if(!regs.display_disabled && vcounter() < (!regs.overscan ? 225 : 240)) addr = 0x0218; memory::oam[addr] = data; } @@ -44,11 +51,11 @@ void sPPU::cgram_write(unsigned addr, uint8 data) { } bool sPPU::interlace() const { - return false; + return display.interlace; } bool sPPU::overscan() const { - return false; + return regs.overscan; } bool sPPU::hires() const { @@ -185,6 +192,22 @@ void sPPU::mmio_w2105(uint8 data) { } break; case 7: { + if(regs.mode7_extbg == false) { + bg1.regs.mode = Background::Mode::Mode7; + bg2.regs.mode = Background::Mode::Inactive; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 2; bg1.regs.priority1 = 2; + oam.regs.priority0 = 1; oam.regs.priority1 = 3; oam.regs.priority2 = 4; oam.regs.priority3 = 5; + } else { + bg1.regs.mode = Background::Mode::Mode7; + bg2.regs.mode = Background::Mode::Mode7; + bg3.regs.mode = Background::Mode::Inactive; + bg4.regs.mode = Background::Mode::Inactive; + bg1.regs.priority0 = 3; bg1.regs.priority1 = 3; + bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 7; + } } break; } } @@ -236,12 +259,18 @@ void sPPU::mmio_w210c(uint8 data) { //BG1HOFS void sPPU::mmio_w210d(uint8 data) { + regs.m7hofs = (data << 8) | regs.m7_latchdata; + regs.m7_latchdata = data; + bg1.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg1.regs.hoffset >> 8) & 7); regs.bgofs_latchdata = data; } //BG1VOFS void sPPU::mmio_w210e(uint8 data) { + regs.m7vofs = (data << 8) | regs.m7_latchdata; + regs.m7_latchdata = data; + bg1.regs.voffset = (data << 8) | regs.bgofs_latchdata; regs.bgofs_latchdata = data; } @@ -326,7 +355,11 @@ void sPPU::mmio_w2119(uint8 data) { if(regs.vram_incmode == 1) regs.vram_addr += regs.vram_incsize; } +//M7SEL void sPPU::mmio_w211a(uint8 data) { + regs.mode7_repeat = (data >> 6) & 3; + regs.mode7_vflip = data & 0x02; + regs.mode7_hflip = data & 0x01; } //M7A @@ -517,7 +550,11 @@ void sPPU::mmio_w2132(uint8 data) { //SETINI void sPPU::mmio_w2133(uint8 data) { + regs.mode7_extbg = data & 0x40; + regs.pseudo_hires = data & 0x08; + regs.overscan = data & 0x04; oam.regs.interlace = data & 0x02; + regs.interlace = data & 0x01; } //MPYL @@ -671,6 +708,12 @@ void sPPU::mmio_reset() { regs.bg3_priority = false; regs.bgmode = 0; + //$210d BG1HOFS + regs.m7hofs = 0x0000; + + //$210e BG1VOFS + regs.m7vofs = 0x0000; + //$2115 VMAIN regs.vram_incmode = 1; regs.vram_mapping = 0; @@ -680,6 +723,11 @@ void sPPU::mmio_reset() { //$2117 VMADDH regs.vram_addr = 0x0000; + //$211a M7SEL + regs.mode7_repeat = 0; + regs.mode7_vflip = false; + regs.mode7_hflip = false; + //$211b M7A regs.m7a = 0x0000; @@ -701,6 +749,12 @@ void sPPU::mmio_reset() { //$2121 CGADD regs.cgram_addr = 0x0000; + //$2133 SETINI + regs.mode7_extbg = false; + regs.pseudo_hires = false; + regs.overscan = false; + regs.interlace = false; + //$213c OPHCT regs.hcounter = 0; diff --git a/src/snes/ppu/sppu/mmio/mmio.hpp b/src/snes/ppu/sppu/mmio/mmio.hpp index 27c667e1..968b52c4 100644 --- a/src/snes/ppu/sppu/mmio/mmio.hpp +++ b/src/snes/ppu/sppu/mmio/mmio.hpp @@ -25,6 +25,12 @@ struct { bool bg3_priority; uint8 bgmode; + //$210d BG1HOFS + uint16 m7hofs; + + //$210e BG1VOFS + uint16 m7vofs; + //$2115 VMAIN bool vram_incmode; uint8 vram_mapping; @@ -34,6 +40,11 @@ struct { //$2117 VMADDH uint16 vram_addr; + //$211a M7SEL + uint8 mode7_repeat; + bool mode7_vflip; + bool mode7_hflip; + //$211b M7A uint16 m7a; @@ -55,6 +66,12 @@ struct { //$2121 CGADD uint16 cgram_addr; + //$2133 SETINI + bool mode7_extbg; + bool pseudo_hires; + bool overscan; + bool interlace; + //$213c OPHCT uint16 hcounter; diff --git a/src/snes/ppu/sppu/screen/screen.cpp b/src/snes/ppu/sppu/screen/screen.cpp index 5d9f4183..c1f0443d 100644 --- a/src/snes/ppu/sppu/screen/screen.cpp +++ b/src/snes/ppu/sppu/screen/screen.cpp @@ -2,109 +2,156 @@ void sPPU::Screen::scanline() { output = self.output + self.vcounter() * 1024; + if(self.display.interlace && self.field() == 1) output += 512; } void sPPU::Screen::run() { + uint16 color; + if(self.regs.pseudo_hires == false && self.regs.bgmode != 5 && self.regs.bgmode != 6) { + color = get_pixel(false); + *output++ = color; + *output++ = color; + } else { + color = get_pixel(true); + *output++ = color; + color = get_pixel(false); + *output++ = color; + } +} + +uint16 sPPU::Screen::get_pixel(bool swap) { enum source_t { BG1, BG2, BG3, BG4, OAM, BACK }; + bool color_enable[] = { regs.bg1_color_enable, regs.bg2_color_enable, regs.bg3_color_enable, regs.bg4_color_enable, regs.oam_color_enable, regs.back_color_enable }; + + //=========== + //main screen + //=========== + + unsigned priority_main = 0; + unsigned color_main; + unsigned source_main; + + if(self.bg1.output.main.valid) { + priority_main = self.bg1.output.main.priority; + if(regs.direct_color && (self.regs.bgmode == 3 || self.regs.bgmode == 4 || self.regs.bgmode == 7)) { + color_main = get_direct_color(self.bg1.output.main.palette, self.bg1.output.main.palette_number); + } else { + color_main = get_color(self.bg1.output.main.palette); + } + source_main = BG1; + } + if(self.bg2.output.main.valid && self.bg2.output.main.priority > priority_main) { + priority_main = self.bg2.output.main.priority; + color_main = get_color(self.bg2.output.main.palette); + source_main = BG2; + } + if(self.bg3.output.main.valid && self.bg3.output.main.priority > priority_main) { + priority_main = self.bg3.output.main.priority; + color_main = get_color(self.bg3.output.main.palette); + source_main = BG3; + } + if(self.bg4.output.main.valid && self.bg4.output.main.priority > priority_main) { + priority_main = self.bg4.output.main.priority; + color_main = get_color(self.bg4.output.main.palette); + source_main = BG4; + } + if(self.oam.output.main.valid && self.oam.output.main.priority > priority_main) { + priority_main = self.oam.output.main.priority; + color_main = get_color(self.oam.output.main.palette); + source_main = OAM; + } + if(priority_main == 0) { + color_main = get_color(0); + source_main = BACK; + } + + //========== + //sub screen + //========== unsigned priority_sub = 0; - uint16 color_sub; - source_t source_sub; + unsigned color_sub; + unsigned source_sub; if(self.bg1.output.sub.valid) { priority_sub = self.bg1.output.sub.priority; - color_sub = self.bg1.output.sub.color; + if(regs.direct_color && (self.regs.bgmode == 3 || self.regs.bgmode == 4 || self.regs.bgmode == 7)) { + color_sub = get_direct_color(self.bg1.output.sub.palette, self.bg1.output.sub.palette_number); + } else { + color_sub = get_color(self.bg1.output.sub.palette); + } source_sub = BG1; } if(self.bg2.output.sub.valid && self.bg2.output.sub.priority > priority_sub) { priority_sub = self.bg2.output.sub.priority; - color_sub = self.bg2.output.sub.color; + color_sub = get_color(self.bg2.output.sub.palette); source_sub = BG2; } if(self.bg3.output.sub.valid && self.bg3.output.sub.priority > priority_sub) { priority_sub = self.bg3.output.sub.priority; - color_sub = self.bg3.output.sub.color; + color_sub = get_color(self.bg3.output.sub.palette); source_sub = BG3; } if(self.bg4.output.sub.valid && self.bg4.output.sub.priority > priority_sub) { priority_sub = self.bg4.output.sub.priority; - color_sub = self.bg4.output.sub.color; + color_sub = get_color(self.bg4.output.sub.palette); source_sub = BG4; } if(self.oam.output.sub.valid && self.oam.output.sub.priority > priority_sub) { priority_sub = self.oam.output.sub.priority; - color_sub = self.oam.output.sub.color; + color_sub = get_color(self.oam.output.sub.palette); source_sub = OAM; } if(priority_sub == 0) { if(self.regs.bgmode == 5 || self.regs.bgmode == 6) { - color_sub = memory::cgram[0] + (memory::cgram[1] << 8); + color_sub = get_color(0); } else { color_sub = regs.color_rgb; } source_sub = BACK; } - unsigned priority_main = 0; - uint16 color_main; - source_t source_main; + if(swap == true) { + nall::swap(priority_main, priority_sub); + nall::swap(color_main, color_sub); + nall::swap(source_main, source_sub); + } - if(self.bg1.output.main.valid) { - priority_main = self.bg1.output.main.priority; - color_main = self.bg1.output.main.color; - source_main = BG1; - if(regs.bg1_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub); - } - if(self.bg2.output.main.valid && self.bg2.output.main.priority > priority_main) { - priority_main = self.bg2.output.main.priority; - color_main = self.bg2.output.main.color; - source_main = BG2; - if(regs.bg2_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub); - } - if(self.bg3.output.main.valid && self.bg3.output.main.priority > priority_main) { - priority_main = self.bg3.output.main.priority; - color_main = self.bg3.output.main.color; - source_main = BG3; - if(regs.bg3_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub); - } - if(self.bg4.output.main.valid && self.bg4.output.main.priority > priority_main) { - priority_main = self.bg4.output.main.priority; - color_main = self.bg4.output.main.color; - source_main = BG4; - if(regs.bg4_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub); - } - if(self.oam.output.main.valid && self.oam.output.main.priority > priority_main) { - priority_main = self.oam.output.main.priority; - color_main = self.oam.output.main.color; - source_main = OAM; - if(self.oam.output.main.palette >= 192) { - if(regs.oam_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub); - } - } - if(priority_main == 0) { - color_main = memory::cgram[0] + (memory::cgram[1] << 8); - source_main = BACK; - if(regs.back_color_enable && self.window.output.sub.color_enable) color_main = addsub(color_main, color_sub, !regs.addsub_mode || source_sub != BACK); + uint16 output; + if(!regs.addsub_mode) { + source_sub = BACK; + color_sub = regs.color_rgb; } if(self.window.output.main.color_enable == false) { if(self.window.output.sub.color_enable == false) { - color_main = 0x0000; - goto plot; + return 0x0000; } color_main = 0x0000; } - color_main = light_table[self.regs.display_brightness][color_main]; - if(self.regs.display_disabled) color_main = 0; - plot: - *output++ = color_main; - *output++ = color_main; + bool color_exempt = (source_main == OAM && self.oam.output.main.palette < 192); + if(!color_exempt && color_enable[source_main] && self.window.output.sub.color_enable) { + bool halve = false; + if(regs.color_halve && self.window.output.main.color_enable) { + if(regs.addsub_mode && source_sub == BACK); + else halve = true; + } + output = addsub(color_main, color_sub, halve); + } else { + output = color_main; + } + + //======== + //lighting + //======== + + output = light_table[self.regs.display_brightness][output]; + if(self.regs.display_disabled) output = 0x0000; + return output; } -uint16 sPPU::Screen::addsub(unsigned x, unsigned y, bool allow_halve) { - bool halve = allow_halve && regs.color_halve && self.window.output.main.color_enable; - +uint16 sPPU::Screen::addsub(unsigned x, unsigned y, bool halve) { if(!regs.color_mode) { if(!halve) { unsigned sum = x + y; @@ -124,6 +171,36 @@ uint16 sPPU::Screen::addsub(unsigned x, unsigned y, bool allow_halve) { } } +uint16 sPPU::Screen::get_color(uint8 n) { + return memory::cgram[(n << 1) + 0] + (memory::cgram[(n << 1) + 1] << 8); +} + +//color = BBBGGGRR (from tiledata) +//palette = 00000bgr (from tilemap) +//result = 0BBb00GGGg0RRRr0 +uint16 sPPU::Screen::get_direct_color(uint8 color, uint8 palette) { + return ((color & 7) << 2) | ((palette & 1) << 1) + | (((color >> 3) & 7) << 7) | (((palette >> 1) & 1) << 6) + | ((color >> 6) << 13) | ((palette >> 2) << 12); +} + +void sPPU::Screen::reset() { + regs.addsub_mode = 0; + regs.direct_color = 0; + regs.color_mode = 0; + regs.color_halve = 0; + regs.bg1_color_enable = 0; + regs.bg2_color_enable = 0; + regs.bg3_color_enable = 0; + regs.bg4_color_enable = 0; + regs.oam_color_enable = 0; + regs.back_color_enable = 0; + regs.color_r = 0; + regs.color_g = 0; + regs.color_b = 0; + regs.color_rgb = 0; +} + sPPU::Screen::Screen(sPPU &self) : self(self) { //generate light table for(unsigned l = 0; l < 16; l++) { diff --git a/src/snes/ppu/sppu/screen/screen.hpp b/src/snes/ppu/sppu/screen/screen.hpp index 0335d2de..89cbdd90 100644 --- a/src/snes/ppu/sppu/screen/screen.hpp +++ b/src/snes/ppu/sppu/screen/screen.hpp @@ -24,10 +24,14 @@ public: void scanline(); void run(); + void reset(); Screen(sPPU &self); private: uint16 light_table[16][32768]; - uint16 addsub(unsigned x, unsigned y, bool allow_halve = true); + uint16 get_pixel(bool swap); + uint16 addsub(unsigned x, unsigned y, bool halve); + uint16 get_color(uint8 n); + uint16 get_direct_color(uint8 color, uint8 palette); }; diff --git a/src/snes/ppu/sppu/sppu.cpp b/src/snes/ppu/sppu/sppu.cpp index 6f022517..a4cf2f97 100644 --- a/src/snes/ppu/sppu/sppu.cpp +++ b/src/snes/ppu/sppu/sppu.cpp @@ -9,10 +9,11 @@ namespace SNES { #include "sprite/sprite.cpp" #include "window/window.cpp" -#if !defined(DEBUGGER) - sPPU ppu; -#else +#if defined(DEBUGGER) + #include "debugger/debugger.cpp" sPPUDebugger ppu; +#else + sPPU ppu; #endif void sPPU::enter() { @@ -21,8 +22,14 @@ void sPPU::enter() { add_clocks(88); - if(vcounter() >= 1 && vcounter() <= 224) { + if(vcounter() >= 1 && vcounter() <= (!regs.overscan ? 224 : 239)) { for(unsigned n = 0; n < 256; n++) { + bg1.run(); + bg2.run(); + bg3.run(); + bg4.run(); + add_clocks(2); + bg1.run(); bg2.run(); bg3.run(); @@ -30,7 +37,7 @@ void sPPU::enter() { oam.run(); window.run(); screen.run(); - add_clocks(4); + add_clocks(2); } } else { add_clocks(1024); @@ -51,6 +58,10 @@ void sPPU::add_clocks(unsigned clocks) { void sPPU::power() { PPU::power(); + memset(memory::vram.data(), 0x00, memory::vram.size()); + memset(memory::oam.data(), 0x00, memory::oam.size()); + memset(memory::cgram.data(), 0x00, memory::cgram.size()); + reset(); } @@ -60,6 +71,13 @@ void sPPU::reset() { memset(output, 0x00, 512 * 480 * sizeof(uint16)); mmio_reset(); + bg1.reset(); + bg2.reset(); + bg3.reset(); + bg4.reset(); + oam.reset(); + window.reset(); + screen.reset(); } void sPPU::scanline() { @@ -77,6 +95,8 @@ void sPPU::frame() { PPU::frame(); system.frame(); oam.frame(); + + display.interlace = regs.interlace; } sPPU::sPPU() : diff --git a/src/snes/ppu/sppu/sppu.hpp b/src/snes/ppu/sppu/sppu.hpp index cef1379f..f1dc2284 100644 --- a/src/snes/ppu/sppu/sppu.hpp +++ b/src/snes/ppu/sppu/sppu.hpp @@ -14,6 +14,10 @@ public: Window window; Screen screen; + struct { + bool interlace; + } display; + void enter(); void add_clocks(unsigned); @@ -26,19 +30,9 @@ public: sPPU(); }; -#if !defined(DEBUGGER) - extern sPPU ppu; +#if defined(DEBUGGER) + #include "debugger/debugger.hpp" + extern sPPUDebugger ppu; #else -class sPPUDebugger : public sPPU, public PPUDebugger { -public: - bool bg1_enabled[2]; - bool bg2_enabled[2]; - bool bg3_enabled[2]; - bool bg4_enabled[2]; - bool oam_enabled[4]; - - inline bool interlace() { return false; } - inline bool overscan() { return false; } -}; -extern sPPUDebugger ppu; + extern sPPU ppu; #endif diff --git a/src/snes/ppu/sppu/sprite/sprite.cpp b/src/snes/ppu/sppu/sprite/sprite.cpp index aeeca8c6..5a0c2c37 100644 --- a/src/snes/ppu/sppu/sprite/sprite.cpp +++ b/src/snes/ppu/sppu/sprite/sprite.cpp @@ -45,9 +45,11 @@ void sPPU::Sprite::scanline() { break; case 6: list[i].width = (!size ? 16 : 32); list[i].height = (!size ? 32 : 64); + if(regs.interlace && !size) list[i].height = 16; break; case 7: list[i].width = (!size ? 16 : 32); list[i].height = (!size ? 32 : 32); + if(regs.interlace && !size) list[i].height = 16; break; } @@ -100,21 +102,17 @@ void sPPU::Sprite::run() { if(state.output_priority[x] != 0xff) { unsigned priority_table[] = { regs.priority0, regs.priority1, regs.priority2, regs.priority3 }; - unsigned priority = priority_table[state.output_priority[x]]; - unsigned palette = state.output_palette[x] << 1; if(regs.main_enabled) { output.main.valid = true; - output.main.color = memory::cgram[palette + 0] + (memory::cgram[palette + 1] << 8); output.main.palette = state.output_palette[x]; - output.main.priority = priority; + output.main.priority = priority_table[state.output_priority[x]]; } if(regs.sub_enabled) { output.sub.valid = true; - output.sub.color = memory::cgram[palette + 0] + (memory::cgram[palette + 1] << 8); output.sub.palette = state.output_palette[x]; - output.sub.priority = priority; + output.sub.priority = priority_table[state.output_priority[x]]; } } } @@ -134,6 +132,7 @@ void sPPU::Sprite::load_tiles() { unsigned tile_width = sprite.width >> 3; signed x = sprite.x; signed y = (state.y - sprite.y) & 0xff; + if(regs.interlace) y <<= 1; if(sprite.vflip) { if(sprite.width == sprite.height) { @@ -210,6 +209,56 @@ void sPPU::Sprite::render_tile(unsigned tile) { } } +void sPPU::Sprite::reset() { + regs.main_enabled = 0; + regs.sub_enabled = 0; + regs.interlace = 0; + regs.base_size = 0; + regs.nameselect = 0; + regs.tiledata_addr = 0; + regs.first_sprite = 0; + regs.priority0 = 0; + regs.priority1 = 0; + regs.priority2 = 0; + regs.priority3 = 0; + regs.time_over = 0; + regs.range_over = 0; + for(unsigned i = 0; i < 128; i++) { + list[i].width = 0; + list[i].height = 0; + list[i].x = 0; + list[i].y = 0; + list[i].character = 0; + list[i].nameselect = 0; + list[i].vflip = 0; + list[i].hflip = 0; + list[i].palette = 0; + list[i].priority = 0; + } + state.x = 0; + state.y = 0; + state.item_count = 0; + state.tile_count = 0; + memset(state.output_palette, 0, 256); + memset(state.output_priority, 0, 256); + memset(state.item_list, 0, 32); + for(unsigned i = 0; i < 34; i++) { + state.tile_list[i].x = 0; + state.tile_list[i].y = 0; + state.tile_list[i].priority = 0; + state.tile_list[i].palette = 0; + state.tile_list[i].tile = 0; + state.tile_list[i].hflip = 0; + } + state.active_sprite = 0; + output.main.valid = 0; + output.main.palette = 0; + output.main.priority = 0; + output.sub.valid = 0; + output.sub.palette = 0; + output.sub.priority = 0; +} + sPPU::Sprite::Sprite(sPPU &self) : self(self) { } diff --git a/src/snes/ppu/sppu/sprite/sprite.hpp b/src/snes/ppu/sppu/sprite/sprite.hpp index 576136f5..d8d6b35c 100644 --- a/src/snes/ppu/sppu/sprite/sprite.hpp +++ b/src/snes/ppu/sppu/sprite/sprite.hpp @@ -59,9 +59,8 @@ public: struct { struct { bool valid; - uint16 color; - unsigned palette; - unsigned priority; + uint8 palette; + uint8 priority; } main, sub; } output; @@ -69,6 +68,7 @@ public: void frame(); void scanline(); void run(); + void reset(); Sprite(sPPU &self); diff --git a/src/snes/ppu/sppu/window/window.cpp b/src/snes/ppu/sppu/window/window.cpp index 45739015..a0e47829 100644 --- a/src/snes/ppu/sppu/window/window.cpp +++ b/src/snes/ppu/sppu/window/window.cpp @@ -109,6 +109,58 @@ void sPPU::Window::test( sub = sub_enable ? output : false; } +void sPPU::Window::reset() { + regs.bg1_one_enable = false; + regs.bg1_one_invert = false; + regs.bg1_two_enable = false; + regs.bg1_two_invert = false; + regs.bg2_one_enable = false; + regs.bg2_one_invert = false; + regs.bg2_two_enable = false; + regs.bg2_two_invert = false; + regs.bg3_one_enable = false; + regs.bg3_one_invert = false; + regs.bg3_two_enable = false; + regs.bg3_two_invert = false; + regs.bg4_one_enable = false; + regs.bg4_one_invert = false; + regs.bg4_two_enable = false; + regs.bg4_two_invert = false; + regs.oam_one_enable = false; + regs.oam_one_invert = false; + regs.oam_two_enable = false; + regs.oam_two_invert = false; + regs.col_one_enable = false; + regs.col_one_invert = false; + regs.col_two_enable = false; + regs.col_two_invert = false; + regs.one_left = 0; + regs.one_right = 0; + regs.two_left = 0; + regs.two_right = 0; + regs.bg1_mask = 0; + regs.bg2_mask = 0; + regs.bg3_mask = 0; + regs.bg4_mask = 0; + regs.oam_mask = 0; + regs.col_mask = 0; + regs.bg1_main_enable = 0; + regs.bg1_sub_enable = 0; + regs.bg2_main_enable = 0; + regs.bg2_sub_enable = 0; + regs.bg3_main_enable = 0; + regs.bg3_sub_enable = 0; + regs.bg4_main_enable = 0; + regs.bg4_sub_enable = 0; + regs.oam_main_enable = 0; + regs.oam_sub_enable = 0; + regs.col_main_mask = 0; + regs.col_sub_mask = 0; + state.x = 0; + output.main.color_enable = 0; + output.sub.color_enable = 0; +} + sPPU::Window::Window(sPPU &self) : self(self) { } diff --git a/src/snes/ppu/sppu/window/window.hpp b/src/snes/ppu/sppu/window/window.hpp index a1f0f942..ae62f94d 100644 --- a/src/snes/ppu/sppu/window/window.hpp +++ b/src/snes/ppu/sppu/window/window.hpp @@ -72,6 +72,7 @@ public: void scanline(); void run(); + void reset(); Window(sPPU &self); diff --git a/src/snes/snes.hpp b/src/snes/snes.hpp index 9eacd74d..d5d6abd5 100644 --- a/src/snes/snes.hpp +++ b/src/snes/snes.hpp @@ -1,4 +1,4 @@ -static const char bsnesVersion[] = "063.09"; +static const char bsnesVersion[] = "063.10"; static const char bsnesTitle[] = "bsnes"; static const unsigned bsnesSerializerVersion = 9;