Update to bsnes v107r5 beta release.

byuu says:

  - bsnes: allow video filtering even when the emulator is paused
  - bsnes: improve overscan masking, especially with HD mode 7
  - bsnes: improve snow support, especially with HD mode 7
  - bsnes: replace real-time cheat code replace with per-frame replace
    (ala Pro Action Replay, Snes9X)
  - bsnes: treat the latter step() half of CPU::read() calls as idle
    cycles
  - bsnes: templatize step() where possible (not always practical)
  - bsnes: removed Natural<T> templates from key portions of the fast
    PPU renderer
  - bsnes: dethreaded peripherals (controllers and expansion port
    devices)
  - bsnes: above optimizations result in a ~20-25% speedup over v107.4
    with no accuracy loss

Note that light guns aren't going to work for now, I'll have to fix them
before we can release v108.
This commit is contained in:
Tim Allen 2019-07-10 17:33:13 +10:00
parent d87a0f633d
commit a03d91882c
51 changed files with 716 additions and 608 deletions

View File

@ -4,31 +4,38 @@ build := performance
openmp := true openmp := true
flags += -I. -I.. flags += -I. -I..
# in order for this to work, obj/lzma.o must be omitted or bsnes will hang on startup.
# further, only the X-Video driver works reliably. OpenGL 3.2, OpenGL 2.0, and XShm crash bsnes.
ifeq ($(profile),true)
flags += -pg
options += -pg
endif
nall.path := ../nall nall.path := ../nall
include $(nall.path)/GNUmakefile include $(nall.path)/GNUmakefile
ifeq ($(platform),windows) ifeq ($(platform),windows)
ifeq ($(binary),application) ifeq ($(binary),application)
link += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 options += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
link += -Wl,-enable-auto-import options += -Wl,-enable-auto-import
link += -Wl,-enable-runtime-pseudo-reloc options += -Wl,-enable-runtime-pseudo-reloc
else ifeq ($(binary),library) else ifeq ($(binary),library)
link += -shared options += -shared
endif endif
else ifeq ($(platform),macos) else ifeq ($(platform),macos)
ifeq ($(binary),application) ifeq ($(binary),application)
else ifeq ($(binary),library) else ifeq ($(binary),library)
flags += -fPIC flags += -fPIC
link += -dynamiclib options += -dynamiclib
endif endif
else ifneq ($(filter $(platform),linux bsd),) else ifneq ($(filter $(platform),linux bsd),)
ifeq ($(binary),application) ifeq ($(binary),application)
flags += -march=native flags += -march=native
link += -Wl,-export-dynamic options += -Wl,-export-dynamic
link += -lX11 -lXext options += -lX11 -lXext
else ifeq ($(binary),library) else ifeq ($(binary),library)
flags += -fPIC flags += -fPIC
link += -shared options += -shared
endif endif
else else
$(error "unsupported platform") $(error "unsupported platform")

View File

@ -4,9 +4,19 @@ namespace Emulator {
struct Cheat { struct Cheat {
struct Code { struct Code {
uint addr; auto operator==(const Code& code) const -> bool {
if(address != code.address) return false;
if(data != code.data) return false;
if((bool)compare != (bool)code.compare) return false;
if(compare && code.compare && compare() != code.compare()) return false;
return true;
}
uint address;
uint data; uint data;
maybe<uint> comp; maybe<uint> compare;
bool enable;
uint restore;
}; };
explicit operator bool() const { explicit operator bool() const {
@ -17,8 +27,8 @@ struct Cheat {
codes.reset(); codes.reset();
} }
auto append(uint addr, uint data, maybe<uint> comp = {}) -> void { auto append(uint address, uint data, maybe<uint> compare = {}) -> void {
codes.append({addr, data, comp}); codes.append({address, data, compare});
} }
auto assign(const vector<string>& list) -> void { auto assign(const vector<string>& list) -> void {
@ -32,16 +42,15 @@ struct Cheat {
} }
} }
auto find(uint addr, uint comp) -> maybe<uint> { auto find(uint address, uint compare) -> maybe<uint> {
for(auto& code : codes) { for(auto& code : codes) {
if(code.addr == addr && (!code.comp || code.comp() == comp)) { if(code.address == address && (!code.compare || code.compare() == compare)) {
return code.data; return code.data;
} }
} }
return nothing; return nothing;
} }
private:
vector<Code> codes; vector<Code> codes;
}; };

View File

@ -31,7 +31,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "bsnes"; static const string Name = "bsnes";
static const string Version = "107.4"; static const string Version = "107.5";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org/"; static const string Website = "https://byuu.org/";

View File

@ -16,7 +16,7 @@ struct Platform {
virtual auto path(uint id) -> string { return ""; } virtual auto path(uint id) -> string { return ""; }
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; } virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; } virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; }
virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height) -> void {} virtual auto videoFrame(const uint16_t* data, uint pitch, uint width, uint height, uint scale) -> void {}
virtual auto audioFrame(const double* samples, uint channels) -> void {} virtual auto audioFrame(const double* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; } virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {} virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}

View File

@ -154,7 +154,7 @@ auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void
} }
} }
platform->videoFrame((const uint16*)output, width * sizeof(uint32), width, height); //platform->videoFrame((const uint16*)output, width * sizeof(uint32), width, height);
} }
} }

View File

