Update to bsnes v063r09 release.

So that's about 24 solid hours worth of programming in two days. Holy
fuck am I exhausted. Don't expect the last bits any time soon.

Missing features:
- Mode 7 renderer
- OAM previous-line caching
- offset-per-tile mode
- some edge cases in color add/sub
- hires
- interlace
- overscan
- S-PPU control over VRAM, OAM, CGRAM during active display

Speed hit is about as bad as I had feared. 172fps with scanline
rendering to 80fps with dot rendering. I'm guessing that with
optimizations I can make it to ~100-110fps.
This commit is contained in:
byuu 2010-04-05 18:38:43 +00:00
parent efa7879c6d
commit 0a3fdc404d
14 changed files with 758 additions and 58 deletions

BIN
bsnes.exe Normal file

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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