mirror of https://github.com/bsnes-emu/bsnes.git
Update to v097r27 release.
byuu says: Absolutely major improvements to the WS/C emulation today. Changelog: (all WS/C related) - fixed channel 3 sweep pitch adjustment - fixed channel 3 sweep value sign extension - removed errant channel 5 speed setting (not what's really going on) - fixed sign extension on channel 5 samples - improved DAC mixing of all five audio channels - fixed r26 regression with PPU timing loop - fixed sprite windowing behavior (sprite attribute flag is window mode; not window enable) - added per-scanline register latching to the PPU - IRQs should terminate HLT even when the IRQ enable register bits are clear - fixed PALMONO reads - added blur emulation - added color emulation (based on GBA, so it heavily desaturates colors; not entirely correct, but it helps a lot) - no longer decimating audio to 24KHz; running at full 3.072MHz through the windowed sinc filter [1] - cleaned up PPU portRead / portWrite functions significantly - emulated a weird quirk as mentioned by trap15 regarding timer frequency writes enabling said timers [2] - emulated LCD_CTRL sleep bit; screen can now be disabled (always draws black in this case for now) - improved OAM caching; but it's still disabled because it causes huge amounts of sprite glitches (unsure why) - fixed rendering of sprites that wrap around the screen edges back to the top/left of the display - emulated keypad interrupts - icarus: detect orientation bit in game header - higan: use orientation setting in manifest to set default screen rotation [1] the 24KHz -> 3.072MHz sound change is huge. Sound is substantially improved over the previous WIPs. It does come at a pretty major speed penalty, though. This is the highest frequency of any system in higan running through an incredibly (amazing, yet) demanding sinc resampler. Frame rate dropped from around 240fps to 150fps with the sinc filter on. If you choose a different audio filter, you'll get most of that speed back, but audio will sound worse again. [2] we aren't sure if this is correct hardware behavior or not. It seems to very slightly help Magical Drop, but not much. The blur emulation is brutal. It's absolutely required for Riviera's translucency simulation of selected menu items, but it causes serious headaches due to the WS's ~75hz refresh rate running on ~60hz monitors without vsync. It's probably best to leave it off and just deal with the awful flickering on Riviera's menu options. Overall, WS/C emulation is starting to get quite usable indeed. Couple of major bugs that I'd really like to get fixed before releasing it, though. But they're getting harder and harder to fix ... Major Bugs: - Final Fantasy battle background music is absent. Sound effects still work. Very weird. - Final Fantasy IV scrolling during airship flight opening sequence is horribly broken. Scrolls one screen at a time. - Magical Drop flickers like crazy in-game. Basically unplayable like this. - Star Hearts character names don't appear in the smaller dialog box that pops up. Minor Bugs: - Occasional flickering during Riviera opening scenes. - One-frame flicker of Leda's sprite at the start of the first stage.
This commit is contained in:
parent
a7f7985581
commit
d3413db04a
|
@ -1,2 +1,2 @@
|
|||
higan/profile/WonderSwan.sys/internal.rom
|
||||
higan/profile/WonderSwan Color.sys/internal.rom
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
||||
|
|
|
@ -6,7 +6,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "097.26";
|
||||
static const string Version = "097.27";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -22,9 +22,8 @@ auto APU::main() -> void {
|
|||
channel3.run();
|
||||
channel4.run();
|
||||
channel5.run();
|
||||
if(s.clock.bits(0,12) == 0) channel3.sweep();
|
||||
if(s.clock.bits(0, 6) == 0) dacRun();
|
||||
s.clock++;
|
||||
dacRun();
|
||||
if(++s.sweepClock == 0) channel3.sweep();
|
||||
step(1);
|
||||
}
|
||||
|
||||
|
@ -40,7 +39,7 @@ auto APU::dacRun() -> void {
|
|||
if(channel2.r.enable) left += channel2.o.left;
|
||||
if(channel3.r.enable) left += channel3.o.left;
|
||||
if(channel4.r.enable) left += channel4.o.left;
|
||||
if(channel5.r.enable) left += (int11)channel5.o.left >> 3;
|
||||
if(channel5.r.enable) left += channel5.o.left;
|
||||
left = sclamp<16>(left << 5);
|
||||
|
||||
int right = 0;
|
||||
|
@ -48,7 +47,7 @@ auto APU::dacRun() -> void {
|
|||
if(channel2.r.enable) right += channel2.o.right;
|
||||
if(channel3.r.enable) right += channel3.o.right;
|
||||
if(channel4.r.enable) right += channel4.o.right;
|
||||
if(channel5.r.enable) right += (int11)channel5.o.right >> 3;
|
||||
if(channel5.r.enable) right += channel5.o.right;
|
||||
right = sclamp<16>(right << 5);
|
||||
|
||||
if(!r.headphoneEnable) {
|
||||
|
@ -73,7 +72,7 @@ auto APU::power() -> void {
|
|||
bus.map(this, 0x006a, 0x006b);
|
||||
bus.map(this, 0x0080, 0x0095);
|
||||
|
||||
s.clock = 0;
|
||||
s.sweepClock = 0;
|
||||
r.waveBase = 0;
|
||||
r.speakerEnable = 0;
|
||||
r.speakerShift = 0;
|
||||
|
|
|
@ -11,7 +11,7 @@ struct APU : Thread, IO {
|
|||
auto portWrite(uint16 addr, uint8 data) -> void;
|
||||
|
||||
struct State {
|
||||
uint13 clock;
|
||||
uint13 sweepClock;
|
||||
} s;
|
||||
|
||||
struct DMA {
|
||||
|
@ -146,7 +146,7 @@ struct APU : Thread, IO {
|
|||
uint4 volumeRight;
|
||||
|
||||
//$008c SND_SWEEP_VALUE
|
||||
uint8 sweepValue;
|
||||
int8 sweepValue;
|
||||
|
||||
//$008d SND_SWEEP_TIME
|
||||
uint5 sweepTime;
|
||||
|
@ -197,13 +197,13 @@ struct APU : Thread, IO {
|
|||
auto run() -> void;
|
||||
|
||||
struct Output {
|
||||
uint11 left;
|
||||
uint11 right;
|
||||
int11 left;
|
||||
int11 right;
|
||||
} o;
|
||||
|
||||
struct State {
|
||||
uint clock;
|
||||
uint8 data;
|
||||
int8 data;
|
||||
} s;
|
||||
|
||||
struct Registers {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
auto APU::Channel3::sweep() -> void {
|
||||
if(r.sweep && --s.sweepCounter < 0) {
|
||||
s.sweepCounter = r.sweepTime;
|
||||
r.pitch += r.sweepTime;
|
||||
r.pitch += r.sweepValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
auto APU::Channel5::run() -> void {
|
||||
if(r.speed <= 5 && s.clock++ < 1536) return; //2000hz
|
||||
if(r.speed == 6 && s.clock++ < 2048) return; //1500hz
|
||||
if(r.speed == 7 && s.clock++ < 3072) return; //1000hz
|
||||
s.clock = 0;
|
||||
|
||||
uint11 output = s.data;
|
||||
int11 output = (int8)s.data;
|
||||
switch(r.scale) {
|
||||
case 0: output <<= 3 - r.volume; break;
|
||||
case 1: output <<= 3 - r.volume; output |= -0x100 << (3 - r.volume); break;
|
||||
case 2: output <<= 3 - r.volume; break;
|
||||
case 3: output <<= r.volume; break;
|
||||
case 3: output <<= 3; break;
|
||||
}
|
||||
|
||||
o.left = r.leftEnable ? output : (uint11)0;
|
||||
o.right = r.rightEnable ? output : (uint11)0;
|
||||
o.left = r.leftEnable ? output : (int11)0;
|
||||
o.right = r.rightEnable ? output : (int11)0;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ auto Cartridge::load() -> void {
|
|||
}
|
||||
|
||||
information.title = document["information/title"].text();
|
||||
information.orientation = document["information/orientation"].text() == "vertical";
|
||||
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ struct Cartridge : IO {
|
|||
struct Information {
|
||||
string manifest;
|
||||
string title;
|
||||
bool orientation; //0 = horizontal; 1 = vertical
|
||||
string sha256;
|
||||
} information;
|
||||
};
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
auto CPU::poll() -> void {
|
||||
if(!V30MZ::r.f.i || !state.poll) return;
|
||||
if(!state.poll) return;
|
||||
|
||||
for(int n = 7; n >= 0; n--) {
|
||||
if(!r.interruptEnable.bit(n)) continue;
|
||||
if(!r.interruptStatus.bit(n)) continue;
|
||||
return interrupt(r.interruptBase + n);
|
||||
state.halt = false;
|
||||
if(V30MZ::r.f.i) interrupt(r.interruptBase + n);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ auto Interface::videoFrequency() -> double {
|
|||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 3072000.0 / 128.0; //24Khz
|
||||
return 3072000.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
|
@ -177,14 +177,20 @@ auto Interface::unserialize(serializer& s) -> bool {
|
|||
}
|
||||
|
||||
auto Interface::cap(const string& name) -> bool {
|
||||
if(name == "Blur Emulation") return true;
|
||||
if(name == "Color Emulation") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::get(const string& name) -> any {
|
||||
if(name == "Blur Emulation") return settings.blurEmulation;
|
||||
if(name == "Color Emulation") return settings.colorEmulation;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ private:
|
|||
};
|
||||
|
||||
struct Settings {
|
||||
bool blurEmulation = true;
|
||||
bool colorEmulation = true;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
|
|
|
@ -1,39 +1,29 @@
|
|||
auto PPU::portRead(uint16 addr) -> uint8 {
|
||||
//DISP_CTRL
|
||||
if(addr == 0x0000) {
|
||||
return (
|
||||
r.screenTwoWindowEnable << 5
|
||||
| r.screenTwoWindowInvert << 4
|
||||
| r.spriteWindowEnable << 3
|
||||
| r.spriteEnable << 2
|
||||
| r.screenTwoEnable << 1
|
||||
| r.screenOneEnable << 0
|
||||
);
|
||||
}
|
||||
if(addr == 0x0000) return (
|
||||
r.screenOneEnable << 0
|
||||
| r.screenTwoEnable << 1
|
||||
| r.spriteEnable << 2
|
||||
| r.spriteWindowEnable << 3
|
||||
| r.screenTwoWindowInvert << 4
|
||||
| r.screenTwoWindowEnable << 5
|
||||
);
|
||||
|
||||
//BACK_COLOR
|
||||
if(addr == 0x0001) {
|
||||
if(!system.depth()) {
|
||||
return r.backColor.bits(0,2);
|
||||
} else {
|
||||
return r.backColor.bits(0,7);
|
||||
}
|
||||
}
|
||||
if(addr == 0x0001) return (
|
||||
r.backColor.bits(0, !system.depth() ? 2 : 7)
|
||||
);
|
||||
|
||||
//LINE_CUR
|
||||
if(addr == 0x0002) return status.vclk;
|
||||
if(addr == 0x0002) return s.vclk;
|
||||
|
||||
//LINE_CMP
|
||||
if(addr == 0x0003) return r.lineCompare;
|
||||
|
||||
//SPR_BASE
|
||||
if(addr == 0x0004) {
|
||||
if(!system.depth()) {
|
||||
return r.spriteBase.bits(0,4);
|
||||
} else {
|
||||
return r.spriteBase.bits(0,5);
|
||||
}
|
||||
}
|
||||
if(addr == 0x0004) return (
|
||||
r.spriteBase.bits(0, 4 + system.depth())
|
||||
);
|
||||
|
||||
//SPR_FIRST
|
||||
if(addr == 0x0005) return r.spriteFirst;
|
||||
|
@ -42,13 +32,10 @@ auto PPU::portRead(uint16 addr) -> uint8 {
|
|||
if(addr == 0x0006) return r.spriteCount;
|
||||
|
||||
//MAP_BASE
|
||||
if(addr == 0x0007) {
|
||||
if(!system.depth()) {
|
||||
return r.screenTwoMapBase.bits(0,2) << 4 | r.screenOneMapBase.bits(0,2) << 0;
|
||||
} else {
|
||||
return r.screenTwoMapBase.bits(0,3) << 4 | r.screenOneMapBase.bits(0,3) << 0;
|
||||
}
|
||||
}
|
||||
if(addr == 0x0007) return (
|
||||
r.screenOneMapBase.bits(0, 2 + system.depth()) << 0
|
||||
| r.screenTwoMapBase.bits(0, 2 + system.depth()) << 4
|
||||
);
|
||||
|
||||
//SCR2_WIN_X0
|
||||
if(addr == 0x0008) return r.screenTwoWindowX0;
|
||||
|
@ -87,19 +74,21 @@ auto PPU::portRead(uint16 addr) -> uint8 {
|
|||
if(addr == 0x0013) return r.scrollTwoY;
|
||||
|
||||
//LCD_CTRL
|
||||
if(addr == 0x0014) return r.control;
|
||||
if(addr == 0x0014) return (
|
||||
r.lcdEnable << 0
|
||||
| r.lcdContrast << 1
|
||||
| r.lcdUnknown << 2
|
||||
);
|
||||
|
||||
//LCD_ICON
|
||||
if(addr == 0x0015) {
|
||||
return (
|
||||
r.iconAux3 << 5
|
||||
| r.iconAux2 << 4
|
||||
| r.iconAux1 << 3
|
||||
| r.iconHorizontal << 2
|
||||
| r.iconVertical << 1
|
||||
| r.iconSleep << 0
|
||||
);
|
||||
}
|
||||
if(addr == 0x0015) return (
|
||||
r.iconSleep << 0
|
||||
| r.iconVertical << 1
|
||||
| r.iconHorizontal << 2
|
||||
| r.iconAux1 << 3
|
||||
| r.iconAux2 << 4
|
||||
| r.iconAux3 << 5
|
||||
);
|
||||
|
||||
//LCD_VTOTAL
|
||||
if(addr == 0x0016) return r.vtotal;
|
||||
|
@ -108,20 +97,16 @@ auto PPU::portRead(uint16 addr) -> uint8 {
|
|||
if(addr == 0x0017) return r.vblank;
|
||||
|
||||
//PALMONO_POOL
|
||||
if(addr >= 0x001c && addr <= 0x001f) {
|
||||
return (
|
||||
r.pool[addr.bits(1,0) * 2 + 1] << 4
|
||||
| r.pool[addr.bits(1,0) * 2 + 0] << 0
|
||||
);
|
||||
}
|
||||
if(addr >= 0x001c && addr <= 0x001f) return (
|
||||
r.pool[addr.bits(0,1) * 2 + 0] << 0
|
||||
| r.pool[addr.bits(0,1) * 2 + 1] << 4
|
||||
);
|
||||
|
||||
//PALMONO
|
||||
if(addr >= 0x0020 && addr <= 0x003f) {
|
||||
return (
|
||||
r.palette[addr.bits(3,1)].color[addr.bit(0) * 2 + 1] << 4
|
||||
| r.palette[addr.bits(3,1)].color[addr.bit(0) * 2 + 0] << 0
|
||||
);
|
||||
}
|
||||
if(addr >= 0x0020 && addr <= 0x003f) return (
|
||||
r.palette[addr.bits(1,4)].color[addr.bit(0) * 2 + 0] << 0
|
||||
| r.palette[addr.bits(1,4)].color[addr.bit(0) * 2 + 1] << 4
|
||||
);
|
||||
|
||||
//TMR_CTRL
|
||||
if(addr == 0x00a2) return (
|
||||
|
@ -153,165 +138,113 @@ auto PPU::portRead(uint16 addr) -> uint8 {
|
|||
auto PPU::portWrite(uint16 addr, uint8 data) -> void {
|
||||
//DISP_CTRL
|
||||
if(addr == 0x0000) {
|
||||
r.screenTwoWindowEnable = data.bit(5);
|
||||
r.screenTwoWindowInvert = data.bit(4);
|
||||
r.spriteWindowEnable = data.bit(3);
|
||||
r.spriteEnable = data.bit(2);
|
||||
r.screenTwoEnable = data.bit(1);
|
||||
r.screenOneEnable = data.bit(0);
|
||||
return;
|
||||
r.screenTwoEnable = data.bit(1);
|
||||
r.spriteEnable = data.bit(2);
|
||||
r.spriteWindowEnable = data.bit(3);
|
||||
r.screenTwoWindowInvert = data.bit(4);
|
||||
r.screenTwoWindowEnable = data.bit(5);
|
||||
}
|
||||
|
||||
//BACK_COLOR
|
||||
if(addr == 0x0001) {
|
||||
r.backColor = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0001) r.backColor = data;
|
||||
|
||||
//LINE_CMP
|
||||
if(addr == 0x0003) {
|
||||
r.lineCompare = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0003) r.lineCompare = data;
|
||||
|
||||
//SPR_BASE
|
||||
if(addr == 0x0004) {
|
||||
r.spriteBase = data.bits(0,5);
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0004) r.spriteBase = data.bits(0,5);
|
||||
|
||||
//SPR_FIRST
|
||||
if(addr == 0x0005) {
|
||||
r.spriteFirst = data.bits(6,0);
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0005) r.spriteFirst = data.bits(6,0);
|
||||
|
||||
//SPR_COUNT
|
||||
if(addr == 0x0006) {
|
||||
r.spriteCount = data; //0 - 128
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0006) r.spriteCount = data;
|
||||
|
||||
//MAP_BASE
|
||||
if(addr == 0x0007) {
|
||||
r.screenOneMapBase = data.bits(0,3);
|
||||
r.screenTwoMapBase = data.bits(4,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//SCR2_WIN_X0
|
||||
if(addr == 0x0008) {
|
||||
r.screenTwoWindowX0 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0008) r.screenTwoWindowX0 = data;
|
||||
|
||||
//SCR2_WIN_Y0
|
||||
if(addr == 0x0009) {
|
||||
r.screenTwoWindowY0 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0009) r.screenTwoWindowY0 = data;
|
||||
|
||||
//SCR2_WIN_X1
|
||||
if(addr == 0x000a) {
|
||||
r.screenTwoWindowX1 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000a) r.screenTwoWindowX1 = data;
|
||||
|
||||
//SCR2_WIN_Y1
|
||||
if(addr == 0x000b) {
|
||||
r.screenTwoWindowY1 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000b) r.screenTwoWindowY1 = data;
|
||||
|
||||
//SPR_WIN_X0
|
||||
if(addr == 0x000c) {
|
||||
r.spriteWindowX0 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000c) r.spriteWindowX0 = data;
|
||||
|
||||
//SPR_WIN_Y0
|
||||
if(addr == 0x000d) {
|
||||
r.spriteWindowY0 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000d) r.spriteWindowY0 = data;
|
||||
|
||||
//SPR_WIN_X1
|
||||
if(addr == 0x000e) {
|
||||
r.spriteWindowX1 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000e) r.spriteWindowX1 = data;
|
||||
|
||||
//SPR_WIN_Y1
|
||||
if(addr == 0x000f) {
|
||||
r.spriteWindowY1 = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x000f) r.spriteWindowY1 = data;
|
||||
|
||||
//SCR1_X
|
||||
if(addr == 0x0010) {
|
||||
r.scrollOneX = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0010) r.scrollOneX = data;
|
||||
|
||||
//SCR1_Y
|
||||
if(addr == 0x0011) {
|
||||
r.scrollOneY = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0011) r.scrollOneY = data;
|
||||
|
||||
//SCR2_X
|
||||
if(addr == 0x0012) {
|
||||
r.scrollTwoX = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0012) r.scrollTwoX = data;
|
||||
|
||||
//SCR2_Y
|
||||
if(addr == 0x0013) {
|
||||
r.scrollTwoY = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0013) r.scrollTwoY = data;
|
||||
|
||||
//LCD_CTRL
|
||||
if(addr == 0x0014) {
|
||||
r.control = data;
|
||||
return;
|
||||
r.lcdEnable = data.bit (0);
|
||||
r.lcdContrast = data.bit (1);
|
||||
r.lcdUnknown = data.bits(2,7);
|
||||
|
||||
if(system.model() == Model::WonderSwanColor) {
|
||||
r.lcdUnknown &= 0b111100;
|
||||
}
|
||||
|
||||
if(system.model() == Model::SwanCrystal) {
|
||||
r.lcdContrast = 0;
|
||||
r.lcdUnknown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//LCD_ICON
|
||||
if(addr == 0x0015) {
|
||||
r.iconAux3 = data.bit(5);
|
||||
r.iconAux2 = data.bit(4);
|
||||
r.iconAux1 = data.bit(3);
|
||||
r.iconHorizontal = data.bit(2);
|
||||
r.iconVertical = data.bit(1);
|
||||
r.iconSleep = data.bit(0);
|
||||
return;
|
||||
r.iconVertical = data.bit(1);
|
||||
r.iconHorizontal = data.bit(2);
|
||||
r.iconAux1 = data.bit(3);
|
||||
r.iconAux2 = data.bit(4);
|
||||
r.iconAux3 = data.bit(5);
|
||||
}
|
||||
|
||||
//LCD_VTOTAL
|
||||
if(addr == 0x0016) {
|
||||
r.vtotal = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0016) r.vtotal = data;
|
||||
|
||||
//LCD_VBLANK
|
||||
if(addr == 0x0017) {
|
||||
r.vblank = data;
|
||||
return;
|
||||
}
|
||||
if(addr == 0x0017) r.vblank = data;
|
||||
|
||||
//PALMONO_POOL
|
||||
if(addr >= 0x001c && addr <= 0x001f) {
|
||||
r.pool[addr.bits(1,0) * 2 + 1] = data.bits(7,4);
|
||||
r.pool[addr.bits(1,0) * 2 + 0] = data.bits(3,0);
|
||||
return;
|
||||
r.pool[addr.bits(0,1) * 2 + 0] = data.bits(0,3);
|
||||
r.pool[addr.bits(0,1) * 2 + 1] = data.bits(4,7);
|
||||
}
|
||||
|
||||
//PALMONO
|
||||
if(addr >= 0x0020 && addr <= 0x003f) {
|
||||
r.palette[addr.bits(4,1)].color[addr.bit(0) * 2 + 1] = data.bits(6,4);
|
||||
r.palette[addr.bits(4,1)].color[addr.bit(0) * 2 + 0] = data.bits(2,0);
|
||||
return;
|
||||
r.palette[addr.bits(1,4)].color[addr.bit(0) * 2 + 0] = data.bits(0,2);
|
||||
r.palette[addr.bits(1,4)].color[addr.bit(0) * 2 + 1] = data.bits(4,6);
|
||||
}
|
||||
|
||||
//TMR_CTRL
|
||||
|
@ -320,6 +253,9 @@ auto PPU::portWrite(uint16 addr, uint8 data) -> void {
|
|||
r.htimerRepeat = data.bit(1);
|
||||
r.vtimerEnable = data.bit(2);
|
||||
r.vtimerRepeat = data.bit(3);
|
||||
|
||||
if(r.htimerEnable) r.htimerCounter = 0;
|
||||
if(r.vtimerEnable) r.vtimerCounter = 0;
|
||||
}
|
||||
|
||||
//HTMR_FREQ
|
||||
|
@ -329,4 +265,18 @@ auto PPU::portWrite(uint16 addr, uint8 data) -> void {
|
|||
//VTMR_FREQ
|
||||
if(addr == 0x00a6) r.vtimerFrequency.byte(0) = data;
|
||||
if(addr == 0x00a7) r.vtimerFrequency.byte(1) = data;
|
||||
|
||||
//todo: is this correct?
|
||||
if(addr == 0x00a5) {
|
||||
r.htimerEnable = true;
|
||||
r.htimerRepeat = true;
|
||||
r.htimerCounter = 0;
|
||||
}
|
||||
|
||||
//todo: is this correct?
|
||||
if(addr == 0x00a7) {
|
||||
r.vtimerEnable = true;
|
||||
r.vtimerRepeat = true;
|
||||
r.vtimerCounter = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,14 @@ auto PPU::Enter() -> void {
|
|||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
if(status.vclk < 144) {
|
||||
if(s.vclk < 144) {
|
||||
latchRegisters();
|
||||
renderSpriteFetch();
|
||||
renderSpriteDecode();
|
||||
for(auto x : range(256)) {
|
||||
if(!system.color()) {
|
||||
for(auto x : range(224)) {
|
||||
if(!r.lcdEnable) {
|
||||
pixel = {Pixel::Source::Back, 0x000};
|
||||
} else if(!system.color()) {
|
||||
renderMonoBack();
|
||||
renderMonoScreenOne();
|
||||
renderMonoScreenTwo();
|
||||
|
@ -29,7 +32,7 @@ auto PPU::main() -> void {
|
|||
renderColorScreenTwo();
|
||||
renderColorSprite();
|
||||
}
|
||||
output[status.vclk * 224 + status.hclk] = pixel.color;
|
||||
output[s.vclk * 224 + s.hclk] = pixel.color;
|
||||
step(1);
|
||||
}
|
||||
step(32);
|
||||
|
@ -50,12 +53,12 @@ auto PPU::main() -> void {
|
|||
}
|
||||
|
||||
auto PPU::scanline() -> void {
|
||||
status.hclk = 0;
|
||||
status.vclk++;
|
||||
if(status.vclk == r.lineCompare) {
|
||||
s.hclk = 0;
|
||||
s.vclk++;
|
||||
if(s.vclk == r.lineCompare) {
|
||||
cpu.raise(CPU::Interrupt::LineCompare);
|
||||
}
|
||||
if(status.vclk == 144) {
|
||||
if(s.vclk == 144) {
|
||||
cpu.raise(CPU::Interrupt::Vblank);
|
||||
if(r.vtimerEnable && r.vtimerCounter < r.vtimerFrequency) {
|
||||
if(++r.vtimerCounter == r.vtimerFrequency) {
|
||||
|
@ -68,22 +71,53 @@ auto PPU::scanline() -> void {
|
|||
}
|
||||
}
|
||||
}
|
||||
if(status.vclk == 159) frame();
|
||||
if(s.vclk == 159) frame();
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
status.vclk = 0;
|
||||
s.field = !s.field;
|
||||
s.vclk = 0;
|
||||
video.refresh();
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto PPU::step(uint clocks) -> void {
|
||||
status.hclk += clocks;
|
||||
s.hclk += clocks;
|
||||
|
||||
clock += clocks;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto PPU::latchRegisters() -> void {
|
||||
l.backColor = r.backColor;
|
||||
|
||||
l.screenOneEnable = r.screenOneEnable;
|
||||
l.screenOneMapBase = r.screenOneMapBase;
|
||||
l.scrollOneX = r.scrollOneX;
|
||||
l.scrollOneY = r.scrollOneY;
|
||||
|
||||
l.screenTwoEnable = r.screenTwoEnable;
|
||||
l.screenTwoMapBase = r.screenTwoMapBase;
|
||||
l.scrollTwoX = r.scrollTwoX;
|
||||
l.scrollTwoY = r.scrollTwoY;
|
||||
l.screenTwoWindowEnable = r.screenTwoWindowEnable;
|
||||
l.screenTwoWindowInvert = r.screenTwoWindowInvert;
|
||||
l.screenTwoWindowX0 = r.screenTwoWindowX0;
|
||||
l.screenTwoWindowY0 = r.screenTwoWindowY0;
|
||||
l.screenTwoWindowX1 = r.screenTwoWindowX1;
|
||||
l.screenTwoWindowY1 = r.screenTwoWindowY1;
|
||||
|
||||
l.spriteEnable = r.spriteEnable;
|
||||
l.spriteBase = r.spriteBase;
|
||||
l.spriteFirst = r.spriteFirst;
|
||||
l.spriteCount = r.spriteCount;
|
||||
l.spriteWindowEnable = r.spriteWindowEnable;
|
||||
l.spriteWindowX0 = r.spriteWindowX0;
|
||||
l.spriteWindowY0 = r.spriteWindowY0;
|
||||
l.spriteWindowX1 = r.spriteWindowX1;
|
||||
l.spriteWindowY1 = r.spriteWindowY1;
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(PPU::Enter, 3'072'000);
|
||||
|
||||
|
@ -93,24 +127,25 @@ auto PPU::power() -> void {
|
|||
bus.map(this, 0x00a4, 0x00ab);
|
||||
|
||||
for(auto& n : output) n = 0;
|
||||
for(auto& n : oam) n = 0;
|
||||
for(auto& n : oam[0]) n = 0;
|
||||
for(auto& n : oam[1]) n = 0;
|
||||
|
||||
status.vclk = 0;
|
||||
status.hclk = 0;
|
||||
s.vclk = 0;
|
||||
s.hclk = 0;
|
||||
|
||||
r.screenTwoWindowEnable = 0;
|
||||
r.screenTwoWindowInvert = 0;
|
||||
r.spriteWindowEnable = 0;
|
||||
r.spriteEnable = 0;
|
||||
r.screenTwoEnable = 0;
|
||||
r.screenOneEnable = 0;
|
||||
r.screenTwoEnable = 0;
|
||||
r.spriteEnable = 0;
|
||||
r.spriteWindowEnable = 0;
|
||||
r.screenTwoWindowInvert = 0;
|
||||
r.screenTwoWindowEnable = 0;
|
||||
r.backColor = 0;
|
||||
r.lineCompare = 0xff;
|
||||
r.spriteBase = 0;
|
||||
r.spriteFirst = 0;
|
||||
r.spriteCount = 0;
|
||||
r.screenTwoMapBase = 0;
|
||||
r.screenOneMapBase = 0;
|
||||
r.screenTwoMapBase = 0;
|
||||
r.screenTwoWindowX0 = 0;
|
||||
r.screenTwoWindowY0 = 0;
|
||||
r.screenTwoWindowX1 = 0;
|
||||
|
@ -123,15 +158,19 @@ auto PPU::power() -> void {
|
|||
r.scrollOneY = 0;
|
||||
r.scrollTwoX = 0;
|
||||
r.scrollTwoY = 0;
|
||||
r.control = 0;
|
||||
r.iconAux3 = 0;
|
||||
r.iconAux2 = 0;
|
||||
r.iconAux1 = 0;
|
||||
r.iconHorizontal = 0;
|
||||
r.iconVertical = 0;
|
||||
r.lcdEnable = 1;
|
||||
r.lcdContrast = 0;
|
||||
r.lcdUnknown = 0;
|
||||
r.iconSleep = 0;
|
||||
r.iconVertical = 0;
|
||||
r.iconHorizontal = 0;
|
||||
r.iconAux1 = 0;
|
||||
r.iconAux2 = 0;
|
||||
r.iconAux3 = 0;
|
||||
r.vtotal = 158;
|
||||
r.vblank = 155;
|
||||
for(auto& color : r.pool) color = 0;
|
||||
for(auto& p : r.palette) for(auto& color : p.color) color = 0;
|
||||
r.htimerEnable = 0;
|
||||
r.htimerRepeat = 0;
|
||||
r.vtimerEnable = 0;
|
||||
|
@ -140,8 +179,6 @@ auto PPU::power() -> void {
|
|||
r.vtimerFrequency = 0;
|
||||
r.htimerCounter = 0;
|
||||
r.vtimerCounter = 0;
|
||||
for(auto& color : r.pool) color = 0;
|
||||
for(auto& p : r.palette) for(auto& color : p.color) color = 0;
|
||||
|
||||
video.power();
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ struct PPU : Thread, IO {
|
|||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto latchRegisters() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//io.cpp
|
||||
|
@ -34,12 +35,7 @@ struct PPU : Thread, IO {
|
|||
|
||||
//state
|
||||
uint12 output[224 * 144];
|
||||
uint32 oam[128];
|
||||
|
||||
struct Status {
|
||||
uint vclk;
|
||||
uint hclk;
|
||||
} status;
|
||||
uint32 oam[2][128];
|
||||
|
||||
struct Sprite {
|
||||
uint8 x;
|
||||
|
@ -59,14 +55,50 @@ struct PPU : Thread, IO {
|
|||
uint12 color;
|
||||
} pixel;
|
||||
|
||||
struct Registers {
|
||||
//$0000 DISP_CTRL
|
||||
struct State {
|
||||
bool field;
|
||||
uint vclk;
|
||||
uint hclk;
|
||||
} s;
|
||||
|
||||
struct Latches {
|
||||
uint8 backColor;
|
||||
|
||||
uint1 screenOneEnable;
|
||||
uint4 screenOneMapBase;
|
||||
uint8 scrollOneX;
|
||||
uint8 scrollOneY;
|
||||
|
||||
uint1 screenTwoEnable;
|
||||
uint4 screenTwoMapBase;
|
||||
uint8 scrollTwoX;
|
||||
uint8 scrollTwoY;
|
||||
uint1 screenTwoWindowEnable;
|
||||
uint1 screenTwoWindowInvert;
|
||||
uint1 spriteWindowEnable;
|
||||
uint8 screenTwoWindowX0;
|
||||
uint8 screenTwoWindowY0;
|
||||
uint8 screenTwoWindowX1;
|
||||
uint8 screenTwoWindowY1;
|
||||
|
||||
uint1 spriteEnable;
|
||||
uint1 screenTwoEnable;
|
||||
uint6 spriteBase;
|
||||
uint7 spriteFirst;
|
||||
uint8 spriteCount;
|
||||
uint1 spriteWindowEnable;
|
||||
uint8 spriteWindowX0;
|
||||
uint8 spriteWindowY0;
|
||||
uint8 spriteWindowX1;
|
||||
uint8 spriteWindowY1;
|
||||
} l;
|
||||
|
||||
struct Registers {
|
||||
//$0000 DISP_CTRL
|
||||
uint1 screenOneEnable;
|
||||
uint1 screenTwoEnable;
|
||||
uint1 spriteEnable;
|
||||
uint1 spriteWindowEnable;
|
||||
uint1 screenTwoWindowInvert;
|
||||
uint1 screenTwoWindowEnable;
|
||||
|
||||
//$0001 BACK_COLOR
|
||||
uint8 backColor;
|
||||
|
@ -84,8 +116,8 @@ struct PPU : Thread, IO {
|
|||
uint8 spriteCount; //0 - 128
|
||||
|
||||
//$0007 MAP_BASE
|
||||
uint4 screenTwoMapBase;
|
||||
uint4 screenOneMapBase;
|
||||
uint4 screenTwoMapBase;
|
||||
|
||||
//$0008 SCR2_WIN_X0
|
||||
uint8 screenTwoWindowX0;
|
||||
|
@ -124,15 +156,17 @@ struct PPU : Thread, IO {
|
|||
uint8 scrollTwoY;
|
||||
|
||||
//$0014 LCD_CTRL
|
||||
uint8 control;
|
||||
uint1 lcdEnable;
|
||||
uint1 lcdContrast; //WSC only
|
||||
uint6 lcdUnknown;
|
||||
|
||||
//$0015 LCD_ICON
|
||||
uint1 iconAux3;
|
||||
uint1 iconAux2;
|
||||
uint1 iconAux1;
|
||||
uint1 iconHorizontal;
|
||||
uint1 iconVertical;
|
||||
uint1 iconSleep;
|
||||
uint1 iconVertical;
|
||||
uint1 iconHorizontal;
|
||||
uint1 iconAux1;
|
||||
uint1 iconAux2;
|
||||
uint1 iconAux3;
|
||||
|
||||
//$0016 LCD_VTOTAL
|
||||
uint8 vtotal;
|
||||
|
|
|
@ -22,17 +22,17 @@ auto PPU::renderColorPalette(uint4 palette, uint4 index) -> uint12 {
|
|||
}
|
||||
|
||||
auto PPU::renderColorBack() -> void {
|
||||
uint12 color = iram.read(0xfe00 + (r.backColor << 1), Word);
|
||||
uint12 color = iram.read(0xfe00 + (l.backColor << 1), Word);
|
||||
pixel = {Pixel::Source::Back, color};
|
||||
}
|
||||
|
||||
auto PPU::renderColorScreenOne() -> void {
|
||||
if(!r.screenOneEnable) return;
|
||||
if(!l.screenOneEnable) return;
|
||||
|
||||
uint8 scrollY = status.vclk + r.scrollOneY;
|
||||
uint8 scrollX = status.hclk + r.scrollOneX;
|
||||
uint8 scrollY = s.vclk + l.scrollOneY;
|
||||
uint8 scrollX = s.hclk + l.scrollOneX;
|
||||
|
||||
uint16 tilemapOffset = r.screenOneMapBase << 11;
|
||||
uint16 tilemapOffset = l.screenOneMapBase << 11;
|
||||
tilemapOffset += (scrollY >> 3) << 6;
|
||||
tilemapOffset += (scrollX >> 3) << 1;
|
||||
|
||||
|
@ -47,17 +47,17 @@ auto PPU::renderColorScreenOne() -> void {
|
|||
}
|
||||
|
||||
auto PPU::renderColorScreenTwo() -> void {
|
||||
if(!r.screenTwoEnable) return;
|
||||
if(!l.screenTwoEnable) return;
|
||||
|
||||
bool windowInside = status.vclk >= r.screenTwoWindowY0 && status.vclk <= r.screenTwoWindowY1
|
||||
&& status.hclk >= r.screenTwoWindowX0 && status.hclk <= r.screenTwoWindowX1;
|
||||
windowInside ^= r.screenTwoWindowInvert;
|
||||
if(r.screenTwoWindowEnable && !windowInside) return;
|
||||
bool windowInside = s.vclk >= l.screenTwoWindowY0 && s.vclk <= l.screenTwoWindowY1
|
||||
&& s.hclk >= l.screenTwoWindowX0 && s.hclk <= l.screenTwoWindowX1;
|
||||
windowInside ^= l.screenTwoWindowInvert;
|
||||
if(l.screenTwoWindowEnable && !windowInside) return;
|
||||
|
||||
uint8 scrollY = status.vclk + r.scrollTwoY;
|
||||
uint8 scrollX = status.hclk + r.scrollTwoX;
|
||||
uint8 scrollY = s.vclk + l.scrollTwoY;
|
||||
uint8 scrollX = s.hclk + l.scrollTwoX;
|
||||
|
||||
uint16 tilemapOffset = r.screenTwoMapBase << 11;
|
||||
uint16 tilemapOffset = l.screenTwoMapBase << 11;
|
||||
tilemapOffset += (scrollY >> 3) << 6;
|
||||
tilemapOffset += (scrollX >> 3) << 1;
|
||||
|
||||
|
@ -72,17 +72,16 @@ auto PPU::renderColorScreenTwo() -> void {
|
|||
}
|
||||
|
||||
auto PPU::renderColorSprite() -> void {
|
||||
if(!r.spriteEnable) return;
|
||||
if(!l.spriteEnable) return;
|
||||
|
||||
bool windowInside = status.hclk >= r.spriteWindowX0 && status.hclk <= r.spriteWindowX1;
|
||||
bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1;
|
||||
for(auto& sprite : sprites) {
|
||||
if(r.spriteWindowEnable && sprite.window && !windowInside) continue;
|
||||
if(status.hclk < sprite.x) continue;
|
||||
if(status.hclk > sprite.x + 7) continue;
|
||||
if(l.spriteWindowEnable && sprite.window == windowInside) continue;
|
||||
if((uint8)(s.hclk - sprite.x) > 7) continue;
|
||||
|
||||
uint16 tileOffset = 0x4000 + (sprite.tile << 5);
|
||||
uint3 tileY = (uint8)(status.vclk - sprite.y) ^ (sprite.vflip ? 7 : 0);
|
||||
uint3 tileX = (uint8)(status.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
|
||||
uint3 tileY = (uint8)(s.vclk - sprite.y) ^ (sprite.vflip ? 7 : 0);
|
||||
uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
|
||||
uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX);
|
||||
if(tileColor == 0) continue;
|
||||
if(!sprite.priority && pixel.source == Pixel::Source::ScreenTwo) continue;
|
||||
|
|
|
@ -22,17 +22,17 @@ auto PPU::renderMonoPalette(uint4 palette, uint2 index) -> uint12 {
|
|||
}
|
||||
|
||||
auto PPU::renderMonoBack() -> void {
|
||||
uint4 poolColor = 15 - r.pool[r.backColor.bits(0,2)];
|
||||
uint4 poolColor = 15 - r.pool[l.backColor.bits(0,2)];
|
||||
pixel = {Pixel::Source::Back, poolColor << 0 | poolColor << 4 | poolColor << 8};
|
||||
}
|
||||
|
||||
auto PPU::renderMonoScreenOne() -> void {
|
||||
if(!r.screenOneEnable) return;
|
||||
if(!l.screenOneEnable) return;
|
||||
|
||||
uint8 scrollY = status.vclk + r.scrollOneY;
|
||||
uint8 scrollX = status.hclk + r.scrollOneX;
|
||||
uint8 scrollY = s.vclk + l.scrollOneY;
|
||||
uint8 scrollX = s.hclk + l.scrollOneX;
|
||||
|
||||
uint14 tilemapOffset = r.screenOneMapBase.bits(0,2) << 11;
|
||||
uint14 tilemapOffset = l.screenOneMapBase.bits(0,2) << 11;
|
||||
tilemapOffset += (scrollY >> 3) << 6;
|
||||
tilemapOffset += (scrollX >> 3) << 1;
|
||||
|
||||
|
@ -47,17 +47,17 @@ auto PPU::renderMonoScreenOne() -> void {
|
|||
}
|
||||
|
||||
auto PPU::renderMonoScreenTwo() -> void {
|
||||
if(!r.screenTwoEnable) return;
|
||||
if(!l.screenTwoEnable) return;
|
||||
|
||||
bool windowInside = status.vclk >= r.screenTwoWindowY0 && status.vclk <= r.screenTwoWindowY1
|
||||
&& status.hclk >= r.screenTwoWindowX0 && status.hclk <= r.screenTwoWindowX1;
|
||||
windowInside ^= r.screenTwoWindowInvert;
|
||||
if(r.screenTwoWindowEnable && !windowInside) return;
|
||||
bool windowInside = s.vclk >= l.screenTwoWindowY0 && s.vclk <= l.screenTwoWindowY1
|
||||
&& s.hclk >= l.screenTwoWindowX0 && s.hclk <= l.screenTwoWindowX1;
|
||||
windowInside ^= l.screenTwoWindowInvert;
|
||||
if(l.screenTwoWindowEnable && !windowInside) return;
|
||||
|
||||
uint8 scrollY = status.vclk + r.scrollTwoY;
|
||||
uint8 scrollX = status.hclk + r.scrollTwoX;
|
||||
uint8 scrollY = s.vclk + l.scrollTwoY;
|
||||
uint8 scrollX = s.hclk + l.scrollTwoX;
|
||||
|
||||
uint14 tilemapOffset = r.screenTwoMapBase.bits(0,2) << 11;
|
||||
uint14 tilemapOffset = l.screenTwoMapBase.bits(0,2) << 11;
|
||||
tilemapOffset += (scrollY >> 3) << 6;
|
||||
tilemapOffset += (scrollX >> 3) << 1;
|
||||
|
||||
|
@ -72,17 +72,16 @@ auto PPU::renderMonoScreenTwo() -> void {
|
|||
}
|
||||
|
||||
auto PPU::renderMonoSprite() -> void {
|
||||
if(!r.spriteEnable) return;
|
||||
if(!l.spriteEnable) return;
|
||||
|
||||
bool windowInside = status.hclk >= r.spriteWindowX0 && status.hclk <= r.spriteWindowX1;
|
||||
bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1;
|
||||
for(auto& sprite : sprites) {
|
||||
if(r.spriteWindowEnable && sprite.window && !windowInside) continue;
|
||||
if(status.hclk < sprite.x) continue;
|
||||
if(status.hclk > sprite.x + 7) continue;
|
||||
if(l.spriteWindowEnable && sprite.window == windowInside) continue;
|
||||
if((uint8)(s.hclk - sprite.x) > 7) continue;
|
||||
|
||||
uint14 tileOffset = 0x2000 + (sprite.tile << 4);
|
||||
uint3 tileY = (uint8)(status.vclk - sprite.y) ^ (sprite.vflip ? 7 : 0);
|
||||
uint3 tileX = (uint8)(status.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
|
||||
uint3 tileY = (uint8)(s.vclk - sprite.y) ^ (sprite.vflip ? 7 : 0);
|
||||
uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
|
||||
uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX);
|
||||
if(sprite.palette.bit(2) && tileColor == 0) continue;
|
||||
if(!sprite.priority && pixel.source == Pixel::Source::ScreenTwo) continue;
|
||||
|
|
|
@ -1,34 +1,32 @@
|
|||
auto PPU::renderSpriteFetch() -> void {
|
||||
uint16 spriteBase = r.spriteBase.bits(0, 4 + system.depth()) << 9;
|
||||
uint16 spriteBase = l.spriteBase.bits(0, 4 + system.depth()) << 9;
|
||||
for(auto spriteIndex : range(128)) {
|
||||
oam[spriteIndex] = iram.read(spriteBase + (spriteIndex << 2), Long);
|
||||
oam[s.field][spriteIndex] = iram.read(spriteBase + (spriteIndex << 2), Long);
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::renderSpriteDecode() -> void {
|
||||
sprites.reset();
|
||||
sprites.reserve(32);
|
||||
if(!r.spriteEnable) return;
|
||||
if(!l.spriteEnable) return;
|
||||
|
||||
uint offset = 0;
|
||||
bool windowInside = status.vclk >= r.spriteWindowY0 && status.vclk <= r.spriteWindowY1;
|
||||
uint16 spriteBase = r.spriteBase.bits(0, 4 + system.depth()) << 9;
|
||||
uint7 spriteIndex = r.spriteFirst;
|
||||
uint8 spriteCount = min(128, (uint)r.spriteCount);
|
||||
bool windowInside = s.vclk >= l.spriteWindowY0 && s.vclk <= l.spriteWindowY1;
|
||||
uint7 spriteIndex = l.spriteFirst;
|
||||
uint8 spriteCount = min(128, (uint)l.spriteCount);
|
||||
while(spriteCount--) {
|
||||
uint32 attributes = oam[spriteIndex++];
|
||||
uint32 attributes = oam[s.field][spriteIndex++];
|
||||
|
||||
Sprite sprite;
|
||||
sprite.x = attributes.bits(24,31);
|
||||
if(sprite.x > 224) continue;
|
||||
if(sprite.x > 224 && sprite.x < 249) continue;
|
||||
sprite.y = attributes.bits(16,23);
|
||||
if(status.vclk < sprite.y) continue;
|
||||
if(status.vclk > sprite.y + 7) continue;
|
||||
if((uint8)(s.vclk - sprite.y) > 7) continue;
|
||||
sprite.vflip = attributes.bit(15);
|
||||
sprite.hflip = attributes.bit(14);
|
||||
sprite.priority = attributes.bit(13);
|
||||
sprite.window = attributes.bit(12);
|
||||
if(r.spriteWindowEnable && sprite.window && !windowInside) continue;
|
||||
if(l.spriteWindowEnable && sprite.window == windowInside) continue;
|
||||
sprite.palette = 8 + attributes.bits(9,11);
|
||||
sprite.tile = attributes.bits(0,8);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Video::Video() {
|
|||
output = new uint32[224 * 224];
|
||||
paletteLiteral = new uint32[1 << 12];
|
||||
paletteStandard = new uint32[1 << 12];
|
||||
paletteEmulation = new uint32[1 << 12];
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
|
@ -12,18 +13,30 @@ auto Video::power() -> void {
|
|||
for(uint12 color : range(1 << 12)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint B = color.bits(0, 3);
|
||||
uint G = color.bits(4, 7);
|
||||
uint R = color.bits(8,11);
|
||||
uint b = color.bits(0, 3);
|
||||
uint g = color.bits(4, 7);
|
||||
uint r = color.bits(8,11);
|
||||
|
||||
R = image::normalize(R, 4, 16);
|
||||
G = image::normalize(G, 4, 16);
|
||||
B = image::normalize(B, 4, 16);
|
||||
uint R = image::normalize(r, 4, 16);
|
||||
uint G = image::normalize(g, 4, 16);
|
||||
uint B = image::normalize(b, 4, 16);
|
||||
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||
|
||||
//todo: this uses the Game Boy Advance color emulation algorithm
|
||||
//need to determine proper color emulation for WonderSwan systems
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(480, R), 9, 16);
|
||||
G = image::normalize(min(480, G), 9, 16);
|
||||
B = image::normalize(min(480, B), 9, 16);
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
if(system.orientation() == 0) {
|
||||
for(uint y = 0; y < 224; y++) {
|
||||
auto target = output() + y * 224;
|
||||
|
@ -33,8 +46,13 @@ auto Video::refresh() -> void {
|
|||
}
|
||||
auto source = ppu.output + (y - 40) * 224;
|
||||
for(uint x = 0; x < 224; x++) {
|
||||
auto color = paletteStandard[*source++];
|
||||
*target++ = color;
|
||||
auto color = palette[*source++];
|
||||
if(settings.blurEmulation) {
|
||||
auto a = color, b = *target;
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
} else {
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +65,13 @@ auto Video::refresh() -> void {
|
|||
target += 40;
|
||||
for(uint x = 0; x < 144; x++) {
|
||||
auto source = ppu.output + x * 224 + (223 - y);
|
||||
auto color = paletteStandard[*source];
|
||||
*target++ = color;
|
||||
auto color = palette[*source];
|
||||
if(settings.blurEmulation) {
|
||||
auto a = color, b = *target;
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
} else {
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ private:
|
|||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
||||
|
|
|
@ -21,7 +21,6 @@ auto System::term() -> void {
|
|||
|
||||
auto System::load(Model model) -> void {
|
||||
_model = model;
|
||||
_orientation = 0;
|
||||
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
@ -37,6 +36,7 @@ auto System::load(Model model) -> void {
|
|||
|
||||
cartridge.load();
|
||||
_loaded = true;
|
||||
_orientation = cartridge.information.orientation;
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
|
@ -85,6 +85,13 @@ auto System::run() -> void {
|
|||
keypad.start = interface->inputPoll(_orientation, 0, 10);
|
||||
keypad.rotate = interface->inputPoll(_orientation, 0, 11);
|
||||
|
||||
if(keypad.y1 || keypad.y2 || keypad.y3 || keypad.y4
|
||||
|| keypad.x1 || keypad.x2 || keypad.x3 || keypad.x4
|
||||
|| keypad.b || keypad.a || keypad.start
|
||||
) {
|
||||
cpu.raise(CPU::Interrupt::Input);
|
||||
}
|
||||
|
||||
if(!rotate && keypad.rotate) {
|
||||
_orientation = !_orientation;
|
||||
}
|
||||
|
|
|
@ -6,26 +6,20 @@ auto Icarus::wonderSwanColorManifest(string location) -> string {
|
|||
|
||||
auto Icarus::wonderSwanColorManifest(vector<uint8_t>& buffer, string location) -> string {
|
||||
string manifest;
|
||||
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
|
||||
if(settings["icarus/UseDatabase"].boolean() && !manifest) {
|
||||
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
for(auto node : database.wonderSwanColor) {
|
||||
if(node["sha256"].text() == digest) {
|
||||
manifest.append(node.text(), "\n sha256: ", digest, "\n");
|
||||
manifest.append(node.text(), "\n sha256: ", digest, "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(settings["icarus/UseHeuristics"].boolean() && !manifest) {
|
||||
WonderSwanCartridge cartridge{buffer.data(), buffer.size()};
|
||||
if(manifest = cartridge.manifest) {
|
||||
manifest.append("\n");
|
||||
manifest.append("information\n");
|
||||
manifest.append(" title: ", prefixname(location), "\n");
|
||||
manifest.append(" sha256: ", digest, "\n");
|
||||
manifest.append(" note: ", "heuristically generated by icarus\n");
|
||||
}
|
||||
WonderSwanCartridge cartridge{location, buffer.data(), buffer.size()};
|
||||
manifest = cartridge.manifest;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
|
|
|
@ -6,26 +6,20 @@ auto Icarus::wonderSwanManifest(string location) -> string {
|
|||
|
||||
auto Icarus::wonderSwanManifest(vector<uint8_t>& buffer, string location) -> string {
|
||||
string manifest;
|
||||
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
|
||||
if(settings["icarus/UseDatabase"].boolean() && !manifest) {
|
||||
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
for(auto node : database.wonderSwan) {
|
||||
if(node["sha256"].text() == digest) {
|
||||
manifest.append(node.text(), "\n sha256: ", digest, "\n");
|
||||
manifest.append(node.text(), "\n sha256: ", digest, "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(settings["icarus/UseHeuristics"].boolean() && !manifest) {
|
||||
WonderSwanCartridge cartridge{buffer.data(), buffer.size()};
|
||||
if(manifest = cartridge.manifest) {
|
||||
manifest.append("\n");
|
||||
manifest.append("information\n");
|
||||
manifest.append(" title: ", prefixname(location), "\n");
|
||||
manifest.append(" sha256: ", digest, "\n");
|
||||
manifest.append(" note: ", "heuristically generated by icarus\n");
|
||||
}
|
||||
WonderSwanCartridge cartridge{location, buffer.data(), buffer.size()};
|
||||
manifest = cartridge.manifest;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
struct WonderSwanCartridge {
|
||||
WonderSwanCartridge(uint8_t* data, uint size);
|
||||
WonderSwanCartridge(string location, uint8_t* data, uint size);
|
||||
|
||||
string manifest;
|
||||
|
||||
|
@ -9,10 +9,11 @@ struct WonderSwanCartridge {
|
|||
|
||||
string ramType;
|
||||
uint ramSize;
|
||||
bool orientation; //0 = horizontal; 1 = vertical
|
||||
} information;
|
||||
};
|
||||
|
||||
WonderSwanCartridge::WonderSwanCartridge(uint8_t* data, uint size) {
|
||||
WonderSwanCartridge::WonderSwanCartridge(string location, uint8_t* data, uint size) {
|
||||
if(size < 0x10000) return;
|
||||
|
||||
auto metadata = data + size - 16;
|
||||
|
@ -31,8 +32,17 @@ WonderSwanCartridge::WonderSwanCartridge(uint8_t* data, uint size) {
|
|||
case 0x50: information.ramType = "eeprom"; information.ramSize = 1024; break;
|
||||
}
|
||||
|
||||
information.orientation = metadata[12] & 1;
|
||||
|
||||
manifest.append("board\n");
|
||||
manifest.append(" rom name=program.rom size=0x", hex(size), "\n");
|
||||
if(information.ramType && information.ramSize)
|
||||
manifest.append(" ram name=save.ram type=", information.ramType, " size=0x", hex(information.ramSize), "\n");
|
||||
manifest.append("\n");
|
||||
manifest.append("information\n");
|
||||
manifest.append(" title: ", prefixname(location), "\n");
|
||||
manifest.append(" orientation: ", !information.orientation ? "horizontal" : "vertical", "\n");
|
||||
manifest.append(" sha256: ", Hash::SHA256(data, size).digest(), "\n");
|
||||
manifest.append("\n");
|
||||
manifest.append("note: heuristically generated by icarus\n");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue