Update to bsnes v063r10 release.

With this release, the final last-generation holdout, the scanline-based PPU renderer, has been replaced with a true, accurate, cycle-level PPU that renders one dot at a time. Finally, this fulfills the greatest milestone remaining in the SNES emulation scene. With every processor emulated at the lowest possible level, SNES emulation finally rivals the accuracy levels that NES emulators have offered for years.
Now, please do understand that this release is not a beta, nor is it even an alpha. It is simply a preview of things to come, and as such you can consider it a pre-alpha. There are many caveats at this time.
First, it is very slow. More than twice as slow as v063 official. There have been absolutely no optimizations whatsoever to the new dot-based renderer. I do expect to be able to speed this up significantly in future releases.
Second, this may lock up on Windows Vista and later for unknown reasons. I haven't had a chance to look into it; so stick with Windows XP or Linux for now.
Third, save states are not supported yet. If you try and use them anyway, bad things will happen.
Fourth, and most importantly, this isn't 100% bit-perfect by any stretch of the imagination. Off the top of my head, memory is accessed far too often, the OAM and CGRAM address lines are not controlled by the S-PPU during active display, none of the various glitches are supported, and the OAM renderer does not pre-cache the next scanline's sprites, it happens on the same line for now.
I will obviously be doing my best to improve the accuracy of the aforementioned things. But even with those missing, this is still leaps and bounds above a pure scanline-based renderer. It essentially provides 682 times the parallelism. It is enough to finally properly emulate the shadow effect in Air Strike Patrol, and it finally eliminates the "PPU Hclock render position" hack once and for all.
Lastly, you'll need the DLLs from v063 official. I didn't bother to include them this time.
Enjoy!
This commit is contained in:
byuu 2010-04-07 14:40:59 +00:00
parent 0a3fdc404d
commit c33f70a8c6
19 changed files with 622 additions and 116 deletions

BIN
bsnes-sppu.exe Normal file

Binary file not shown.

BIN
bsnes.exe

Binary file not shown.

View File

@ -13,7 +13,7 @@ objects :=
# link += -lgcov
# profile-guided optimization
# flags += -fprofile-use
flags += -fprofile-use
# platform
ifeq ($(platform),x)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
#ifdef SPPU_CPP
bool sPPUDebugger::interlace() { return false; }
bool sPPUDebugger::overscan() { return false; }
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() :

View File

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

View File

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

View File

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

View File

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

View File

@ -72,6 +72,7 @@ public:
void scanline();
void run();
void reset();
Window(sPPU &self);

View File

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