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:
Tim Allen 2016-03-19 18:35:25 +11:00
parent a7f7985581
commit d3413db04a
23 changed files with 361 additions and 309 deletions

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@ struct Cartridge : IO {
struct Information {
string manifest;
string title;
bool orientation; //0 = horizontal; 1 = vertical
string sha256;
} information;
};

View File

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

View File

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

View File

@ -56,6 +56,8 @@ private:
};
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
};
extern Interface* interface;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ private:
unique_pointer<uint32[]> output;
unique_pointer<uint32[]> paletteLiteral;
unique_pointer<uint32[]> paletteStandard;
unique_pointer<uint32[]> paletteEmulation;
};
extern Video video;

View File

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

View File

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

View File

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

View File

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