@ -2,6 +2,12 @@
#include <emulator/emulator.hpp> #include <emulator/emulator.hpp>
namespace Filter {
using Size = auto (*)(uint& width, uint& height) -> void;
using Render = auto (*)(uint32_t* palette, uint32_t* output, uint outpitch,
const uint16_t* input, uint pitch, uint width, uint height) -> void;
}
namespace Filter::None { namespace Filter::None {
auto size(uint& width, uint& height) -> void; auto size(uint& width, uint& height) -> void;
auto render( auto render(

View File

@ -3,15 +3,7 @@ processors += wdc65816 spc700 arm7tdmi gsu hg51b upd96050
objects += sfc-interface sfc-system sfc-controller objects += sfc-interface sfc-system sfc-controller
objects += sfc-cartridge sfc-memory objects += sfc-cartridge sfc-memory
objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu sfc-ppu-fast objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu sfc-ppu-fast
objects += sfc-expansion sfc-satellaview sfc-21fx objects += sfc-expansion sfc-coprocessor sfc-slot
objects += sfc-icd sfc-mcc sfc-dip sfc-event
objects += sfc-sa1 sfc-superfx
objects += sfc-armdsp sfc-hitachidsp sfc-necdsp
objects += sfc-epsonrtc sfc-sharprtc
objects += sfc-spc7110 sfc-sdd1
objects += sfc-obc1 sfc-msu1
objects += sfc-cx4 sfc-dsp1 sfc-dsp2 sfc-dsp4 sfc-st0010
objects += sfc-bsmemory sfc-sufamiturbo
obj/sfc-interface.o: sfc/interface/interface.cpp obj/sfc-interface.o: sfc/interface/interface.cpp
obj/sfc-system.o: sfc/system/system.cpp obj/sfc-system.o: sfc/system/system.cpp
@ -26,35 +18,5 @@ obj/sfc-ppu.o: sfc/ppu/ppu.cpp
obj/sfc-ppu-fast.o: sfc/ppu-fast/ppu.cpp obj/sfc-ppu-fast.o: sfc/ppu-fast/ppu.cpp
obj/sfc-expansion.o: sfc/expansion/expansion.cpp obj/sfc-expansion.o: sfc/expansion/expansion.cpp
obj/sfc-satellaview.o: sfc/expansion/satellaview/satellaview.cpp obj/sfc-coprocessor.o: sfc/coprocessor/coprocessor.cpp
obj/sfc-21fx.o: sfc/expansion/21fx/21fx.cpp obj/sfc-slot.o: sfc/slot/slot.cpp
obj/sfc-icd.o: sfc/coprocessor/icd/icd.cpp
obj/sfc-mcc.o: sfc/coprocessor/mcc/mcc.cpp
obj/sfc-dip.o: sfc/coprocessor/dip/dip.cpp
obj/sfc-event.o: sfc/coprocessor/event/event.cpp
obj/sfc-sa1.o: sfc/coprocessor/sa1/sa1.cpp
obj/sfc-superfx.o: sfc/coprocessor/superfx/superfx.cpp
obj/sfc-armdsp.o: sfc/coprocessor/armdsp/armdsp.cpp
obj/sfc-hitachidsp.o: sfc/coprocessor/hitachidsp/hitachidsp.cpp
obj/sfc-necdsp.o: sfc/coprocessor/necdsp/necdsp.cpp
obj/sfc-epsonrtc.o: sfc/coprocessor/epsonrtc/epsonrtc.cpp
obj/sfc-sharprtc.o: sfc/coprocessor/sharprtc/sharprtc.cpp
obj/sfc-spc7110.o: sfc/coprocessor/spc7110/spc7110.cpp
obj/sfc-sdd1.o: sfc/coprocessor/sdd1/sdd1.cpp
obj/sfc-obc1.o: sfc/coprocessor/obc1/obc1.cpp
obj/sfc-msu1.o: sfc/coprocessor/msu1/msu1.cpp
obj/sfc-cx4.o: sfc/coprocessor/cx4/cx4.cpp
obj/sfc-dsp1.o: sfc/coprocessor/dsp1/dsp1.cpp
obj/sfc-dsp2.o: sfc/coprocessor/dsp2/dsp2.cpp
obj/sfc-dsp4.o: sfc/coprocessor/dsp4/dsp4.cpp
obj/sfc-st0010.o: sfc/coprocessor/st0010/st0010.cpp
obj/sfc-bsmemory.o: sfc/slot/bsmemory/bsmemory.cpp
obj/sfc-sufamiturbo.o: sfc/slot/sufamiturbo/sufamiturbo.cpp

View File

@ -11,24 +11,9 @@ ControllerPort controllerPort2;
#include "justifier/justifier.cpp" #include "justifier/justifier.cpp"
Controller::Controller(uint port) : port(port) { Controller::Controller(uint port) : port(port) {
if(!handle()) create(Controller::Enter, 1);
} }
Controller::~Controller() { Controller::~Controller() {
scheduler.remove(*this);
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(controllerPort1.device->active()) controllerPort1.device->main();
if(controllerPort2.device->active()) controllerPort2.device->main();
}
}
auto Controller::main() -> void {
step(1);
synchronize(cpu);
} }
auto Controller::iobit() -> bool { auto Controller::iobit() -> bool {
@ -61,11 +46,6 @@ auto ControllerPort::connect(uint deviceID) -> void {
case ID::Device::Justifier: device = new Justifier(port, false); break; case ID::Device::Justifier: device = new Justifier(port, false); break;
case ID::Device::Justifiers: device = new Justifier(port, true); break; case ID::Device::Justifiers: device = new Justifier(port, true); break;
} }
cpu.peripherals.reset();
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
if(auto device = expansionPort.device) cpu.peripherals.append(device);
} }
auto ControllerPort::power(uint port) -> void { auto ControllerPort::power(uint port) -> void {

View File

@ -11,12 +11,10 @@
// 6: iobit $4201.d6 write; $4213.d6 read $4201.d7 write; $4213.d7 read // 6: iobit $4201.d6 write; $4213.d6 read $4201.d7 write; $4213.d7 read
// 7: gnd // 7: gnd
struct Controller : Thread { struct Controller {
Controller(uint port); Controller(uint port);
virtual ~Controller(); virtual ~Controller();
static auto Enter() -> void;
virtual auto main() -> void;
auto iobit() -> bool; auto iobit() -> bool;
auto iobit(bool data) -> void; auto iobit(bool data) -> void;
virtual auto data() -> uint2 { return 0; } virtual auto data() -> uint2 { return 0; }

View File

@ -3,7 +3,6 @@ Controller(port),
chained(chained), chained(chained),
device(!chained ? ID::Device::Justifier : ID::Device::Justifiers) device(!chained ? ID::Device::Justifier : ID::Device::Justifiers)
{ {
create(Controller::Enter, system.cpuFrequency());
latched = 0; latched = 0;
counter = 0; counter = 0;
active = 0; active = 0;
@ -37,6 +36,7 @@ Justifier::~Justifier() {
Emulator::video.removeSprite(player2.sprite); Emulator::video.removeSprite(player2.sprite);
} }
/*
auto Justifier::main() -> void { auto Justifier::main() -> void {
uint next = cpu.vcounter() * 1364 + cpu.hcounter(); uint next = cpu.vcounter() * 1364 + cpu.hcounter();
@ -78,6 +78,7 @@ auto Justifier::main() -> void {
step(2); step(2);
synchronize(cpu); synchronize(cpu);
} }
*/
auto Justifier::data() -> uint2 { auto Justifier::data() -> uint2 {
if(counter >= 32) return 1; if(counter >= 32) return 1;

View File

@ -6,7 +6,6 @@ struct Justifier : Controller {
Justifier(uint port, bool chained); Justifier(uint port, bool chained);
~Justifier(); ~Justifier();
auto main() -> void;
auto data() -> uint2; auto data() -> uint2;
auto latch(bool data) -> void; auto latch(bool data) -> void;

View File

@ -11,7 +11,6 @@
//Note that no commercial game ever utilizes a Super Scope in port 1. //Note that no commercial game ever utilizes a Super Scope in port 1.
SuperScope::SuperScope(uint port) : Controller(port) { SuperScope::SuperScope(uint port) : Controller(port) {
create(Controller::Enter, system.cpuFrequency());
sprite = Emulator::video.createSprite(32, 32); sprite = Emulator::video.createSprite(32, 32);
sprite->setPixels(Resource::Sprite::CrosshairGreen); sprite->setPixels(Resource::Sprite::CrosshairGreen);
@ -39,6 +38,7 @@ SuperScope::~SuperScope() {
Emulator::video.removeSprite(sprite); Emulator::video.removeSprite(sprite);
} }
/*
auto SuperScope::main() -> void { auto SuperScope::main() -> void {
uint next = cpu.vcounter() * 1364 + cpu.hcounter(); uint next = cpu.vcounter() * 1364 + cpu.hcounter();
@ -68,6 +68,7 @@ auto SuperScope::main() -> void {
step(2); step(2);
synchronize(cpu); synchronize(cpu);
} }
*/
auto SuperScope::data() -> uint2 { auto SuperScope::data() -> uint2 {
if(counter >= 8) return 1; if(counter >= 8) return 1;

View File

@ -8,7 +8,6 @@ struct SuperScope : Controller {
SuperScope(uint port); SuperScope(uint port);
~SuperScope(); ~SuperScope();
auto main() -> void;
auto data() -> uint2; auto data() -> uint2;
auto latch(bool data) -> void; auto latch(bool data) -> void;

View File

@ -0,0 +1,26 @@
#include <sfc/coprocessor/icd/icd.cpp>
#include <sfc/coprocessor/mcc/mcc.cpp>
#include <sfc/coprocessor/dip/dip.cpp>
#include <sfc/coprocessor/event/event.cpp>
#include <sfc/coprocessor/sa1/sa1.cpp>
#include <sfc/coprocessor/superfx/superfx.cpp>
#include <sfc/coprocessor/armdsp/armdsp.cpp>
#include <sfc/coprocessor/hitachidsp/hitachidsp.cpp>
#include <sfc/coprocessor/necdsp/necdsp.cpp>
#include <sfc/coprocessor/epsonrtc/epsonrtc.cpp>
#include <sfc/coprocessor/sharprtc/sharprtc.cpp>
#include <sfc/coprocessor/spc7110/spc7110.cpp>
#include <sfc/coprocessor/sdd1/sdd1.cpp>
#include <sfc/coprocessor/obc1/obc1.cpp>
#include <sfc/coprocessor/msu1/msu1.cpp>
#include <sfc/coprocessor/cx4/cx4.cpp>
#include <sfc/coprocessor/dsp1/dsp1.cpp>
#include <sfc/coprocessor/dsp2/dsp2.cpp>
#include <sfc/coprocessor/dsp4/dsp4.cpp>
#include <sfc/coprocessor/st0010/st0010.cpp>

View File

@ -30,12 +30,12 @@ auto CPU::main() -> void {
interrupt(); interrupt();
} else if(status.resetPending) { } else if(status.resetPending) {
status.resetPending = false; status.resetPending = false;
step(132); for(uint repeat : range(22)) step<6,0>(); //step(132);
r.vector = 0xfffc; r.vector = 0xfffc;
interrupt(); interrupt();
} else if(status.powerPending) { } else if(status.powerPending) {
status.powerPending = false; status.powerPending = false;
step(186); for(uint repeat : range(31)) step<6,0>(); //step(186);
r.pc.byte(0) = bus.read(0xfffc, r.mdr); r.pc.byte(0) = bus.read(0xfffc, r.mdr);
r.pc.byte(1) = bus.read(0xfffd, r.mdr); r.pc.byte(1) = bus.read(0xfffd, r.mdr);
} }

View File

@ -24,7 +24,6 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
auto idle() -> void override; auto idle() -> void override;
auto read(uint24 addr) -> uint8 override; auto read(uint24 addr) -> uint8 override;
auto write(uint24 addr, uint8 data) -> void override; auto write(uint24 addr, uint8 data) -> void override;
alwaysinline auto wait(uint24 addr) const -> uint;
auto readDisassembler(uint24 addr) -> uint8 override; auto readDisassembler(uint24 addr) -> uint8 override;
//io.cpp //io.cpp
@ -42,8 +41,9 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
inline auto dmaCounter() const -> uint; inline auto dmaCounter() const -> uint;
inline auto joypadCounter() const -> uint; inline auto joypadCounter() const -> uint;
auto step(uint clocks) -> void; alwaysinline auto stepOnce() -> void;
inline auto stepIdle(uint clocks) -> void; alwaysinline auto step(uint clocks) -> void;
template<uint Clocks, bool Synchronize> auto step() -> void;
auto scanline() -> void; auto scanline() -> void;
alwaysinline auto aluEdge() -> void; alwaysinline auto aluEdge() -> void;
@ -67,7 +67,6 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
uint8 wram[128 * 1024]; uint8 wram[128 * 1024];
vector<Thread*> coprocessors; vector<Thread*> coprocessors;
vector<Thread*> peripherals;
private: private:
uint version = 2; //allowed: 1, 2 uint version = 2; //allowed: 1, 2
@ -146,7 +145,7 @@ private:
uint9 vtime = 0x1ff; uint9 vtime = 0x1ff;
//$420d //$420d
uint romSpeed = 8; uint1 fastROM = 0;
//$4214-$4217 //$4214-$4217
uint16 rddiv; uint16 rddiv;
@ -167,7 +166,7 @@ private:
struct Channel { struct Channel {
//dma.cpp //dma.cpp
inline auto step(uint clocks) -> void; template<uint Clocks, bool Synchronize> inline auto step() -> void;
inline auto edge() -> void; inline auto edge() -> void;
inline auto validA(uint24 address) -> bool; inline auto validA(uint24 address) -> bool;

View File

@ -14,7 +14,7 @@ auto CPU::hdmaActive() -> bool {
} }
auto CPU::dmaRun() -> void { auto CPU::dmaRun() -> void {
step(8); step<8,0>();
dmaEdge(); dmaEdge();
for(auto& channel : channels) channel.dmaRun(); for(auto& channel : channels) channel.dmaRun();
status.irqLock = true; status.irqLock = true;
@ -25,13 +25,13 @@ auto CPU::hdmaReset() -> void {
} }
auto CPU::hdmaSetup() -> void { auto CPU::hdmaSetup() -> void {
step(8); step<8,0>();
for(auto& channel : channels) channel.hdmaSetup(); for(auto& channel : channels) channel.hdmaSetup();
status.irqLock = true; status.irqLock = true;
} }
auto CPU::hdmaRun() -> void { auto CPU::hdmaRun() -> void {
step(8); step<8,0>();
for(auto& channel : channels) channel.hdmaTransfer(); for(auto& channel : channels) channel.hdmaTransfer();
for(auto& channel : channels) channel.hdmaAdvance(); for(auto& channel : channels) channel.hdmaAdvance();
status.irqLock = true; status.irqLock = true;
@ -39,7 +39,8 @@ auto CPU::hdmaRun() -> void {
// //
auto CPU::Channel::step(uint clocks) -> void { return cpu.step(clocks); } template<uint Clocks, bool Synchronize>
auto CPU::Channel::step() -> void { return cpu.step<Clocks, Synchronize>(); }
auto CPU::Channel::edge() -> void { return cpu.dmaEdge(); } auto CPU::Channel::edge() -> void { return cpu.dmaEdge(); }
auto CPU::Channel::validA(uint24 address) -> bool { auto CPU::Channel::validA(uint24 address) -> bool {
@ -52,16 +53,16 @@ auto CPU::Channel::validA(uint24 address) -> bool {
} }
auto CPU::Channel::readA(uint24 address) -> uint8 { auto CPU::Channel::readA(uint24 address) -> uint8 {
step(4); step<4,1>();
cpu.r.mdr = validA(address) ? bus.read(address, cpu.r.mdr) : (uint8)0x00; cpu.r.mdr = validA(address) ? bus.read(address, cpu.r.mdr) : (uint8)0x00;
step(4); step<4,1>();
return cpu.r.mdr; return cpu.r.mdr;
} }
auto CPU::Channel::readB(uint8 address, bool valid) -> uint8 { auto CPU::Channel::readB(uint8 address, bool valid) -> uint8 {
step(4); step<4,1>();
cpu.r.mdr = valid ? bus.read(0x2100 | address, cpu.r.mdr) : (uint8)0x00; cpu.r.mdr = valid ? bus.read(0x2100 | address, cpu.r.mdr) : (uint8)0x00;
step(4); step<4,1>();
return cpu.r.mdr; return cpu.r.mdr;
} }
@ -97,7 +98,7 @@ auto CPU::Channel::transfer(uint24 addressA, uint2 index) -> void {
auto CPU::Channel::dmaRun() -> void { auto CPU::Channel::dmaRun() -> void {
if(!dmaEnable) return; if(!dmaEnable) return;
step(8); step<8,0>();
edge(); edge();
uint2 index = 0; uint2 index = 0;

View File

@ -203,7 +203,7 @@ auto CPU::writeCPU(uint24 addr, uint8 data) -> void {
return; return;
case 0x420d: //MEMSEL case 0x420d: //MEMSEL
io.romSpeed = data.bit(0) ? 6 : 8; io.fastROM = data.bit(0);
return; return;
} }

View File

@ -1,17 +1,45 @@
auto CPU::idle() -> void { auto CPU::idle() -> void {
status.irqLock = false;
status.clockCount = 6; status.clockCount = 6;
dmaEdge(); dmaEdge();
stepIdle(6); step<6,0>();
aluEdge(); aluEdge();
} }
auto CPU::read(uint24 address) -> uint8 { auto CPU::read(uint24 address) -> uint8 {
status.clockCount = wait(address); status.irqLock = false;
dmaEdge();
r.mar = address; if(address & 0x408000) {
step(status.clockCount - 4); if(address & 0x800000 && io.fastROM) {
status.clockCount = 6;
dmaEdge();
r.mar = address;
step<2,1>();
} else {
status.clockCount = 8;
dmaEdge();
r.mar = address;
step<4,1>();
}
} else if(address + 0x6000 & 0x4000) {
status.clockCount = 8;
dmaEdge();
r.mar = address;
step<4,1>();
} else if(address - 0x4000 & 0x7e00) {
status.clockCount = 6;
dmaEdge();
r.mar = address;
step<2,1>();
} else {
status.clockCount = 12;
dmaEdge();
r.mar = address;
step<8,1>();
}
auto data = bus.read(address, r.mdr); auto data = bus.read(address, r.mdr);
step(4); step<4,0>();
aluEdge(); aluEdge();
//$00-3f,80-bf:4000-43ff reads are internal to CPU, and do not update the MDR //$00-3f,80-bf:4000-43ff reads are internal to CPU, and do not update the MDR
if((address & 0x40fc00) != 0x4000) r.mdr = data; if((address & 0x40fc00) != 0x4000) r.mdr = data;
@ -19,19 +47,39 @@ auto CPU::read(uint24 address) -> uint8 {
} }
auto CPU::write(uint24 address, uint8 data) -> void { auto CPU::write(uint24 address, uint8 data) -> void {
status.irqLock = false;
aluEdge(); aluEdge();
status.clockCount = wait(address);
dmaEdge();
r.mar = address;
step(status.clockCount);
bus.write(address, r.mdr = data);
}
auto CPU::wait(uint24 address) const -> uint { if(address & 0x408000) {
if(address & 0x408000) return address & 0x800000 ? io.romSpeed : 8; if(address & 0x800000 && io.fastROM) {
if(address + 0x6000 & 0x4000) return 8; status.clockCount = 6;
if(address - 0x4000 & 0x7e00) return 6; dmaEdge();
return 12; r.mar = address;
step<6,1>();
} else {
status.clockCount = 8;
dmaEdge();
r.mar = address;
step<8,1>();
}
} else if(address + 0x6000 & 0x4000) {
status.clockCount = 8;
dmaEdge();
r.mar = address;
step<8,1>();
} else if(address - 0x4000 & 0x7e00) {
status.clockCount = 6;
dmaEdge();
r.mar = address;
step<6,1>();
} else {
status.clockCount = 12;
dmaEdge();
r.mar = address;
step<12,1>();
}
bus.write(address, r.mdr = data);
} }
auto CPU::readDisassembler(uint24 address) -> uint8 { auto CPU::readDisassembler(uint24 address) -> uint8 {

View File

@ -69,7 +69,7 @@ auto CPU::serialize(serializer& s) -> void {
s.integer(io.htime); s.integer(io.htime);
s.integer(io.vtime); s.integer(io.vtime);
s.integer(io.romSpeed); s.integer(io.fastROM);
s.integer(io.rddiv); s.integer(io.rddiv);
s.integer(io.rdmpy); s.integer(io.rdmpy);

View File

@ -17,53 +17,48 @@ auto CPU::joypadCounter() const -> uint {
return counter.cpu & 255; return counter.cpu & 255;
} }
auto CPU::step(uint clocks) -> void { auto CPU::stepOnce() -> void {
status.irqLock = false; counter.cpu += 2;
uint ticks = clocks >> 1; tick();
while(ticks--) { if(hcounter() & 2) pollInterrupts();
counter.cpu += 2; if(joypadCounter() == 0) joypadEdge();
tick();
if(hcounter() & 2) pollInterrupts();
if(joypadCounter() == 0) joypadEdge();
}
Thread::step(clocks);
for(auto peripheral : peripherals) synchronize(*peripheral);
if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) {
//note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer
//result averages out the same as no coprocessor polls refresh() at > frequency()/2
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge();
}
if(configuration.hacks.coprocessors.delayedSync) return;
for(auto coprocessor : coprocessors) synchronize(*coprocessor);
} }
auto CPU::stepIdle(uint clocks) -> void { template<uint Clocks, bool Synchronize>
status.irqLock = false; auto CPU::step() -> void {
uint ticks = clocks >> 1; static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12);
while(ticks--) { if constexpr(Clocks >= 2) stepOnce();
counter.cpu += 2; if constexpr(Clocks >= 4) stepOnce();
tick(); if constexpr(Clocks >= 6) stepOnce();
if(hcounter() & 2) pollInterrupts(); if constexpr(Clocks >= 8) stepOnce();
if(joypadCounter() == 0) joypadEdge(); if constexpr(Clocks >= 10) stepOnce();
} if constexpr(Clocks >= 12) stepOnce();
Thread::step(Clocks);
Thread::step(clocks);
if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) { if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) {
//note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer //note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer
//result averages out the same as no coprocessor polls refresh() at > frequency()/2 //result averages out the same as no coprocessor polls refresh() at > frequency()/2
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
status.dramRefresh = 1; step(6); status.dramRefresh = 2; step(2); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
}
if constexpr(Synchronize) {
if(configuration.hacks.coprocessors.delayedSync) return;
for(auto coprocessor : coprocessors) synchronize(*coprocessor);
}
}
auto CPU::step(uint clocks) -> void {
switch(clocks) {
case 2: return step< 2,1>();
case 4: return step< 4,1>();
case 6: return step< 6,1>();
case 8: return step< 8,1>();
case 10: return step<10,1>();
case 12: return step<12,1>();
} }
} }

View File

@ -19,7 +19,7 @@ void DSP::main() {
signed count = spc_dsp.sample_count(); signed count = spc_dsp.sample_count();
if(count > 0) { if(count > 0) {
for(unsigned n = 0; n < count; n += 2) { for(unsigned n = 0; n < count; n += 2) {
stream->sample(samplebuffer[n + 0] / 32767.0, samplebuffer[n + 1] / 32767.0); stream->sample(samplebuffer[n + 0] / 32768.0, samplebuffer[n + 1] / 32768.0);
} }
spc_dsp.set_output(samplebuffer, 8192); spc_dsp.set_output(samplebuffer, 8192);
} }

View File

@ -1,24 +1,15 @@
#include <sfc/sfc.hpp> #include <sfc/sfc.hpp>
#include <sfc/expansion/satellaview/satellaview.cpp>
//#include <sfc/expansion/21fx/21fx.cpp>
namespace SuperFamicom { namespace SuperFamicom {
ExpansionPort expansionPort; ExpansionPort expansionPort;
Expansion::Expansion() { Expansion::Expansion() {
if(!handle()) create(Expansion::Enter, 1);
} }
Expansion::~Expansion() { Expansion::~Expansion() {
scheduler.remove(*this);
}
auto Expansion::Enter() -> void {
while(true) scheduler.synchronize(), expansionPort.device->main();
}
auto Expansion::main() -> void {
step(1);
synchronize(cpu);
} }
// //
@ -30,13 +21,8 @@ auto ExpansionPort::connect(uint deviceID) -> void {
switch(deviceID) { default: switch(deviceID) { default:
case ID::Device::None: device = new Expansion; break; case ID::Device::None: device = new Expansion; break;
case ID::Device::Satellaview: device = new Satellaview; break; case ID::Device::Satellaview: device = new Satellaview; break;
case ID::Device::S21FX: device = new S21FX; break; //case ID::Device::S21FX: device = new S21FX; break;
} }
cpu.peripherals.reset();
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
if(auto device = expansionPort.device) cpu.peripherals.append(device);
} }
auto ExpansionPort::power() -> void { auto ExpansionPort::power() -> void {

View File

@ -1,8 +1,6 @@
struct Expansion : Thread { struct Expansion : Thread {
Expansion(); Expansion();
virtual ~Expansion(); virtual ~Expansion();
static auto Enter() -> void;
virtual auto main() -> void;
}; };
struct ExpansionPort { struct ExpansionPort {
@ -18,4 +16,4 @@ struct ExpansionPort {
extern ExpansionPort expansionPort; extern ExpansionPort expansionPort;
#include <sfc/expansion/satellaview/satellaview.hpp> #include <sfc/expansion/satellaview/satellaview.hpp>
#include <sfc/expansion/21fx/21fx.hpp> //#include <sfc/expansion/21fx/21fx.hpp>

View File

@ -245,9 +245,57 @@ auto Interface::unserialize(serializer& s) -> bool {
} }
auto Interface::cheats(const vector<string>& list) -> void { auto Interface::cheats(const vector<string>& list) -> void {
cheat.reset();
if(cartridge.has.ICD) return GameBoy::cheat.assign(list); if(cartridge.has.ICD) return GameBoy::cheat.assign(list);
cheat.assign(list);
//make all ROM data writable temporarily
Memory::GlobalWriteEnable = true;
Cheat oldCheat = cheat;
Cheat newCheat;
newCheat.assign(list);
//determine all old codes to remove
for(auto& oldCode : oldCheat.codes) {
bool found = false;
for(auto& newCode : newCheat.codes) {
if(oldCode == newCode) {
found = true;
break;
}
}
if(!found) {
//remove old cheat
if(oldCode.enable) {
bus.write(oldCode.address, oldCode.restore);
}
}
}
//determine all new codes to create
for(auto& newCode : newCheat.codes) {
bool found = false;
for(auto& oldCode : oldCheat.codes) {
if(newCode == oldCode) {
found = true;
break;
}
}
if(!found) {
//create new cheat
newCode.restore = bus.read(newCode.address);
if(!newCode.compare || newCode.compare() == newCode.restore) {
newCode.enable = true;
bus.write(newCode.address, newCode.data);
} else {
newCode.enable = false;
}
}
}
cheat = newCheat;
//restore ROM write protection
Memory::GlobalWriteEnable = false;
} }
auto Interface::configuration() -> string { auto Interface::configuration() -> string {

View File

@ -24,12 +24,7 @@ auto Bus::reduce(uint addr, uint mask) -> uint {
} }
auto Bus::read(uint24 addr, uint8 data) -> uint8 { auto Bus::read(uint24 addr, uint8 data) -> uint8 {
data = reader[lookup[addr]](target[addr], data); return reader[lookup[addr]](target[addr], data);
if(cheat) {
if(!(addr & 0x40e000)) addr = 0x7e0000 | (addr & 0x1fff); //de-mirror WRAM
if(auto result = cheat.find(addr, data)) return result();
}
return data;
} }
auto Bus::write(uint24 addr, uint8 data) -> void { auto Bus::write(uint24 addr, uint8 data) -> void {

View File

@ -2,6 +2,7 @@
namespace SuperFamicom { namespace SuperFamicom {
bool Memory::GlobalWriteEnable = false;
Bus bus; Bus bus;
Bus::~Bus() { Bus::~Bus() {

View File

@ -1,4 +1,6 @@
struct Memory { struct Memory {
static bool GlobalWriteEnable;
virtual ~Memory() { reset(); } virtual ~Memory() { reset(); }
inline explicit operator bool() const { return size() > 0; } inline explicit operator bool() const { return size() > 0; }

View File

@ -32,8 +32,9 @@ struct ProtectableMemory : Memory {
} }
inline auto write(uint24 address, uint8 data) -> void override { inline auto write(uint24 address, uint8 data) -> void override {
if(!self.writable) return; if(self.writable || Memory::GlobalWriteEnable) {
self.data[address] = data; self.data[address] = data;
}
} }
inline auto operator[](uint24 address) const -> uint8 { inline auto operator[](uint24 address) const -> uint8 {

View File

@ -24,6 +24,9 @@ struct ReadableMemory : Memory {
} }
inline auto write(uint24 address, uint8 data) -> void override { inline auto write(uint24 address, uint8 data) -> void override {
if(Memory::GlobalWriteEnable) {
self.data[address] = data;
}
} }
inline auto operator[](uint24 address) const -> uint8 { inline auto operator[](uint24 address) const -> uint8 {

View File

@ -120,8 +120,8 @@ auto PPUfast::Line::getTile(PPUfast::IO::Background& self, uint hoffset, uint vo
bool hires = io.bgMode == 5 || io.bgMode == 6; bool hires = io.bgMode == 5 || io.bgMode == 6;
uint tileHeight = 3 + self.tileSize; uint tileHeight = 3 + self.tileSize;
uint tileWidth = !hires ? tileHeight : 4; uint tileWidth = !hires ? tileHeight : 4;
uint screenX = self.screenSize.bit(0) ? 32 << 5 : 0; uint screenX = (self.screenSize & 1) ? 32 << 5 : 0;
uint screenY = self.screenSize.bit(1) ? 32 << 5 + self.screenSize.bit(0) : 0; uint screenY = (self.screenSize & 2) ? 32 << 5 + (self.screenSize & 1) : 0;
uint tileX = hoffset >> tileWidth; uint tileX = hoffset >> tileWidth;
uint tileY = voffset >> tileHeight; uint tileY = voffset >> tileHeight;
uint offset = (tileY & 0x1f) << 5 | (tileX & 0x1f); uint offset = (tileY & 0x1f) << 5 | (tileX & 0x1f);

View File

@ -5,7 +5,7 @@ auto PPUfast::latchCounters() -> void {
} }
auto PPUfast::vramAddress() const -> uint15 { //uint15 for 64K VRAM; uint16 for 128K VRAM auto PPUfast::vramAddress() const -> uint15 { //uint15 for 64K VRAM; uint16 for 128K VRAM
uint16 address = io.vramAddress; uint15 address = io.vramAddress;
switch(io.vramMapping) { switch(io.vramMapping) {
case 0: return address; case 0: return address;
case 1: return address.bits( 8,15) << 8 | address.bits(0,4) << 3 | address.bits(5,7); case 1: return address.bits( 8,15) << 8 | address.bits(0,4) << 3 | address.bits(5,7);
@ -21,28 +21,31 @@ auto PPUfast::readVRAM() -> uint16 {
return vram[address]; return vram[address];
} }
auto PPUfast::writeVRAM(uint1 byte, uint8 data) -> void { template<bool Byte>
auto PPUfast::writeVRAM(uint8_t data) -> void {
if(!io.displayDisable && cpu.vcounter() < vdisp()) return; if(!io.displayDisable && cpu.vcounter() < vdisp()) return;
Line::flush(); Line::flush();
auto address = vramAddress(); auto address = vramAddress();
vram[address].byte(byte) = data; if constexpr(Byte == 0) {
vram[address] = vram[address] & 0xff00 | data << 0;
}
if constexpr(Byte == 1) {
vram[address] = vram[address] & 0x00ff | data << 8;
}
updateTiledata(address); updateTiledata(address);
} }
auto PPUfast::updateTiledata(uint15 address) -> void { auto PPUfast::updateTiledata(uint address) -> void {
auto word = vram[address]; auto word = vram[address];
auto line2bpp = tilecache[TileMode::BPP2] + (address.bits(3,14) << 6) + (address.bits(0,2) << 3); auto line2bpp = tilecache[TileMode::BPP2] + ((address & 0x7fff) << 3);
auto line4bpp = tilecache[TileMode::BPP4] + (address.bits(4,14) << 6) + (address.bits(0,2) << 3); auto line4bpp = tilecache[TileMode::BPP4] + ((address & 0x7ff0) << 2) + ((address & 7) << 3);
auto line8bpp = tilecache[TileMode::BPP8] + (address.bits(5,14) << 6) + (address.bits(0,2) << 3); auto line8bpp = tilecache[TileMode::BPP8] + ((address & 0x7fe0) << 1) + ((address & 7) << 3);
uint plane4bpp = address.bit(3) << 1; uint plane4bpp = address >> 2 & 2;
uint plane8bpp = address.bit(3) << 1 | address.bit(4) << 2; uint plane8bpp = address >> 2 & 6;
for(uint x : range(8)) { for(uint x : range(8)) {
line2bpp[7 - x].bit( 0) = word.bit(x + 0); line2bpp[7 - x] = word >> x & 1 | word >> x + 7 & 2;
line2bpp[7 - x].bit( 1) = word.bit(x + 8); line4bpp[7 - x] = line4bpp[7 - x] & ~(3 << plane4bpp) | (word >> x & 1) << plane4bpp | (word >> x + 7 & 2) << plane4bpp;
line4bpp[7 - x].bit(plane4bpp + 0) = word.bit(x + 0); line8bpp[7 - x] = line8bpp[7 - x] & ~(3 << plane8bpp) | (word >> x & 1) << plane8bpp | (word >> x + 7 & 2) << plane8bpp;
line4bpp[7 - x].bit(plane4bpp + 1) = word.bit(x + 8);
line8bpp[7 - x].bit(plane8bpp + 0) = word.bit(x + 0);
line8bpp[7 - x].bit(plane8bpp + 1) = word.bit(x + 8);
} }
} }
@ -58,12 +61,18 @@ auto PPUfast::writeOAM(uint10 address, uint8 data) -> void {
return writeObject(address, data); return writeObject(address, data);
} }
auto PPUfast::readCGRAM(uint1 byte, uint8 address) -> uint8 { template<bool Byte>
auto PPUfast::readCGRAM(uint8_t address) -> uint8 {
if(!io.displayDisable if(!io.displayDisable
&& cpu.vcounter() > 0 && cpu.vcounter() < vdisp() && cpu.vcounter() > 0 && cpu.vcounter() < vdisp()
&& cpu.hcounter() >= 88 && cpu.hcounter() < 1096 && cpu.hcounter() >= 88 && cpu.hcounter() < 1096
) address = latch.cgramAddress; ) address = latch.cgramAddress;
return cgram[address].byte(byte); if constexpr(Byte == 0) {
return cgram[address] >> 0;
}
if constexpr(Byte == 1) {
return cgram[address] >> 8;
}
} }
auto PPUfast::writeCGRAM(uint8 address, uint15 data) -> void { auto PPUfast::writeCGRAM(uint8 address, uint15 data) -> void {
@ -114,7 +123,7 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 {
} }
case 0x2139: { //VMDATALREAD case 0x2139: { //VMDATALREAD
data = latch.vram.byte(0); data = latch.vram & 0xff;
if(io.vramIncrementMode == 0) { if(io.vramIncrementMode == 0) {
latch.vram = readVRAM(); latch.vram = readVRAM();
io.vramAddress += io.vramIncrementSize; io.vramAddress += io.vramIncrementSize;
@ -123,7 +132,7 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 {
} }
case 0x213a: { //VMDATAHREAD case 0x213a: { //VMDATAHREAD
data = latch.vram.byte(1); data = latch.vram >> 8;
if(io.vramIncrementMode == 1) { if(io.vramIncrementMode == 1) {
latch.vram = readVRAM(); latch.vram = readVRAM();
io.vramAddress += io.vramIncrementSize; io.vramAddress += io.vramIncrementSize;
@ -133,26 +142,30 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 {
case 0x213b: { //CGDATAREAD case 0x213b: { //CGDATAREAD
if(io.cgramAddressLatch++ == 0) { if(io.cgramAddressLatch++ == 0) {
latch.ppu2.mdr.bits(0,7) = readCGRAM(0, io.cgramAddress); latch.ppu2.mdr.bits(0,7) = readCGRAM<0>(io.cgramAddress);
} else { } else {
latch.ppu2.mdr.bits(0,6) = readCGRAM(1, io.cgramAddress++); latch.ppu2.mdr.bits(0,6) = readCGRAM<1>(io.cgramAddress++);
} }
return latch.ppu2.mdr; return latch.ppu2.mdr;
} }
case 0x213c: { //OPHCT case 0x213c: { //OPHCT
if(latch.hcounter++ == 0) { if(latch.hcounter == 0) {
latch.hcounter = 1;
latch.ppu2.mdr.bits(0,7) = io.hcounter.bits(0,7); latch.ppu2.mdr.bits(0,7) = io.hcounter.bits(0,7);
} else { } else {
latch.hcounter = 0;
latch.ppu2.mdr.bit(0) = io.hcounter.bit(8); latch.ppu2.mdr.bit(0) = io.hcounter.bit(8);
} }
return latch.ppu2.mdr; return latch.ppu2.mdr;
} }
case 0x213d: { //OPVCT case 0x213d: { //OPVCT
if(latch.vcounter++ == 0) { if(latch.vcounter == 0) {
latch.vcounter = 1;
latch.ppu2.mdr.bits(0,7) = io.vcounter.bits(0,7); latch.ppu2.mdr.bits(0,7) = io.vcounter.bits(0,7);
} else { } else {
latch.vcounter = 0;
latch.ppu2.mdr.bit(0) = io.vcounter.bit(8); latch.ppu2.mdr.bit(0) = io.vcounter.bit(8);
} }
return latch.ppu2.mdr; return latch.ppu2.mdr;
@ -355,25 +368,25 @@ auto PPUfast::writeIO(uint24 address, uint8 data) -> void {
} }
case 0x2116: { //VMADDL case 0x2116: { //VMADDL
io.vramAddress.byte(0) = data; io.vramAddress = io.vramAddress & 0xff00 | data << 0;
latch.vram = readVRAM(); latch.vram = readVRAM();
return; return;
} }
case 0x2117: { //VMADDH case 0x2117: { //VMADDH
io.vramAddress.byte(1) = data; io.vramAddress = io.vramAddress & 0x00ff | data << 8;
latch.vram = readVRAM(); latch.vram = readVRAM();
return; return;
} }
case 0x2118: { //VMDATAL case 0x2118: { //VMDATAL
writeVRAM(0, data); writeVRAM<0>(data);
if(io.vramIncrementMode == 0) io.vramAddress += io.vramIncrementSize; if(io.vramIncrementMode == 0) io.vramAddress += io.vramIncrementSize;
return; return;
} }
case 0x2119: { //VMDATAH case 0x2119: { //VMDATAH
writeVRAM(1, data); writeVRAM<1>(data);
if(io.vramIncrementMode == 1) io.vramAddress += io.vramIncrementSize; if(io.vramIncrementMode == 1) io.vramAddress += io.vramIncrementSize;
return; return;
} }

View File

@ -13,6 +13,8 @@ auto PPUfast::Line::flush() -> void {
} }
auto PPUfast::Line::render() -> void { auto PPUfast::Line::render() -> void {
uint y = this->y + (!ppufast.latch.overscan ? 7 : 0);
auto hd = ppufast.hd(); auto hd = ppufast.hd();
auto ss = ppufast.ss(); auto ss = ppufast.ss();
auto scale = ppufast.hdScale(); auto scale = ppufast.hdScale();
@ -25,13 +27,13 @@ auto PPUfast::Line::render() -> void {
: (256 * scale * scale)); : (256 * scale * scale));
if(io.displayDisable) { if(io.displayDisable) {
memory::fill<uint32>(output, width); memory::fill<uint16_t>(output, width);
return; return;
} }
bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
auto aboveColor = cgram[0]; auto aboveColor = cgram[0];
auto belowColor = hires ? cgram[0] : io.col.fixedColor; auto belowColor = hires ? cgram[0] : (uint16_t)io.col.fixedColor;
uint xa = (hd || ss) && ppufast.interlace() && ppufast.field() ? 256 * scale * scale / 2 : 0; uint xa = (hd || ss) && ppufast.interlace() && ppufast.field() ? 256 * scale * scale / 2 : 0;
uint xb = !(hd || ss) ? 256 : ppufast.interlace() && !ppufast.field() ? 256 * scale * scale / 2 : 256 * scale * scale; uint xb = !(hd || ss) ? 256 : ppufast.interlace() && !ppufast.field() ? 256 * scale * scale / 2 : 256 * scale * scale;
for(uint x = xa; x < xb; x++) { for(uint x = xa; x < xb; x++) {
@ -70,7 +72,7 @@ auto PPUfast::Line::render() -> void {
} }
} }
auto PPUfast::Line::pixel(uint x, Pixel above, Pixel below) const -> uint15 { auto PPUfast::Line::pixel(uint x, Pixel above, Pixel below) const -> uint16_t {
if(!windowAbove[x]) above.color = 0x0000; if(!windowAbove[x]) above.color = 0x0000;
if(!windowBelow[x]) return above.color; if(!windowBelow[x]) return above.color;
if(!io.col.enable[above.source]) return above.color; if(!io.col.enable[above.source]) return above.color;
@ -78,7 +80,7 @@ auto PPUfast::Line::pixel(uint x, Pixel above, Pixel below) const -> uint15 {
return blend(above.color, below.color, io.col.halve && windowAbove[x] && below.source != Source::COL); return blend(above.color, below.color, io.col.halve && windowAbove[x] && below.source != Source::COL);
} }
auto PPUfast::Line::blend(uint x, uint y, bool halve) const -> uint15 { auto PPUfast::Line::blend(uint x, uint y, bool halve) const -> uint16_t {
if(!io.col.mathMode) { //add if(!io.col.mathMode) { //add
if(!halve) { if(!halve) {
uint sum = x + y; uint sum = x + y;
@ -98,7 +100,7 @@ auto PPUfast::Line::blend(uint x, uint y, bool halve) const -> uint15 {
} }
} }
auto PPUfast::Line::directColor(uint paletteIndex, uint paletteColor) const -> uint15 { auto PPUfast::Line::directColor(uint paletteIndex, uint paletteColor) const -> uint16_t {
//paletteIndex = bgr //paletteIndex = bgr
//paletteColor = BBGGGRRR //paletteColor = BBGGGRRR
//output = 0 BBb00 GGGg0 RRRr0 //output = 0 BBb00 GGGg0 RRRr0

View File

@ -43,8 +43,8 @@ auto PPUfast::Line::renderMode7(PPUfast::IO::Background& self, uint source) -> v
bool outOfBounds = (pixelX | pixelY) & ~1023; bool outOfBounds = (pixelX | pixelY) & ~1023;
uint15 tileAddress = tileY * 128 + tileX; uint15 tileAddress = tileY * 128 + tileX;
uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7);
uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppufast.vram[tileAddress].byte(0); uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : (ppufast.vram[tileAddress] & 0xff);
uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppufast.vram[paletteAddress + (tile << 6)].byte(1); uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : (ppufast.vram[paletteAddress + (tile << 6)] >> 8);
uint priority; uint priority;
if(source == Source::BG1) { if(source == Source::BG1) {

View File

@ -80,8 +80,8 @@ auto PPUfast::Line::renderMode7HD(PPUfast::IO::Background& self, uint source) ->
//only compute color again when coordinates have changed //only compute color again when coordinates have changed
if(pixelX != pixelXp || pixelY != pixelYp) { if(pixelX != pixelXp || pixelY != pixelYp) {
uint tile = io.mode7.repeat == 3 && ((pixelX | pixelY) & ~1023) ? 0 : ppufast.vram[(pixelY >> 3 & 127) * 128 + (pixelX >> 3 & 127)].byte(0); uint tile = io.mode7.repeat == 3 && ((pixelX | pixelY) & ~1023) ? 0 : (ppufast.vram[(pixelY >> 3 & 127) * 128 + (pixelX >> 3 & 127)] & 0xff);
uint palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : ppufast.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)].byte(1); uint palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : (ppufast.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)] >> 8);
uint priority; uint priority;
if(!extbg) { if(!extbg) {

View File

@ -26,8 +26,6 @@ auto PPUfast::hdSupersample() const -> bool { return configuration.hacks.ppu.mod
auto PPUfast::hdMosaic() const -> bool { return configuration.hacks.ppu.mode7.mosaic; } auto PPUfast::hdMosaic() const -> bool { return configuration.hacks.ppu.mode7.mosaic; }
PPUfast::PPUfast() { PPUfast::PPUfast() {
output = new uint16[2304 * 2304] + 72 * 2304; //overscan offset
for(uint l : range(16)) { for(uint l : range(16)) {
for(uint r : range(32)) { for(uint r : range(32)) {
for(uint g : range(32)) { for(uint g : range(32)) {
@ -42,9 +40,9 @@ PPUfast::PPUfast() {
} }
} }
tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8]; tilecache[TileMode::BPP2] = new uint8_t[4096 * 8 * 8];
tilecache[TileMode::BPP4] = new uint8[2048 * 8 * 8]; tilecache[TileMode::BPP4] = new uint8_t[2048 * 8 * 8];
tilecache[TileMode::BPP8] = new uint8[1024 * 8 * 8]; tilecache[TileMode::BPP8] = new uint8_t[1024 * 8 * 8];
for(uint y : range(lines.size())) { for(uint y : range(lines.size())) {
lines[y].y = y; lines[y].y = y;
@ -52,7 +50,6 @@ PPUfast::PPUfast() {
} }
PPUfast::~PPUfast() { PPUfast::~PPUfast() {
delete[] (output - 72 * 2304); //overscan offset
delete[] tilecache[TileMode::BPP2]; delete[] tilecache[TileMode::BPP2];
delete[] tilecache[TileMode::BPP4]; delete[] tilecache[TileMode::BPP4];
delete[] tilecache[TileMode::BPP8]; delete[] tilecache[TileMode::BPP8];
@ -93,6 +90,7 @@ auto PPUfast::scanline() -> void {
if(vcounter() == 0) { if(vcounter() == 0) {
ppubase.display.interlace = io.interlace; ppubase.display.interlace = io.interlace;
ppubase.display.overscan = io.overscan; ppubase.display.overscan = io.overscan;
latch.overscan = io.overscan;
latch.hires = false; latch.hires = false;
latch.hd = false; latch.hd = false;
latch.ss = false; latch.ss = false;
@ -121,17 +119,31 @@ auto PPUfast::refresh() -> void {
auto output = this->output; auto output = this->output;
uint pitch, width, height; uint pitch, width, height;
if(!hd()) { if(!hd()) {
if(!overscan()) output -= 7 * 1024;
pitch = 512 << !interlace(); pitch = 512 << !interlace();
width = 256 << hires(); width = 256 << hires();
height = 240 << interlace(); height = 240 << interlace();
} else { } else {
if(!overscan()) output -= 7 * 256 * hdScale() * hdScale();
pitch = 256 * hdScale(); pitch = 256 * hdScale();
width = 256 * hdScale(); width = 256 * hdScale();
height = 240 * hdScale(); height = 240 * hdScale();
} }
platform->videoFrame(output, pitch * sizeof(uint16), width, height);
//clear the areas of the screen that won't be rendered:
//previous video frames may have drawn data here that would now be stale otherwise.
if(!latch.overscan && pitch != frame.pitch && width != frame.width && height != frame.height) {
for(uint y : range(240)) {
if(y >= 8 && y <= 230) continue; //these scanlines are always rendered.
auto output = this->output + (!hd() ? (y * 1024 + (interlace() && field() ? 512 : 0)) : (y * 256 * hdScale() * hdScale()));
auto width = (!hd() ? (!hires() ? 256 : 512) : (256 * hdScale() * hdScale()));
memory::fill<uint16>(output, width);
}
}
platform->videoFrame(output, pitch * sizeof(uint16), width, height, hd() ? hdScale() : 1);
frame.pitch = pitch;
frame.width = width;
frame.height = height;
} }
if(system.frameCounter++ >= system.frameSkip) system.frameCounter = 0; if(system.frameCounter++ >= system.frameSkip) system.frameCounter = 0;
} }
@ -143,7 +155,7 @@ auto PPUfast::load() -> bool {
auto PPUfast::power(bool reset) -> void { auto PPUfast::power(bool reset) -> void {
create(Enter, system.cpuFrequency()); create(Enter, system.cpuFrequency());
PPUcounter::reset(); PPUcounter::reset();
memory::fill<uint16>(output, 1024 * 960); memory::fill<uint16_t>(output, 1024 * 960);
function<auto (uint24, uint8) -> uint8> reader{&PPUfast::readIO, this}; function<auto (uint24, uint8) -> uint8> reader{&PPUfast::readIO, this};
function<auto (uint24, uint8) -> void> writer{&PPUfast::writeIO, this}; function<auto (uint24, uint8) -> void> writer{&PPUfast::writeIO, this};
@ -167,6 +179,8 @@ auto PPUfast::power(bool reset) -> void {
Line::start = 0; Line::start = 0;
Line::count = 0; Line::count = 0;
frame = {};
} }
} }

View File

@ -41,30 +41,30 @@ public:
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint1 interlace; bool interlace = 0;
uint1 overscan; bool overscan = 0;
uint1 hires; bool hires = 0;
uint1 hd; bool hd = 0;
uint1 ss; bool ss = 0;
uint16 vram; uint16_t vram = 0;
uint8 oam; uint8_t oam = 0;
uint8 cgram; uint8_t cgram = 0;
uint10 oamAddress; uint10 oamAddress = 0;
uint8 cgramAddress; uint8_t cgramAddress = 0;
uint8 mode7; uint8_t mode7 = 0;
uint1 counters; bool counters = 0;
uint1 hcounter; //hdot bool hcounter = 0; //hdot
uint1 vcounter; bool vcounter = 0;
struct PPU { struct PPU {
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint8 mdr; uint8 mdr;
uint8 bgofs; uint8_t bgofs = 0;
} ppu1, ppu2; } ppu1, ppu2;
}; };
@ -72,78 +72,78 @@ public:
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint1 displayDisable; bool displayDisable = 1;
uint4 displayBrightness; uint4 displayBrightness;
uint10 oamBaseAddress; uint10 oamBaseAddress;
uint10 oamAddress; uint10 oamAddress;
uint1 oamPriority; bool oamPriority = 0;
uint1 bgPriority; bool bgPriority = 0;
uint3 bgMode; uint3 bgMode;
uint4 mosaicSize; uint4 mosaicSize;
uint1 vramIncrementMode; bool vramIncrementMode = 0;
uint2 vramMapping; uint2 vramMapping;
uint8 vramIncrementSize; uint8_t vramIncrementSize = 0;
uint16 vramAddress; uint16_t vramAddress = 0;
uint8 cgramAddress; uint8_t cgramAddress = 0;
uint1 cgramAddressLatch; uint1 cgramAddressLatch;
uint9 hcounter; //hdot uint9 hcounter; //hdot
uint9 vcounter; uint9 vcounter;
uint1 interlace; bool interlace = 0;
uint1 overscan; bool overscan = 0;
uint1 pseudoHires; bool pseudoHires = 0;
uint1 extbg; bool extbg = 0;
struct Mode7 { struct Mode7 {
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint1 hflip; bool hflip = 0;
uint1 vflip; bool vflip = 0;
uint2 repeat; uint repeat = 0;
uint16 a; uint16_t a = 0;
uint16 b; uint16_t b = 0;
uint16 c; uint16_t c = 0;
uint16 d; uint16_t d = 0;
uint16 x; uint16_t x = 0;
uint16 y; uint16_t y = 0;
uint16 hoffset; uint16_t hoffset = 0;
uint16 voffset; uint16_t voffset = 0;
} mode7; } mode7;
struct Window { struct Window {
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint8 oneLeft; uint8_t oneLeft = 0;
uint8 oneRight; uint8_t oneRight = 0;
uint8 twoLeft; uint8_t twoLeft = 0;
uint8 twoRight; uint8_t twoRight = 0;
} window; } window;
struct WindowLayer { struct WindowLayer {
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint1 oneEnable; bool oneEnable = 0;
uint1 oneInvert; bool oneInvert = 0;
uint1 twoEnable; bool twoEnable = 0;
uint1 twoInvert; bool twoInvert = 0;
uint2 mask; uint mask = 0;
uint1 aboveEnable; bool aboveEnable = 0;
uint1 belowEnable; bool belowEnable = 0;
}; };
struct WindowColor { struct WindowColor {
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint1 oneEnable; bool oneEnable = 0;
uint1 oneInvert; bool oneInvert = 0;
uint1 twoEnable; bool twoEnable = 0;
uint1 twoInvert; bool twoInvert = 0;
uint2 mask; uint mask = 0;
uint2 aboveMask; uint aboveMask = 0;
uint2 belowMask; uint belowMask = 0;
}; };
struct Background { struct Background {
@ -151,17 +151,17 @@ public:
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
WindowLayer window; WindowLayer window;
uint1 aboveEnable; bool aboveEnable = 0;
uint1 belowEnable; bool belowEnable = 0;
uint1 mosaicEnable; bool mosaicEnable = 0;
uint15 tiledataAddress; uint15 tiledataAddress;
uint15 screenAddress; uint15 screenAddress;
uint2 screenSize; uint2 screenSize;
uint1 tileSize; bool tileSize = 0;
uint16 hoffset; uint16 hoffset;
uint16 voffset; uint16 voffset;
uint3 tileMode; uint3 tileMode;
uint4 priority[2]; uint4 priority[2];
} bg1, bg2, bg3, bg4; } bg1, bg2, bg3, bg4;
struct Object { struct Object {
@ -169,16 +169,16 @@ public:
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
WindowLayer window; WindowLayer window;
uint1 aboveEnable; bool aboveEnable = 0;
uint1 belowEnable; bool belowEnable = 0;
uint1 interlace; bool interlace = 0;
uint3 baseSize; uint3 baseSize;
uint2 nameselect; uint2 nameselect;
uint15 tiledataAddress; uint15 tiledataAddress;
uint7 first; uint7 first;
uint1 rangeOver; bool rangeOver = 0;
uint1 timeOver; bool timeOver = 0;
uint4 priority[4]; uint4 priority[4];
} obj; } obj;
struct Color { struct Color {
@ -186,11 +186,11 @@ public:
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
WindowColor window; WindowColor window;
uint1 enable[7]; bool enable[7] = {};
uint1 directColor; bool directColor = 0;
uint1 blendMode; //0 = fixed; 1 = pixel bool blendMode = 0; //0 = fixed; 1 = pixel
uint1 halve; bool halve = 0;
uint1 mathMode; //0 = add; 1 = sub bool mathMode = 0; //0 = add; 1 = sub
uint15 fixedColor; uint15 fixedColor;
} col; } col;
}; };
@ -200,48 +200,48 @@ public:
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint9 x; uint9 x;
uint8 y; uint8_t y;
uint8 character; uint8 character;
uint1 nameselect; bool nameselect;
uint1 vflip; bool vflip;
uint1 hflip; bool hflip;
uint2 priority; uint2 priority;
uint3 palette; uint3 palette;
uint1 size; bool size;
}; };
struct ObjectItem { struct ObjectItem {
uint1 valid; bool valid;
uint7 index; uint7 index;
uint8 width; uint8_t width;
uint8 height; uint8_t height;
}; };
struct ObjectTile { struct ObjectTile {
uint1 valid; bool valid;
uint9 x; uint9 x;
uint8 y; uint8_t y;
uint2 priority; uint2 priority;
uint8 palette; uint8_t palette;
uint1 hflip; bool hflip;
uint11 number; uint11 number;
}; };
struct Pixel { struct Pixel {
uint8 source; uint source;
uint8 priority; uint priority;
uint15 color; uint color;
}; };
//io.cpp //io.cpp
auto latchCounters() -> void; auto latchCounters() -> void;
alwaysinline auto vramAddress() const -> uint15; alwaysinline auto vramAddress() const -> uint15;
alwaysinline auto readVRAM() -> uint16; alwaysinline auto readVRAM() -> uint16;
alwaysinline auto writeVRAM(uint1 byte, uint8 data) -> void; template<bool Byte> alwaysinline auto writeVRAM(uint8_t data) -> void;
alwaysinline auto updateTiledata(uint15 address) -> void; alwaysinline auto updateTiledata(uint address) -> void;
alwaysinline auto readOAM(uint10 address) -> uint8; alwaysinline auto readOAM(uint10 address) -> uint8;
alwaysinline auto writeOAM(uint10 address, uint8 data) -> void; alwaysinline auto writeOAM(uint10 address, uint8 data) -> void;
alwaysinline auto readCGRAM(uint1 byte, uint8 address) -> uint8; template<bool Byte> alwaysinline auto readCGRAM(uint8_t address) -> uint8;
alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void; alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void;
auto readIO(uint24 address, uint8 data) -> uint8; auto readIO(uint24 address, uint8 data) -> uint8;
auto writeIO(uint24 address, uint8 data) -> void; auto writeIO(uint24 address, uint8 data) -> void;
@ -257,24 +257,24 @@ public:
Latch latch; Latch latch;
IO io; IO io;
uint16 vram[32 * 1024]; uint16_t vram[32 * 1024];
uint15 cgram[256]; uint16_t cgram[256];
Object objects[128]; Object objects[128];
//[unserialized] //[unserialized]
uint16* output; uint16_t output[2304 * 2160];
uint16 lightTable[16][32768]; uint16_t lightTable[16][32768];
uint8* tilecache[3]; //bitplane -> bitmap tiledata uint8_t* tilecache[3]; //bitplane -> bitmap tiledata
uint32 ItemLimit; uint ItemLimit;
uint32 TileLimit; uint TileLimit;
struct Line { struct Line {
//line.cpp //line.cpp
static auto flush() -> void; static auto flush() -> void;
auto render() -> void; auto render() -> void;
auto pixel(uint x, Pixel above, Pixel below) const -> uint15; auto pixel(uint x, Pixel above, Pixel below) const -> uint16_t;
auto blend(uint x, uint y, bool halve) const -> uint15; auto blend(uint x, uint y, bool halve) const -> uint16_t;
alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint15; alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint16_t;
alwaysinline auto plotAbove(uint x, uint source, uint priority, uint color) -> void; alwaysinline auto plotAbove(uint x, uint source, uint priority, uint color) -> void;
alwaysinline auto plotBelow(uint x, uint source, uint priority, uint color) -> void; alwaysinline auto plotBelow(uint x, uint source, uint priority, uint color) -> void;
alwaysinline auto plotHD(Pixel*, uint x, uint source, uint priority, uint color, bool hires, bool subpixel) -> void; alwaysinline auto plotHD(Pixel*, uint x, uint source, uint priority, uint color, bool hires, bool subpixel) -> void;
@ -301,7 +301,7 @@ public:
uint9 y; //constant uint9 y; //constant
IO io; IO io;
array<uint15[256]> cgram; array<uint16_t[256]> cgram;
array<ObjectItem[128]> items; //32 on real hardware array<ObjectItem[128]> items; //32 on real hardware
array<ObjectTile[128]> tiles; //34 on real hardware; 1024 max (128 * 64-width tiles) array<ObjectTile[128]> tiles; //34 on real hardware; 1024 max (128 * 64-width tiles)
@ -317,6 +317,13 @@ public:
static uint count; static uint count;
}; };
array<Line[240]> lines; array<Line[240]> lines;
//used to help detect when the video output size changes between frames to clear overscan area.
struct Frame {
uint pitch;
uint width;
uint height;
} frame;
}; };
extern PPUfast ppufast; extern PPUfast ppufast;

View File

@ -20,9 +20,6 @@ bg4(Background::ID::BG4) {
ppu1.version = 1; //allowed values: 1 ppu1.version = 1; //allowed values: 1
ppu2.version = 3; //allowed values: 1, 2, 3 ppu2.version = 3; //allowed values: 1, 2, 3
output = new uint16[512 * 512];
output += 16 * 512; //overscan offset
for(uint l = 0; l < 16; l++) { for(uint l = 0; l < 16; l++) {
for(uint r = 0; r < 32; r++) { for(uint r = 0; r < 32; r++) {
for(uint g = 0; g < 32; g++) { for(uint g = 0; g < 32; g++) {
@ -42,9 +39,6 @@ PPU::~PPU() {
if(system.fastPPU()) { if(system.fastPPU()) {
setHandle(nullptr); setHandle(nullptr);
} }
output -= 16 * 512;
delete[] output;
} }
auto PPU::step(uint clocks) -> void { auto PPU::step(uint clocks) -> void {
@ -253,12 +247,10 @@ auto PPU::refresh() -> void {
} }
auto output = this->output; auto output = this->output;
if(!overscan()) output -= 14 * 512; auto pitch = 512;
auto pitch = 512; auto width = 512;
auto width = 512;
auto height = 480; auto height = 480;
//Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, configuration.video.blurEmulation); platform->videoFrame(output, pitch * sizeof(uint16), width, height, /* scale = */ 1);
platform->videoFrame(output, pitch * sizeof(uint16), width, height);
} }
} }

View File

@ -40,8 +40,8 @@ private:
uint16 mask = 0x7fff; uint16 mask = 0x7fff;
} vram; } vram;
uint16* output = nullptr; uint16_t output[512 * 480];
uint16 lightTable[16][32768]; uint16_t lightTable[16][32768];
struct { struct {
bool interlace; bool interlace;

View File

@ -1,5 +1,7 @@
auto PPU::Screen::scanline() -> void { auto PPU::Screen::scanline() -> void {
lineA = ppu.output + ppu.vcounter() * 1024; auto y = ppu.vcounter() + (!ppu.display.overscan ? 7 : 0);
lineA = ppu.output + y * 1024;
lineB = lineA + (ppu.display.interlace ? 0 : 512); lineB = lineA + (ppu.display.interlace ? 0 : 512);
if(ppu.display.interlace && ppu.field()) lineA += 512, lineB += 512; if(ppu.display.interlace && ppu.field()) lineA += 512, lineB += 512;

View File

@ -13,8 +13,8 @@ struct Screen {
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
uint16* lineA; uint16_t* lineA;
uint16* lineB; uint16_t* lineB;
uint15 cgram[256]; uint15 cgram[256];

2
bsnes/sfc/slot/slot.cpp Normal file
View File

@ -0,0 +1,2 @@
#include <sfc/slot/bsmemory/bsmemory.cpp>
#include <sfc/slot/sufamiturbo/sufamiturbo.cpp>

View File

@ -9,7 +9,18 @@ Cheat cheat;
#include "serialization.cpp" #include "serialization.cpp"
auto System::run() -> void { auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh(); if(scheduler.enter() == Scheduler::Event::Frame) {
ppu.refresh();
//refresh all cheat codes once per frame
Memory::GlobalWriteEnable = true;
for(auto& code : cheat.codes) {
if(code.enable) {
bus.write(code.address, code.data);
}
}
Memory::GlobalWriteEnable = false;
}
} }
auto System::runToSave() -> void { auto System::runToSave() -> void {
@ -17,7 +28,6 @@ auto System::runToSave() -> void {
scheduler.synchronize(smp); scheduler.synchronize(smp);
scheduler.synchronize(ppu); scheduler.synchronize(ppu);
for(auto coprocessor : cpu.coprocessors) scheduler.synchronize(*coprocessor); for(auto coprocessor : cpu.coprocessors) scheduler.synchronize(*coprocessor);
for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral);
} }
auto System::load(Emulator::Interface* interface) -> bool { auto System::load(Emulator::Interface* interface) -> bool {
@ -57,7 +67,6 @@ auto System::save() -> void {
auto System::unload() -> void { auto System::unload() -> void {
if(!loaded()) return; if(!loaded()) return;
cpu.peripherals.reset();
controllerPort1.unload(); controllerPort1.unload();
controllerPort2.unload(); controllerPort2.unload();
expansionPort.unload(); expansionPort.unload();

View File

@ -0,0 +1,83 @@
auto Program::filterSelect(uint& width, uint& height, uint scale) -> Filter::Render {
Filter::Size size = &Filter::None::size;
Filter::Render render = &Filter::None::render;
//HD mode 7 is incompatible with software filters
if(scale != 1) {
size(width, height);
return render;
}
if(presentation.filterScanlinesLight.checked() && width <= 512 && height <= 240) {
size = &Filter::ScanlinesLight::size;
render = &Filter::ScanlinesLight::render;
}
if(presentation.filterScanlinesDark.checked() && width <= 512 && height <= 240) {
size = &Filter::ScanlinesDark::size;
render = &Filter::ScanlinesDark::render;
}
if(presentation.filterScanlinesBlack.checked() && width <= 512 && height <= 240) {
size = &Filter::ScanlinesBlack::size;
render = &Filter::ScanlinesBlack::render;
}
if(presentation.filterPixellate2x.checked() && width <= 512 && height <= 480) {
size = &Filter::Pixellate2x::size;
render = &Filter::Pixellate2x::render;
}
if(presentation.filterScale2x.checked() && width <= 256 && height <= 240) {
size = &Filter::Scale2x::size;
render = &Filter::Scale2x::render;
}
if(presentation.filter2xSaI.checked() && width <= 256 && height <= 240) {
size = &Filter::_2xSaI::size;
render = &Filter::_2xSaI::render;
}
if(presentation.filterSuper2xSaI.checked() && width <= 256 && height <= 240) {
size = &Filter::Super2xSaI::size;
render = &Filter::Super2xSaI::render;
}
if(presentation.filterSuperEagle.checked() && width <= 256 && height <= 240) {
size = &Filter::SuperEagle::size;
render = &Filter::SuperEagle::render;
}
if(presentation.filterLQ2x.checked() && width <= 256 && height <= 240) {
size = &Filter::LQ2x::size;
render = &Filter::LQ2x::render;
}
if(presentation.filterHQ2x.checked() && width <= 256 && height <= 240) {
size = &Filter::HQ2x::size;
render = &Filter::HQ2x::render;
}
if(presentation.filterNTSC_RF.checked() && width <= 512 && height <= 480) {
size = &Filter::NTSC_RF::size;
render = &Filter::NTSC_RF::render;
}
if(presentation.filterNTSC_Composite.checked() && width <= 512 && height <= 480) {
size = &Filter::NTSC_Composite::size;
render = &Filter::NTSC_Composite::render;
}
if(presentation.filterNTSC_SVideo.checked() && width <= 512 && height <= 480) {
size = &Filter::NTSC_SVideo::size;
render = &Filter::NTSC_SVideo::render;
}
if(presentation.filterNTSC_RGB.checked() && width <= 512 && height <= 480) {
size = &Filter::NTSC_RGB::size;
render = &Filter::NTSC_RGB::render;
}
size(width, height);
return render;
}

View File

@ -207,123 +207,28 @@ auto Program::load(uint id, string name, string type, vector<string> options) ->
return {}; return {};
} }
auto Program::videoFrame(const uint16* data, uint pitch, uint width, uint height) -> void { auto Program::videoFrame(const uint16_t* data, uint pitch, uint width, uint height, uint scale) -> void {
//this relies on the UI only running between Emulator::Scheduler::Event::Frame events //this relies on the UI only running between Emulator::Scheduler::Event::Frame events
//this will always be the case; so we can avoid an unnecessary copy or one-frame delay here //this will always be the case; so we can avoid an unnecessary copy or one-frame delay here
//if the core were to exit between a frame event, the next frame might've been only partially rendered //if the core were to exit between a frame event, the next frame might've been only partially rendered
screenshot.data = data; screenshot.data = data;
screenshot.pitch = pitch; screenshot.pitch = pitch;
screenshot.width = width; screenshot.width = width;
screenshot.height = height; screenshot.height = height;
screenshot.scale = scale;
pitch >>= 1; pitch >>= 1;
if(!presentation.showOverscanArea.checked()) { if(!settings.video.overscan) {
data += (height / 30) * pitch; uint multiplier = height / 240;
height -= height / 15; data += 8 * multiplier * pitch;
height -= 16 * multiplier;
} }
uint videoWidth = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0); uint outputWidth, outputHeight;
uint videoHeight = (settings.video.overscan ? 240.0 : 224.0); viewportSize(outputWidth, outputHeight, scale);
auto [viewportWidth, viewportHeight] = video.size(); uint filterWidth = width, filterHeight = height;
auto filterRender = filterSelect(filterWidth, filterHeight, scale);
uint multiplierX = viewportWidth / videoWidth;
uint multiplierY = viewportHeight / videoHeight;
uint multiplier = min(multiplierX, multiplierY);
uint outputWidth = videoWidth * multiplier;
uint outputHeight = videoHeight * multiplier;
if(multiplier == 0 || settings.video.output == "Scale") {
float multiplierX = (float)viewportWidth / (float)videoWidth;
float multiplierY = (float)viewportHeight / (float)videoHeight;
float multiplier = min(multiplierX, multiplierY);
outputWidth = videoWidth * multiplier;
outputHeight = videoHeight * multiplier;
}
if(settings.video.output == "Stretch") {
outputWidth = viewportWidth;
outputHeight = viewportHeight;
}
void (*filterSize)(uint& width, uint& height) = &Filter::None::size;
void (*filterRender)(uint32_t* colortable, uint32_t* output, uint outpitch, const uint16_t* input, uint pitch, uint width, uint height) = &Filter::None::render;
if(presentation.filterScanlinesLight.checked() && height <= 240) {
filterSize = &Filter::ScanlinesLight::size;
filterRender = &Filter::ScanlinesLight::render;
}
if(presentation.filterScanlinesDark.checked() && height <= 240) {
filterSize = &Filter::ScanlinesDark::size;
filterRender = &Filter::ScanlinesDark::render;
}
if(presentation.filterScanlinesBlack.checked() && height <= 240) {
filterSize = &Filter::ScanlinesBlack::size;
filterRender = &Filter::ScanlinesBlack::render;
}
if(presentation.filterPixellate2x.checked()) {
filterSize = &Filter::Pixellate2x::size;
filterRender = &Filter::Pixellate2x::render;
}
if(presentation.filterScale2x.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::Scale2x::size;
filterRender = &Filter::Scale2x::render;
}
if(presentation.filter2xSaI.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::_2xSaI::size;
filterRender = &Filter::_2xSaI::render;
}
if(presentation.filterSuper2xSaI.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::Super2xSaI::size;
filterRender = &Filter::Super2xSaI::render;
}
if(presentation.filterSuperEagle.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::SuperEagle::size;
filterRender = &Filter::SuperEagle::render;
}
if(presentation.filterLQ2x.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::LQ2x::size;
filterRender = &Filter::LQ2x::render;
}
if(presentation.filterHQ2x.checked() && width <= 256 && height <= 240) {
filterSize = &Filter::HQ2x::size;
filterRender = &Filter::HQ2x::render;
}
if(presentation.filterNTSC_RF.checked()) {
filterSize = &Filter::NTSC_RF::size;
filterRender = &Filter::NTSC_RF::render;
}
if(presentation.filterNTSC_Composite.checked()) {
filterSize = &Filter::NTSC_Composite::size;
filterRender = &Filter::NTSC_Composite::render;
}
if(presentation.filterNTSC_SVideo.checked()) {
filterSize = &Filter::NTSC_SVideo::size;
filterRender = &Filter::NTSC_SVideo::render;
}
if(presentation.filterNTSC_RGB.checked()) {
filterSize = &Filter::NTSC_RGB::size;
filterRender = &Filter::NTSC_RGB::render;
}
uint filterWidth = width;
uint filterHeight = height;
filterSize(filterWidth, filterHeight);
if(auto [output, length] = video.acquire(filterWidth, filterHeight); output) { if(auto [output, length] = video.acquire(filterWidth, filterHeight); output) {
filterRender(palette, output, length, (const uint16_t*)data, pitch << 1, width, height); filterRender(palette, output, length, (const uint16_t*)data, pitch << 1, width, height);

View File

@ -11,6 +11,7 @@
#include "utility.cpp" #include "utility.cpp"
#include "patch.cpp" #include "patch.cpp"
#include "hacks.cpp" #include "hacks.cpp"
#include "filter.cpp"
#include "viewport.cpp" #include "viewport.cpp"
Program program; Program program;
@ -78,7 +79,7 @@ auto Program::main() -> void {
audio.clear(); audio.clear();
if(!Application::modal()) { if(!Application::modal()) {
usleep(20 * 1000); usleep(20 * 1000);
refreshViewport(); viewportRefresh();
} }
return; return;
} }

View File

@ -9,7 +9,7 @@ struct Program : Lock, Emulator::Platform {
//platform.cpp //platform.cpp
auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override; auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override;
auto load(uint id, string name, string type, vector<string> options = {}) -> Emulator::Platform::Load override; auto load(uint id, string name, string type, vector<string> options = {}) -> Emulator::Platform::Load override;
auto videoFrame(const uint16* data, uint pitch, uint width, uint height) -> void override; auto videoFrame(const uint16_t* data, uint pitch, uint width, uint height, uint scale) -> void override;
auto audioFrame(const double* samples, uint channels) -> void override; auto audioFrame(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override; auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void override; auto inputRumble(uint port, uint device, uint input, bool enable) -> void override;
@ -104,8 +104,12 @@ struct Program : Lock, Emulator::Platform {
auto hackPatchMemory(vector<uint8_t>& data) -> void; auto hackPatchMemory(vector<uint8_t>& data) -> void;
auto hackOverclockSuperFX() -> void; auto hackOverclockSuperFX() -> void;
//filter.cpp
auto filterSelect(uint& width, uint& height, uint scale) -> Filter::Render;
//viewport.cpp //viewport.cpp
auto refreshViewport() -> void; auto viewportSize(uint& width, uint& height, uint scale) -> void;
auto viewportRefresh() -> void;
public: public:
struct Game { struct Game {
@ -142,12 +146,14 @@ public:
vector<string> gameQueue; vector<string> gameQueue;
uint32_t palette[32768]; uint32_t palette[32768];
uint32_t palettePaused[32768];
struct Screenshot { struct Screenshot {
const uint16* data = nullptr; const uint16_t* data = nullptr;
uint pitch = 0; uint pitch = 0;
uint width = 0; uint width = 0;
uint height = 0; uint height = 0;
uint scale = 0;
} screenshot; } screenshot;
bool frameAdvance = false; bool frameAdvance = false;

View File

@ -99,6 +99,15 @@ auto Program::updateVideoPalette() -> void {
case 24: palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break; case 24: palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
case 30: palette[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break; case 30: palette[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break;
} }
r >>= 1;
g >>= 1;
b >>= 1;
switch(depth) {
case 24: palettePaused[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
case 30: palettePaused[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break;
}
} }
emulator->configure("Video/ColorEmulation", false); emulator->configure("Video/ColorEmulation", false);

View File

@ -1,4 +1,113 @@
uint16_t SnowData[] = { extern uint16_t SnowData[800];
extern uint8_t SnowVelDist[800];
auto Program::viewportSize(uint& width, uint& height, uint scale) -> void {
uint videoWidth = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0);
uint videoHeight = (settings.video.overscan ? 240.0 : 224.0);
auto [viewportWidth, viewportHeight] = video.size();
uint multiplierX = viewportWidth / videoWidth;
uint multiplierY = viewportHeight / videoHeight;
uint multiplier = min(multiplierX, multiplierY);
uint outputWidth = videoWidth * multiplier;
uint outputHeight = videoHeight * multiplier;
if(multiplier == 0 || settings.video.output == "Scale") {
float multiplierX = (float)viewportWidth / (float)videoWidth;
float multiplierY = (float)viewportHeight / (float)videoHeight;
float multiplier = min(multiplierX, multiplierY);
outputWidth = videoWidth * multiplier;
outputHeight = videoHeight * multiplier;
}
if(settings.video.output == "Stretch") {
outputWidth = viewportWidth;
outputHeight = viewportHeight;
}
width = outputWidth;
height = outputHeight;
}
auto Program::viewportRefresh() -> void {
if(!emulator->loaded() && !settings.video.snow) return;
static uint32_t SnowMover = 0;
static uint32_t SnowTimer = 18;
static uint32_t NumSnow = 0;
if(settings.video.snow) SnowMover++;
static const uint16_t nullData[256 * 240] = {};
auto data = nullData;
uint pitch = 512;
uint width = 256;
uint height = 240;
uint scale = 1;
if(emulator->loaded() && screenshot.data) {
data = screenshot.data;
pitch = screenshot.pitch;
width = screenshot.width;
height = screenshot.height;
scale = screenshot.scale;
}
if(!settings.video.overscan) {
uint multiplier = height / 240;
data += 8 * multiplier * (pitch >> 1);
height -= 16 * multiplier;
}
uint outputWidth, outputHeight;
viewportSize(outputWidth, outputHeight, scale);
uint filterWidth = width, filterHeight = height;
auto filterRender = filterSelect(filterWidth, filterHeight, scale);
if(auto [output, length] = video.acquire(filterWidth, filterHeight); output) {
filterRender(palettePaused, output, length, (const uint16_t*)data, pitch, width, height);
length >>= 2;
if(settings.video.snow) {
uint32_t i = 0;
float snowX = filterWidth / 256.0;
float snowY = filterHeight / 256.0;
do {
uint x = uint8_t(SnowData[i * 2 + 0] >> 8) * snowX;
uint y = uint8_t(SnowData[i * 2 + 1] >> 8) * snowY;
if((SnowVelDist[i * 2] & 8) != 0) {
uint8_t color = 228 + (SnowVelDist[i * 2] & 0x03);
if(y) output[y * length + x] = color << 16 | color << 8 | color << 0;
}
} while(++i != 200);
for(; SnowMover != 0; --SnowMover) {
if(--SnowTimer == 0) {
++NumSnow;
SnowTimer = 18;
}
uint32_t i = 0;
uint32_t n = NumSnow;
while(n-- != 0) {
SnowData[i * 2 + 0] += SnowVelDist[i * 2 + 0] + 4 * (uint8_t)(100 - 50);
SnowData[i * 2 + 1] += SnowVelDist[i * 2 + 1] + 256;
if(SnowData[i * 2 + 1] <= 0x200) {
SnowVelDist[i * 2] |= 8;
}
++i;
}
}
}
video.release();
video.output(outputWidth, outputHeight);
}
}
uint16_t SnowData[800] = {
161, 251, 115, 211, 249, 87, 128, 101, 232, 176, 51, 180, 108, 193, 224, 112, 254, 159, 102, 238, 161, 251, 115, 211, 249, 87, 128, 101, 232, 176, 51, 180, 108, 193, 224, 112, 254, 159, 102, 238,
223, 123, 218, 42, 173, 160, 143, 170, 64, 1, 174, 29, 34, 187, 194, 199, 40, 89, 232, 32, 223, 123, 218, 42, 173, 160, 143, 170, 64, 1, 174, 29, 34, 187, 194, 199, 40, 89, 232, 32,
7, 195, 141, 67, 216, 48, 234, 1, 243, 116, 164, 182, 146, 136, 66, 70, 36, 43, 98, 208, 7, 195, 141, 67, 216, 48, 234, 1, 243, 116, 164, 182, 146, 136, 66, 70, 36, 43, 98, 208,
@ -41,7 +150,7 @@ uint16_t SnowData[] = {
140, 81, 118, 81, 63, 193, 173, 228, 214, 78, 124, 123, 222, 149, 9, 242, 0, 128, 194, 110 140, 81, 118, 81, 63, 193, 173, 228, 214, 78, 124, 123, 222, 149, 9, 242, 0, 128, 194, 110
}; };
uint8_t SnowVelDist[] = { uint8_t SnowVelDist[800] = {
57, 92, 100, 19, 100, 184, 238, 225, 55, 240, 255, 221, 215, 105, 226, 153, 164, 41, 22, 93, 57, 92, 100, 19, 100, 184, 238, 225, 55, 240, 255, 221, 215, 105, 226, 153, 164, 41, 22, 93,
176, 203, 155, 199, 244, 52, 233, 219, 110, 227, 229, 227, 152, 240, 83, 248, 226, 31, 163, 22, 176, 203, 155, 199, 244, 52, 233, 219, 110, 227, 229, 227, 152, 240, 83, 248, 226, 31, 163, 22,
28, 156, 18, 10, 248, 67, 123, 167, 25, 138, 90, 10, 79, 107, 208, 229, 248, 233, 185, 10, 28, 156, 18, 10, 248, 67, 123, 167, 25, 138, 90, 10, 79, 107, 208, 229, 248, 233, 185, 10,
@ -83,119 +192,3 @@ uint8_t SnowVelDist[] = {
242, 37, 89, 73, 151, 162, 139, 189, 131, 209, 221, 96, 107, 144, 175, 79, 199, 123, 98, 138, 242, 37, 89, 73, 151, 162, 139, 189, 131, 209, 221, 96, 107, 144, 175, 79, 199, 123, 98, 138,
226, 86, 221, 254, 72, 14, 126, 180, 200, 171, 85, 94, 120, 124, 196, 225, 150, 57, 219, 158 226, 86, 221, 254, 72, 14, 126, 180, 200, 171, 85, 94, 120, 124, 196, 225, 150, 57, 219, 158
}; };
auto Program::refreshViewport() -> void {
if(!emulator->loaded() && !settings.video.snow) return;
static uint32_t SnowMover = 0;
static uint32_t SnowTimer = 18;
static uint32_t NumSnow = 0;
if(settings.video.snow) SnowMover++;
auto [viewportWidth, viewportHeight] = video.size();
uint videoWidth = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0);
uint videoHeight = (settings.video.overscan ? 240.0 : 224.0);
uint multiplierX = viewportWidth / videoWidth;
uint multiplierY = viewportHeight / videoHeight;
uint multiplier = min(multiplierX, multiplierY);
uint outputWidth = videoWidth * multiplier;
uint outputHeight = videoHeight * multiplier;
if(multiplier == 0 || settings.video.output == "Scale") {
float multiplierX = (float)viewportWidth / (float)videoWidth;
float multiplierY = (float)viewportHeight / (float)videoHeight;
float multiplier = min(multiplierX, multiplierY);
outputWidth = videoWidth * multiplier;
outputHeight = videoHeight * multiplier;
}
if(settings.video.output == "Stretch") {
outputWidth = viewportWidth;
outputHeight = viewportHeight;
}
uint width = 256;
uint height = 240;
if(emulator->loaded() && screenshot.data) {
width = screenshot.width;
height = screenshot.height;
}
if(!settings.video.overscan) {
if(height == 240) height = 224;
if(height == 480) height = 448;
}
if(auto [output, length] = video.acquire(width, height); output) {
length >>= 2;
if(!emulator->loaded() || !screenshot.data) {
for(uint y : range(height)) {
memory::fill<uint32>(output + y * length, length, 0xff000000);
}
} else {
for(uint y : range(height)) {
auto source = screenshot.data + y * (screenshot.pitch >> 1);
if(!settings.video.overscan) source += 8 * (screenshot.pitch >> 1);
auto target = output + y * length;
for(uint x : range(width)) {
auto color = *source++;
uint a = 255;
uint r = (color >> 10) & 31;
uint g = (color >> 5) & 31;
uint b = (color >> 0) & 31;
r = r << 3 | r >> 2;
g = g << 3 | g >> 2;
b = b << 3 | b >> 2;
r >>= 1;
g >>= 1;
b >>= 1;
*target++ = a << 24 | r << 16 | g << 8 | b << 0;
}
}
}
if(settings.video.snow) {
uint32_t i = 0;
do {
uint y = uint8_t(SnowData[i * 2 + 1] >> 8);
uint x = uint8_t(SnowData[i * 2 + 0] >> 8);
if(width > 256) x <<= 1;
if(height > 240) y <<= 1;
if((SnowVelDist[i * 2] & 8) != 0) {
uint8_t color = 228 + (SnowVelDist[i * 2] & 0x03);
if(y > 0 && y < height) output[y * width + x] = color << 16 | color << 8 | color << 0;
}
} while(++i != 200);
for(; SnowMover != 0; --SnowMover) {
if(--SnowTimer == 0) {
++NumSnow;
SnowTimer = 18;
}
uint32_t i = 0;
uint32_t n = NumSnow;
while(n-- != 0) {
SnowData[i * 2 + 0] += SnowVelDist[i * 2 + 0] + 4 * (uint8_t)(100 - 50);
SnowData[i * 2 + 1] += SnowVelDist[i * 2 + 1] + 256;
if(SnowData[i * 2 + 1] <= 0x200) {
SnowVelDist[i * 2] |= 8;
}
++i;
}
}
}
video.release();
video.output(outputWidth, outputHeight);
}
}

View File

@ -115,6 +115,9 @@ auto CheatEditor::create() -> void {
cheatList.setHeadered(); cheatList.setHeadered();
cheatList.setSortable(); cheatList.setSortable();
cheatList.onActivate([&] { cheatList.onActivate([&] {
//kind of a hack: toggling a cheat code twice quickly (onToggle) will call onActivate.
//do not trigger the CheatWindow unless it's been at least two seconds since a cheat code was last toggled on or off.
if(chrono::timestamp() - activateTimeout < 2) return;
editButton.doActivate(); editButton.doActivate();
}); });
cheatList.onChange([&] { cheatList.onChange([&] {
@ -123,6 +126,7 @@ auto CheatEditor::create() -> void {
removeButton.setEnabled(batched.size() >= 1); removeButton.setEnabled(batched.size() >= 1);
}); });
cheatList.onToggle([&](TableViewCell cell) { cheatList.onToggle([&](TableViewCell cell) {
activateTimeout = chrono::timestamp();
if(auto item = cell->parentTableViewItem()) { if(auto item = cell->parentTableViewItem()) {
cheats[item->offset()].enable = cell.checked(); cheats[item->offset()].enable = cell.checked();
synchronizeCodes(); synchronizeCodes();

View File

@ -59,6 +59,7 @@ struct CheatEditor : TabFrameItem {
public: public:
vector<Cheat> cheats; vector<Cheat> cheats;
uint64_t activateTimeout = 0;
VerticalLayout layout{this}; VerticalLayout layout{this};
TableView cheatList{&layout, Size{~0, ~0}}; TableView cheatList{&layout, Size{~0, ~0}};