diff --git a/bsnes.exe b/bsnes.exe new file mode 100644 index 00000000..65a78ab8 Binary files /dev/null and b/bsnes.exe differ diff --git a/src/snes/ppu/sppu/background/background.cpp b/src/snes/ppu/sppu/background/background.cpp index 3df4d83f..57aa7794 100644 --- a/src/snes/ppu/sppu/background/background.cpp +++ b/src/snes/ppu/sppu/background/background.cpp @@ -1,5 +1,16 @@ #ifdef SPPU_CPP +void sPPU::Background::scanline() { + if(self.vcounter() == 1) { + regs.mosaic_y = 1; + regs.mosaic_countdown = 0; + } else { + if(!regs.mosaic || !regs.mosaic_countdown) regs.mosaic_y = self.vcounter(); + if(!regs.mosaic_countdown) regs.mosaic_countdown = regs.mosaic + 1; + regs.mosaic_countdown--; + } +} + void sPPU::Background::run() { output.main.valid = false; output.sub.valid = false; diff --git a/src/snes/ppu/sppu/background/background.hpp b/src/snes/ppu/sppu/background/background.hpp index 49cc20f6..0f5c7f96 100644 --- a/src/snes/ppu/sppu/background/background.hpp +++ b/src/snes/ppu/sppu/background/background.hpp @@ -14,6 +14,7 @@ public: unsigned screen_size; unsigned mosaic; unsigned mosaic_y; + unsigned mosaic_countdown; bool tile_size; unsigned mode; @@ -35,6 +36,7 @@ public: } main, sub; } output; + void scanline(); void run(); unsigned get_color(unsigned x, unsigned y, uint16 offset); diff --git a/src/snes/ppu/sppu/mmio/mmio.cpp b/src/snes/ppu/sppu/mmio/mmio.cpp index 6e2df25d..841f5d9e 100644 --- a/src/snes/ppu/sppu/mmio/mmio.cpp +++ b/src/snes/ppu/sppu/mmio/mmio.cpp @@ -57,19 +57,23 @@ bool sPPU::hires() const { //INIDISP void sPPU::mmio_w2100(uint8 data) { + if(regs.display_disabled && vcounter() == 225) oam.address_reset(); regs.display_disabled = data & 0x80; regs.display_brightness = data & 0x0f; } +//OBSEL void sPPU::mmio_w2101(uint8 data) { + oam.regs.base_size = (data >> 5) & 7; + oam.regs.nameselect = (data >> 3) & 3; + oam.regs.tiledata_addr = (data & 3) << 14; } //OAMADDL void sPPU::mmio_w2102(uint8 data) { regs.oam_baseaddr &= 0x0100; regs.oam_baseaddr |= (data << 0); - regs.oam_addr = regs.oam_baseaddr << 1; - regs.oam_firstsprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); + oam.address_reset(); } //OAMADDH @@ -77,8 +81,7 @@ void sPPU::mmio_w2103(uint8 data) { regs.oam_priority = data & 0x80; regs.oam_baseaddr &= 0x00ff; regs.oam_baseaddr |= (data & 1) << 8; - regs.oam_addr = regs.oam_baseaddr << 1; - regs.oam_firstsprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); + oam.address_reset(); } //OAMDATA @@ -93,7 +96,7 @@ void sPPU::mmio_w2104(uint8 data) { } regs.oam_addr = (regs.oam_addr + 1) & 0x03ff; - regs.oam_firstsprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); + oam.regs.first_sprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); } //BGMODE @@ -111,6 +114,7 @@ void sPPU::mmio_w2105(uint8 data) { bg2.regs.mode = Background::Mode::BPP2; bg2.regs.priority0 = 7; bg2.regs.priority1 = 10; bg3.regs.mode = Background::Mode::BPP2; bg3.regs.priority0 = 2; bg3.regs.priority1 = 5; bg4.regs.mode = Background::Mode::BPP2; bg4.regs.priority0 = 1; bg4.regs.priority1 = 4; + oam.regs.priority0 = 3; oam.regs.priority1 = 6; oam.regs.priority2 = 9; oam.regs.priority3 = 12; } break; case 1: { @@ -122,10 +126,12 @@ void sPPU::mmio_w2105(uint8 data) { bg1.regs.priority0 = 5; bg1.regs.priority1 = 8; bg2.regs.priority0 = 4; bg2.regs.priority1 = 7; bg3.regs.priority0 = 1; bg3.regs.priority1 = 10; + oam.regs.priority0 = 2; oam.regs.priority1 = 3; oam.regs.priority2 = 6; oam.regs.priority3 = 9; } else { bg1.regs.priority0 = 6; bg1.regs.priority1 = 9; bg2.regs.priority0 = 5; bg2.regs.priority1 = 8; bg3.regs.priority0 = 1; bg3.regs.priority1 = 3; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 7; oam.regs.priority3 = 10; } } break; @@ -136,6 +142,7 @@ void sPPU::mmio_w2105(uint8 data) { bg4.regs.mode = Background::Mode::Inactive; bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; } break; case 3: { @@ -145,6 +152,7 @@ void sPPU::mmio_w2105(uint8 data) { bg4.regs.mode = Background::Mode::Inactive; bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; } break; case 4: { @@ -154,6 +162,7 @@ void sPPU::mmio_w2105(uint8 data) { bg4.regs.mode = Background::Mode::Inactive; bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; } break; case 5: { @@ -163,6 +172,7 @@ void sPPU::mmio_w2105(uint8 data) { bg4.regs.mode = Background::Mode::Inactive; bg1.regs.priority0 = 3; bg1.regs.priority1 = 7; bg2.regs.priority0 = 1; bg2.regs.priority1 = 5; + oam.regs.priority0 = 2; oam.regs.priority1 = 4; oam.regs.priority2 = 6; oam.regs.priority3 = 8; } break; case 6: { @@ -171,6 +181,7 @@ void sPPU::mmio_w2105(uint8 data) { bg3.regs.mode = Background::Mode::Inactive; bg4.regs.mode = Background::Mode::Inactive; bg1.regs.priority0 = 2; bg1.regs.priority1 = 5; + oam.regs.priority0 = 1; oam.regs.priority1 = 3; oam.regs.priority2 = 4; oam.regs.priority3 = 6; } break; case 7: { @@ -180,11 +191,11 @@ void sPPU::mmio_w2105(uint8 data) { //MOSAIC void sPPU::mmio_w2106(uint8 data) { - regs.mosaic_size = (data >> 4) & 15; - bg4.regs.mosaic = (data & 0x08 ? regs.mosaic_size : 0); - bg3.regs.mosaic = (data & 0x04 ? regs.mosaic_size : 0); - bg2.regs.mosaic = (data & 0x02 ? regs.mosaic_size : 0); - bg1.regs.mosaic = (data & 0x01 ? regs.mosaic_size : 0); + unsigned mosaic_size = (data >> 4) & 15; + bg4.regs.mosaic = (data & 0x08 ? mosaic_size : 0); + bg3.regs.mosaic = (data & 0x04 ? mosaic_size : 0); + bg2.regs.mosaic = (data & 0x02 ? mosaic_size : 0); + bg1.regs.mosaic = (data & 0x01 ? mosaic_size : 0); } //BG1SC @@ -370,35 +381,79 @@ void sPPU::mmio_w2122(uint8 data) { regs.cgram_addr = (regs.cgram_addr + 1) & 0x01ff; } +//W12SEL void sPPU::mmio_w2123(uint8 data) { + window.regs.bg2_two_enable = data & 0x80; + window.regs.bg2_two_invert = data & 0x40; + window.regs.bg2_one_enable = data & 0x20; + window.regs.bg2_one_invert = data & 0x10; + window.regs.bg1_two_enable = data & 0x08; + window.regs.bg1_two_invert = data & 0x04; + window.regs.bg1_one_enable = data & 0x02; + window.regs.bg1_one_invert = data & 0x01; } +//W34SEL void sPPU::mmio_w2124(uint8 data) { + window.regs.bg4_two_enable = data & 0x80; + window.regs.bg4_two_invert = data & 0x40; + window.regs.bg4_one_enable = data & 0x20; + window.regs.bg4_one_invert = data & 0x10; + window.regs.bg3_two_enable = data & 0x08; + window.regs.bg3_two_invert = data & 0x04; + window.regs.bg3_one_enable = data & 0x02; + window.regs.bg3_one_invert = data & 0x01; } +//WOBJSEL void sPPU::mmio_w2125(uint8 data) { + window.regs.col_two_enable = data & 0x80; + window.regs.col_two_invert = data & 0x40; + window.regs.col_one_enable = data & 0x20; + window.regs.col_one_invert = data & 0x10; + window.regs.oam_two_enable = data & 0x08; + window.regs.oam_two_invert = data & 0x04; + window.regs.oam_one_enable = data & 0x02; + window.regs.oam_one_invert = data & 0x01; } +//WH0 void sPPU::mmio_w2126(uint8 data) { + window.regs.one_left = data; } +//WH1 void sPPU::mmio_w2127(uint8 data) { + window.regs.one_right = data; } +//WH2 void sPPU::mmio_w2128(uint8 data) { + window.regs.two_left = data; } +//WH3 void sPPU::mmio_w2129(uint8 data) { + window.regs.two_right = data; } +//WBGLOG void sPPU::mmio_w212a(uint8 data) { + window.regs.bg4_mask = (data >> 6) & 3; + window.regs.bg3_mask = (data >> 4) & 3; + window.regs.bg2_mask = (data >> 2) & 3; + window.regs.bg1_mask = (data >> 0) & 3; } +//WOBJLOG void sPPU::mmio_w212b(uint8 data) { + window.regs.col_mask = (data >> 2) & 3; + window.regs.oam_mask = (data >> 0) & 3; } //TM void sPPU::mmio_w212c(uint8 data) { + oam.regs.main_enabled = data & 0x10; bg4.regs.main_enabled = data & 0x08; bg3.regs.main_enabled = data & 0x04; bg2.regs.main_enabled = data & 0x02; @@ -407,28 +462,62 @@ void sPPU::mmio_w212c(uint8 data) { //TS void sPPU::mmio_w212d(uint8 data) { + oam.regs.sub_enabled = data & 0x10; bg4.regs.sub_enabled = data & 0x08; bg3.regs.sub_enabled = data & 0x04; bg2.regs.sub_enabled = data & 0x02; bg1.regs.sub_enabled = data & 0x01; } +//TMW void sPPU::mmio_w212e(uint8 data) { + window.regs.oam_main_enable = data & 0x10; + window.regs.bg4_main_enable = data & 0x08; + window.regs.bg3_main_enable = data & 0x04; + window.regs.bg2_main_enable = data & 0x02; + window.regs.bg1_main_enable = data & 0x01; } +//TSW void sPPU::mmio_w212f(uint8 data) { + window.regs.oam_sub_enable = data & 0x10; + window.regs.bg4_sub_enable = data & 0x08; + window.regs.bg3_sub_enable = data & 0x04; + window.regs.bg2_sub_enable = data & 0x02; + window.regs.bg1_sub_enable = data & 0x01; } +//CGWSEL void sPPU::mmio_w2130(uint8 data) { + window.regs.col_main_mask = (data >> 6) & 3; + window.regs.col_sub_mask = (data >> 4) & 3; + screen.regs.addsub_mode = data & 0x02; + screen.regs.direct_color = data & 0x01; } +//CGADDSUB void sPPU::mmio_w2131(uint8 data) { + screen.regs.color_mode = data & 0x80; + screen.regs.color_halve = data & 0x40; + screen.regs.back_color_enable = data & 0x20; + screen.regs.oam_color_enable = data & 0x10; + screen.regs.bg4_color_enable = data & 0x08; + screen.regs.bg3_color_enable = data & 0x04; + screen.regs.bg2_color_enable = data & 0x02; + screen.regs.bg1_color_enable = data & 0x01; } +//COLDATA void sPPU::mmio_w2132(uint8 data) { + if(data & 0x80) screen.regs.color_b = data & 0x1f; + if(data & 0x40) screen.regs.color_g = data & 0x1f; + if(data & 0x20) screen.regs.color_r = data & 0x1f; + screen.regs.color_rgb = (screen.regs.color_b << 10) + (screen.regs.color_g << 5) + screen.regs.color_r; } +//SETINI void sPPU::mmio_w2133(uint8 data) { + oam.regs.interlace = data & 0x02; } //MPYL @@ -462,7 +551,7 @@ uint8 sPPU::mmio_r2137() { uint8 sPPU::mmio_r2138() { regs.ppu1_mdr = oam_read(regs.oam_addr); regs.oam_addr = (regs.oam_addr + 1) & 0x03ff; - regs.oam_firstsprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); + oam.regs.first_sprite = (regs.oam_priority == false ? 0 : (regs.oam_addr >> 2) & 127); return regs.ppu1_mdr; } @@ -531,7 +620,8 @@ uint8 sPPU::mmio_r213d() { //STAT77 uint8 sPPU::mmio_r213e() { regs.ppu1_mdr &= 0x10; - //missing time over, range over flags + regs.ppu1_mdr |= oam.regs.time_over << 7; + regs.ppu1_mdr |= oam.regs.range_over << 6; regs.ppu1_mdr |= ppu1_version & 0x0f; return regs.ppu1_mdr; } @@ -558,7 +648,6 @@ void sPPU::mmio_reset() { regs.ppu1_mdr = 0xff; regs.ppu2_mdr = 0xff; - regs.mosaic_countdown = 0; regs.vram_readbuffer = 0x0000; regs.oam_latchdata = 0x00; regs.cgram_latchdata = 0x00; @@ -577,15 +666,11 @@ void sPPU::mmio_reset() { regs.oam_baseaddr = 0x0000; regs.oam_addr = 0x0000; regs.oam_priority = false; - regs.oam_firstsprite = 0; //$2105 BGMODE regs.bg3_priority = false; regs.bgmode = 0; - //$2106 MOSAIC - regs.mosaic_size = 0; - //$2115 VMAIN regs.vram_incmode = 1; regs.vram_mapping = 0; diff --git a/src/snes/ppu/sppu/mmio/mmio.hpp b/src/snes/ppu/sppu/mmio/mmio.hpp index 2dc1ab37..27c667e1 100644 --- a/src/snes/ppu/sppu/mmio/mmio.hpp +++ b/src/snes/ppu/sppu/mmio/mmio.hpp @@ -2,7 +2,6 @@ struct { uint8 ppu1_mdr; uint8 ppu2_mdr; - unsigned mosaic_countdown; uint16 vram_readbuffer; uint8 oam_latchdata; uint8 cgram_latchdata; @@ -21,15 +20,11 @@ struct { uint16 oam_baseaddr; uint16 oam_addr; bool oam_priority; - uint8 oam_firstsprite; //$2105 BGMODE bool bg3_priority; uint8 bgmode; - //$2106 MOSAIC - uint8 mosaic_size; - //$2115 VMAIN bool vram_incmode; uint8 vram_mapping; diff --git a/src/snes/ppu/sppu/screen/screen.cpp b/src/snes/ppu/sppu/screen/screen.cpp index fda1dcc9..5d9f4183 100644 --- a/src/snes/ppu/sppu/screen/screen.cpp +++ b/src/snes/ppu/sppu/screen/screen.cpp @@ -5,33 +5,123 @@ void sPPU::Screen::scanline() { } void sPPU::Screen::run() { - unsigned priority = 0; - uint16 color; + enum source_t { BG1, BG2, BG3, BG4, OAM, BACK }; + + unsigned priority_sub = 0; + uint16 color_sub; + source_t source_sub; + + if(self.bg1.output.sub.valid) { + priority_sub = self.bg1.output.sub.priority; + color_sub = self.bg1.output.sub.color; + 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; + 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; + 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; + 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; + 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); + } else { + color_sub = regs.color_rgb; + } + source_sub = BACK; + } + + unsigned priority_main = 0; + uint16 color_main; + source_t source_main; if(self.bg1.output.main.valid) { - priority = self.bg1.output.main.priority; - color = self.bg1.output.main.color; + 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) { - priority = self.bg2.output.main.priority; - color = self.bg2.output.main.color; + 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) { - priority = self.bg3.output.main.priority; - color = self.bg3.output.main.color; + 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) { - priority = self.bg4.output.main.priority; - color = self.bg4.output.main.color; + 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(priority == 0) { - color = (memory::cgram[1] << 8) + (memory::cgram[0] << 0); + 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); } - color = light_table[self.regs.display_brightness][color]; - if(self.regs.display_disabled) color = 0; - *output++ = color; - *output++ = color; + if(self.window.output.main.color_enable == false) { + if(self.window.output.sub.color_enable == false) { + color_main = 0x0000; + goto plot; + } + 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; +} + +uint16 sPPU::Screen::addsub(unsigned x, unsigned y, bool allow_halve) { + bool halve = allow_halve && regs.color_halve && self.window.output.main.color_enable; + + if(!regs.color_mode) { + if(!halve) { + unsigned sum = x + y; + unsigned carry = (sum - ((x ^ y) & 0x0421)) & 0x8420; + return (sum - carry) | (carry - (carry >> 5)); + } else { + return (x + y - ((x ^ y) & 0x0421)) >> 1; + } + } else { + unsigned diff = x - y + 0x8420; + unsigned borrow = (diff - ((x ^ y) & 0x8420)) & 0x8420; + if(!halve) { + return (diff - borrow) & (borrow - (borrow >> 5)); + } else { + return (((diff - borrow) & (borrow - (borrow >> 5))) & 0x7bde) >> 1; + } + } } sPPU::Screen::Screen(sPPU &self) : self(self) { diff --git a/src/snes/ppu/sppu/screen/screen.hpp b/src/snes/ppu/sppu/screen/screen.hpp index 4ac44dc4..0335d2de 100644 --- a/src/snes/ppu/sppu/screen/screen.hpp +++ b/src/snes/ppu/sppu/screen/screen.hpp @@ -3,6 +3,25 @@ public: sPPU &self; uint16 *output; + struct { + bool addsub_mode; + bool direct_color; + + bool color_mode; + bool color_halve; + bool bg1_color_enable; + bool bg2_color_enable; + bool bg3_color_enable; + bool bg4_color_enable; + bool oam_color_enable; + bool back_color_enable; + + uint8 color_b; + uint8 color_g; + uint8 color_r; + uint16 color_rgb; + } regs; + void scanline(); void run(); @@ -10,4 +29,5 @@ public: private: uint16 light_table[16][32768]; + uint16 addsub(unsigned x, unsigned y, bool allow_halve = true); }; diff --git a/src/snes/ppu/sppu/sppu.cpp b/src/snes/ppu/sppu/sppu.cpp index 52024b22..6f022517 100644 --- a/src/snes/ppu/sppu/sppu.cpp +++ b/src/snes/ppu/sppu/sppu.cpp @@ -6,6 +6,8 @@ namespace SNES { #include "background/background.cpp" #include "mmio/mmio.cpp" #include "screen/screen.cpp" +#include "sprite/sprite.cpp" +#include "window/window.cpp" #if !defined(DEBUGGER) sPPU ppu; @@ -19,28 +21,14 @@ void sPPU::enter() { add_clocks(88); - //mosaic - if(vcounter() == 1) { - bg1.regs.mosaic_y = 1; - bg2.regs.mosaic_y = 1; - bg3.regs.mosaic_y = 1; - bg4.regs.mosaic_y = 1; - } else { - if(!bg1.regs.mosaic || !regs.mosaic_countdown) bg1.regs.mosaic_y = vcounter(); - if(!bg2.regs.mosaic || !regs.mosaic_countdown) bg2.regs.mosaic_y = vcounter(); - if(!bg3.regs.mosaic || !regs.mosaic_countdown) bg3.regs.mosaic_y = vcounter(); - if(!bg4.regs.mosaic || !regs.mosaic_countdown) bg4.regs.mosaic_y = vcounter(); - if(!regs.mosaic_countdown) regs.mosaic_countdown = regs.mosaic_size + 1; - regs.mosaic_countdown--; - } - if(vcounter() >= 1 && vcounter() <= 224) { - screen.scanline(); for(unsigned n = 0; n < 256; n++) { bg1.run(); bg2.run(); bg3.run(); bg4.run(); + oam.run(); + window.run(); screen.run(); add_clocks(4); } @@ -76,11 +64,19 @@ void sPPU::reset() { void sPPU::scanline() { if(vcounter() == 0) frame(); + bg1.scanline(); + bg2.scanline(); + bg3.scanline(); + bg4.scanline(); + oam.scanline(); + window.scanline(); + screen.scanline(); } void sPPU::frame() { PPU::frame(); system.frame(); + oam.frame(); } sPPU::sPPU() : @@ -88,6 +84,8 @@ bg1(*this, Background::ID::BG1), bg2(*this, Background::ID::BG2), bg3(*this, Background::ID::BG3), bg4(*this, Background::ID::BG4), +oam(*this), +window(*this), screen(*this) { } diff --git a/src/snes/ppu/sppu/sppu.hpp b/src/snes/ppu/sppu/sppu.hpp index 4aa9c288..cef1379f 100644 --- a/src/snes/ppu/sppu/sppu.hpp +++ b/src/snes/ppu/sppu/sppu.hpp @@ -3,11 +3,15 @@ public: #include "background/background.hpp" #include "mmio/mmio.hpp" #include "screen/screen.hpp" + #include "sprite/sprite.hpp" + #include "window/window.hpp" Background bg1; Background bg2; Background bg3; Background bg4; + Sprite oam; + Window window; Screen screen; void enter(); diff --git a/src/snes/ppu/sppu/sprite/sprite.cpp b/src/snes/ppu/sppu/sprite/sprite.cpp new file mode 100644 index 00000000..aeeca8c6 --- /dev/null +++ b/src/snes/ppu/sppu/sprite/sprite.cpp @@ -0,0 +1,216 @@ +#ifdef SPPU_CPP + +void sPPU::Sprite::address_reset() { + self.regs.oam_addr = self.regs.oam_baseaddr << 1; + regs.first_sprite = (self.regs.oam_priority == false ? 0 : (self.regs.oam_addr >> 2) & 127); +} + +void sPPU::Sprite::frame() { + regs.time_over = false; + regs.range_over = false; +} + +void sPPU::Sprite::scanline() { + state.x = 0; + state.y = self.vcounter(); + + if(state.y == 225 && self.regs.display_disabled == false) address_reset(); + if(state.y < 1 || state.y > 224) return; + + const uint8 *tableA = memory::oam.data(); + const uint8 *tableB = memory::oam.data() + 512; + + for(unsigned i = 0; i < 128; i++) { + bool x = *tableB & (1 << ((i & 3) << 1)); + bool size = *tableB & (2 << ((i & 3) << 1)); + + switch(regs.base_size) { + case 0: list[i].width = (!size ? 8 : 16); + list[i].height = (!size ? 8 : 16); + break; + case 1: list[i].width = (!size ? 8 : 32); + list[i].height = (!size ? 8 : 32); + break; + case 2: list[i].width = (!size ? 8 : 64); + list[i].height = (!size ? 8 : 64); + break; + case 3: list[i].width = (!size ? 16 : 32); + list[i].height = (!size ? 16 : 32); + break; + case 4: list[i].width = (!size ? 16 : 64); + list[i].height = (!size ? 16 : 64); + break; + case 5: list[i].width = (!size ? 32 : 64); + list[i].height = (!size ? 32 : 64); + break; + case 6: list[i].width = (!size ? 16 : 32); + list[i].height = (!size ? 32 : 64); + break; + case 7: list[i].width = (!size ? 16 : 32); + list[i].height = (!size ? 32 : 32); + break; + } + + list[i].x = (x << 8) + tableA[0]; + list[i].y = (tableA[1] + 1) & 0xff; + list[i].character = tableA[2]; + list[i].vflip = tableA[3] & 0x80; + list[i].hflip = tableA[3] & 0x40; + list[i].priority = (tableA[3] >> 4) & 3; + list[i].palette = (tableA[3] >> 1) & 7; + list[i].nameselect = tableA[3] & 1; + + tableA += 4; + if((i & 3) == 3) tableB++; + } + + state.item_count = 0; + state.tile_count = 0; + memset(state.output_priority, 0xff, 256); + memset(state.item_list, 0xff, 32); + for(unsigned i = 0; i < 34; i++) state.tile_list[i].tile = 0xffff; + + for(unsigned i = 0; i < 128; i++) { + state.active_sprite = (i + regs.first_sprite) & 127; + if(on_scanline() == false) continue; + if(state.item_count++ >= 32) break; + state.item_list[state.item_count - 1] = (i + regs.first_sprite) & 127; + } + + for(signed i = 31; i >= 0; i--) { + if(state.item_list[i] == 0xff) continue; + state.active_sprite = state.item_list[i]; + load_tiles(); + } + + regs.time_over |= (state.tile_count > 34); + regs.range_over |= (state.item_count > 32); + + for(unsigned i = 0; i < 34; i++) { + if(state.tile_list[i].tile == 0xffff) continue; + render_tile(i); + } +} + +void sPPU::Sprite::run() { + output.main.valid = false; + output.sub.valid = false; + + unsigned x = state.x++; + + 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; + } + + 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; + } + } +} + +bool sPPU::Sprite::on_scanline() { + SpriteItem &sprite = list[state.active_sprite]; + if(sprite.x > 256 && (sprite.x + sprite.width - 1) < 512) return false; + + signed height = (regs.interlace == false ? sprite.height : (sprite.height >> 1)); + if(state.y >= sprite.y && state.y < (sprite.y + height)) return true; + if((sprite.y + height) >= 256 && state.y < ((sprite.y + height) & 255)) return true; + return false; +} + +void sPPU::Sprite::load_tiles() { + SpriteItem &sprite = list[state.active_sprite]; + unsigned tile_width = sprite.width >> 3; + signed x = sprite.x; + signed y = (state.y - sprite.y) & 0xff; + + if(sprite.vflip) { + if(sprite.width == sprite.height) { + y = (sprite.height - 1) - y; + } else { + y = (y < sprite.width) ? ((sprite.width - 1) - y) : (sprite.width + ((sprite.width - 1) - (y - sprite.width))); + } + } + + if(regs.interlace) { + y = (sprite.vflip == false ? y + self.field() : y - self.field()); + } + + x &= 511; + y &= 255; + + uint16 tiledata_addr = regs.tiledata_addr; + uint16 chrx = (sprite.character >> 0) & 15; + uint16 chry = (sprite.character >> 4) & 15; + if(sprite.nameselect) { + tiledata_addr += (256 * 32) + (regs.nameselect << 13); + } + chry += (y >> 3); + chry &= 15; + chry <<= 4; + + for(unsigned tx = 0; tx < tile_width; tx++) { + unsigned sx = (x + (tx << 3)) & 511; + if(x != 256 && sx >= 256 && (sx + 7) < 512) continue; + + if(state.tile_count++ >= 34) break; + unsigned n = state.tile_count - 1; + state.tile_list[n].x = sx; + state.tile_list[n].y = y; + state.tile_list[n].priority = sprite.priority; + state.tile_list[n].palette = 128 + (sprite.palette << 4); + state.tile_list[n].hflip = sprite.hflip; + + unsigned mx = (sprite.hflip == false) ? tx : ((tile_width - 1) - tx); + unsigned pos = tiledata_addr + ((chry + ((chrx + mx) & 15)) << 5); + state.tile_list[n].tile = (pos >> 5) & 0x07ff; + } +} + +void sPPU::Sprite::render_tile(unsigned tile) { + TileItem &item = state.tile_list[tile]; + + unsigned sx = item.x; + uint16 addr = (item.tile << 5) + ((item.y & 7) * 2); + for(unsigned x = 0; x < 8; x++) { + sx &= 511; + if(sx < 256) { + unsigned px = (item.hflip == false ? x : (7 - x)); + unsigned mask = 0x80 >> (px & 7); + + uint8 d0 = memory::vram[addr + 0]; + uint8 d1 = memory::vram[addr + 1]; + uint8 d2 = memory::vram[addr + 16]; + uint8 d3 = memory::vram[addr + 17]; + + unsigned color; + color = ((bool)(d0 & mask)) << 0; + color |= ((bool)(d1 & mask)) << 1; + color |= ((bool)(d2 & mask)) << 2; + color |= ((bool)(d3 & mask)) << 3; + + if(color) { + color += item.palette; + state.output_palette[sx] = color; + state.output_priority[sx] = item.priority; + } + } + sx++; + } +} + +sPPU::Sprite::Sprite(sPPU &self) : self(self) { +} + +#endif diff --git a/src/snes/ppu/sppu/sprite/sprite.hpp b/src/snes/ppu/sppu/sprite/sprite.hpp new file mode 100644 index 00000000..576136f5 --- /dev/null +++ b/src/snes/ppu/sppu/sprite/sprite.hpp @@ -0,0 +1,79 @@ +class Sprite { +public: + sPPU &self; + + struct { + bool main_enabled; + bool sub_enabled; + bool interlace; + + uint8 base_size; + uint8 nameselect; + uint16 tiledata_addr; + uint8 first_sprite; + + unsigned priority0; + unsigned priority1; + unsigned priority2; + unsigned priority3; + + bool time_over; + bool range_over; + } regs; + + struct SpriteItem { + uint8 width; + uint8 height; + uint16 x; + uint16 y; + uint8 character; + bool nameselect; + bool vflip; + bool hflip; + uint8 palette; + uint8 priority; + } list[128]; + + struct TileItem { + uint16 x; + uint16 y; + uint16 priority; + uint16 palette; + uint16 tile; + bool hflip; + }; + + struct State { + unsigned x; + unsigned y; + + unsigned item_count; + unsigned tile_count; + uint8 output_palette[256]; + uint8 output_priority[256]; + uint8 item_list[32]; + TileItem tile_list[34]; + unsigned active_sprite; + } state; + + struct { + struct { + bool valid; + uint16 color; + unsigned palette; + unsigned priority; + } main, sub; + } output; + + void address_reset(); + void frame(); + void scanline(); + void run(); + + Sprite(sPPU &self); + +private: + bool on_scanline(); + void load_tiles(); + void render_tile(unsigned tile); +}; diff --git a/src/snes/ppu/sppu/window/window.cpp b/src/snes/ppu/sppu/window/window.cpp new file mode 100644 index 00000000..45739015 --- /dev/null +++ b/src/snes/ppu/sppu/window/window.cpp @@ -0,0 +1,115 @@ +#ifdef SPPU_CPP + +void sPPU::Window::scanline() { + state.x = 0; +} + +void sPPU::Window::run() { + bool main, sub; + + test( + main, sub, + regs.bg1_one_enable, regs.bg1_one_invert, + regs.bg1_two_enable, regs.bg1_two_invert, + regs.bg1_mask, regs.bg1_main_enable, regs.bg1_sub_enable + ); + if(main) self.bg1.output.main.valid = false; + if(sub) self.bg1.output.sub.valid = false; + + test( + main, sub, + regs.bg2_one_enable, regs.bg2_one_invert, + regs.bg2_two_enable, regs.bg2_two_invert, + regs.bg2_mask, regs.bg2_main_enable, regs.bg2_sub_enable + ); + if(main) self.bg2.output.main.valid = false; + if(sub) self.bg2.output.sub.valid = false; + + test( + main, sub, + regs.bg3_one_enable, regs.bg3_one_invert, + regs.bg3_two_enable, regs.bg3_two_invert, + regs.bg3_mask, regs.bg3_main_enable, regs.bg3_sub_enable + ); + if(main) self.bg3.output.main.valid = false; + if(sub) self.bg3.output.sub.valid = false; + + test( + main, sub, + regs.bg4_one_enable, regs.bg4_one_invert, + regs.bg4_two_enable, regs.bg4_two_invert, + regs.bg4_mask, regs.bg4_main_enable, regs.bg4_sub_enable + ); + if(main) self.bg4.output.main.valid = false; + if(sub) self.bg4.output.sub.valid = false; + + test( + main, sub, + regs.oam_one_enable, regs.oam_one_invert, + regs.oam_two_enable, regs.oam_two_invert, + regs.oam_mask, regs.oam_main_enable, regs.oam_sub_enable + ); + if(main) self.oam.output.main.valid = false; + if(sub) self.oam.output.sub.valid = false; + + test( + main, sub, + regs.col_one_enable, regs.col_one_invert, + regs.col_two_enable, regs.col_two_invert, + regs.col_mask, true, true + ); + + switch(regs.col_main_mask) { + case 0: main = true; break; + case 1: break; + case 2: main = !main; break; + case 3: main = false; break; + } + + switch(regs.col_sub_mask) { + case 0: sub = true; break; + case 1: break; + case 2: sub = !sub; break; + case 3: sub = false; break; + } + + output.main.color_enable = main; + output.sub.color_enable = sub; + + state.x++; +} + +void sPPU::Window::test( + bool &main, bool &sub, + bool one_enable, bool one_invert, + bool two_enable, bool two_invert, + uint8 mask, bool main_enable, bool sub_enable +) { + unsigned x = state.x; + bool output; + + if(one_enable == false && two_enable == false) { + output = false; + } else if(one_enable == true && two_enable == false) { + output = (x >= regs.one_left && x <= regs.one_right) ^ one_invert; + } else if(one_enable == false && two_enable == true) { + output = (x >= regs.two_left && x <= regs.two_right) ^ two_invert; + } else { + bool one = (x >= regs.one_left && x <= regs.one_right) ^ one_invert; + bool two = (x >= regs.two_left && x <= regs.two_right) ^ two_invert; + switch(mask) { + case 0: output = (one | two) == 1; break; + case 1: output = (one & two) == 1; break; + case 2: output = (one ^ two) == 1; break; + case 3: output = (one ^ two) == 0; break; + } + } + + main = main_enable ? output : false; + sub = sub_enable ? output : false; +} + +sPPU::Window::Window(sPPU &self) : self(self) { +} + +#endif diff --git a/src/snes/ppu/sppu/window/window.hpp b/src/snes/ppu/sppu/window/window.hpp new file mode 100644 index 00000000..a1f0f942 --- /dev/null +++ b/src/snes/ppu/sppu/window/window.hpp @@ -0,0 +1,85 @@ +class Window { +public: + sPPU &self; + + struct { + bool bg1_one_enable; + bool bg1_one_invert; + bool bg1_two_enable; + bool bg1_two_invert; + + bool bg2_one_enable; + bool bg2_one_invert; + bool bg2_two_enable; + bool bg2_two_invert; + + bool bg3_one_enable; + bool bg3_one_invert; + bool bg3_two_enable; + bool bg3_two_invert; + + bool bg4_one_enable; + bool bg4_one_invert; + bool bg4_two_enable; + bool bg4_two_invert; + + bool oam_one_enable; + bool oam_one_invert; + bool oam_two_enable; + bool oam_two_invert; + + bool col_one_enable; + bool col_one_invert; + bool col_two_enable; + bool col_two_invert; + + uint8 one_left; + uint8 one_right; + uint8 two_left; + uint8 two_right; + + uint8 bg1_mask; + uint8 bg2_mask; + uint8 bg3_mask; + uint8 bg4_mask; + uint8 oam_mask; + uint8 col_mask; + + bool bg1_main_enable; + bool bg1_sub_enable; + bool bg2_main_enable; + bool bg2_sub_enable; + bool bg3_main_enable; + bool bg3_sub_enable; + bool bg4_main_enable; + bool bg4_sub_enable; + bool oam_main_enable; + bool oam_sub_enable; + + uint8 col_main_mask; + uint8 col_sub_mask; + } regs; + + struct { + unsigned x; + } state; + + struct { + struct { + bool color_enable; + } main, sub; + } output; + + void scanline(); + void run(); + + Window(sPPU &self); + +private: + void test( + bool &main, bool &sub, + bool one_enable, bool one_invert, + bool two_enable, bool two_invert, + uint8 mask, bool main_enable, bool sub_enable + ); +}; diff --git a/src/snes/snes.hpp b/src/snes/snes.hpp index acb04b47..9eacd74d 100644 --- a/src/snes/snes.hpp +++ b/src/snes/snes.hpp @@ -1,4 +1,4 @@ -static const char bsnesVersion[] = "063.08"; +static const char bsnesVersion[] = "063.09"; static const char bsnesTitle[] = "bsnes"; static const unsigned bsnesSerializerVersion = 9;