diff --git a/bsnes/GNUmakefile b/bsnes/GNUmakefile index 7586ee79..5b533cc0 100644 --- a/bsnes/GNUmakefile +++ b/bsnes/GNUmakefile @@ -4,31 +4,38 @@ build := performance openmp := true 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 include $(nall.path)/GNUmakefile ifeq ($(platform),windows) ifeq ($(binary),application) - link += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 - link += -Wl,-enable-auto-import - link += -Wl,-enable-runtime-pseudo-reloc + options += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 + options += -Wl,-enable-auto-import + options += -Wl,-enable-runtime-pseudo-reloc else ifeq ($(binary),library) - link += -shared + options += -shared endif else ifeq ($(platform),macos) ifeq ($(binary),application) else ifeq ($(binary),library) flags += -fPIC - link += -dynamiclib + options += -dynamiclib endif else ifneq ($(filter $(platform),linux bsd),) ifeq ($(binary),application) flags += -march=native - link += -Wl,-export-dynamic - link += -lX11 -lXext + options += -Wl,-export-dynamic + options += -lX11 -lXext else ifeq ($(binary),library) flags += -fPIC - link += -shared + options += -shared endif else $(error "unsupported platform") diff --git a/bsnes/emulator/cheat.hpp b/bsnes/emulator/cheat.hpp index 1ef898e4..20f194b7 100644 --- a/bsnes/emulator/cheat.hpp +++ b/bsnes/emulator/cheat.hpp @@ -4,9 +4,19 @@ namespace Emulator { struct Cheat { 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; - maybe comp; + maybe compare; + bool enable; + uint restore; }; explicit operator bool() const { @@ -17,8 +27,8 @@ struct Cheat { codes.reset(); } - auto append(uint addr, uint data, maybe comp = {}) -> void { - codes.append({addr, data, comp}); + auto append(uint address, uint data, maybe compare = {}) -> void { + codes.append({address, data, compare}); } auto assign(const vector& list) -> void { @@ -32,16 +42,15 @@ struct Cheat { } } - auto find(uint addr, uint comp) -> maybe { + auto find(uint address, uint compare) -> maybe { 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 nothing; } -private: vector codes; }; diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index e633eb7e..93b574b9 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -31,7 +31,7 @@ using namespace nall; namespace Emulator { 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 License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/bsnes/emulator/platform.hpp b/bsnes/emulator/platform.hpp index c427f6c0..35c829f9 100644 --- a/bsnes/emulator/platform.hpp +++ b/bsnes/emulator/platform.hpp @@ -16,7 +16,7 @@ struct Platform { 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 load(uint id, string name, string type, vector 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 inputPoll(uint port, uint device, uint input) -> int16 { return 0; } virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {} diff --git a/bsnes/emulator/video/video.cpp b/bsnes/emulator/video/video.cpp index e83ce2db..35e03a19 100644 --- a/bsnes/emulator/video/video.cpp +++ b/bsnes/emulator/video/video.cpp @@ -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); } } diff --git a/bsnes/filter/filter.hpp b/bsnes/filter/filter.hpp index 0b46b512..ac6b1990 100644 --- a/bsnes/filter/filter.hpp +++ b/bsnes/filter/filter.hpp @@ -2,6 +2,12 @@ #include +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 { auto size(uint& width, uint& height) -> void; auto render( diff --git a/bsnes/sfc/GNUmakefile b/bsnes/sfc/GNUmakefile index b8f49690..84c1004e 100644 --- a/bsnes/sfc/GNUmakefile +++ b/bsnes/sfc/GNUmakefile @@ -3,15 +3,7 @@ processors += wdc65816 spc700 arm7tdmi gsu hg51b upd96050 objects += sfc-interface sfc-system sfc-controller objects += sfc-cartridge sfc-memory objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu sfc-ppu-fast -objects += sfc-expansion sfc-satellaview sfc-21fx -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 +objects += sfc-expansion sfc-coprocessor sfc-slot obj/sfc-interface.o: sfc/interface/interface.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-expansion.o: sfc/expansion/expansion.cpp -obj/sfc-satellaview.o: sfc/expansion/satellaview/satellaview.cpp -obj/sfc-21fx.o: sfc/expansion/21fx/21fx.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 +obj/sfc-coprocessor.o: sfc/coprocessor/coprocessor.cpp +obj/sfc-slot.o: sfc/slot/slot.cpp diff --git a/bsnes/sfc/controller/controller.cpp b/bsnes/sfc/controller/controller.cpp index 5e087e1d..a68a0378 100644 --- a/bsnes/sfc/controller/controller.cpp +++ b/bsnes/sfc/controller/controller.cpp @@ -11,24 +11,9 @@ ControllerPort controllerPort2; #include "justifier/justifier.cpp" Controller::Controller(uint port) : port(port) { - if(!handle()) create(Controller::Enter, 1); } 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 { @@ -61,11 +46,6 @@ auto ControllerPort::connect(uint deviceID) -> void { case ID::Device::Justifier: device = new Justifier(port, false); 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 { diff --git a/bsnes/sfc/controller/controller.hpp b/bsnes/sfc/controller/controller.hpp index 508bdb46..fae178f6 100644 --- a/bsnes/sfc/controller/controller.hpp +++ b/bsnes/sfc/controller/controller.hpp @@ -11,12 +11,10 @@ // 6: iobit $4201.d6 write; $4213.d6 read $4201.d7 write; $4213.d7 read // 7: gnd -struct Controller : Thread { +struct Controller { Controller(uint port); virtual ~Controller(); - static auto Enter() -> void; - virtual auto main() -> void; auto iobit() -> bool; auto iobit(bool data) -> void; virtual auto data() -> uint2 { return 0; } diff --git a/bsnes/sfc/controller/justifier/justifier.cpp b/bsnes/sfc/controller/justifier/justifier.cpp index 29365cef..9c2618d1 100644 --- a/bsnes/sfc/controller/justifier/justifier.cpp +++ b/bsnes/sfc/controller/justifier/justifier.cpp @@ -3,7 +3,6 @@ Controller(port), chained(chained), device(!chained ? ID::Device::Justifier : ID::Device::Justifiers) { - create(Controller::Enter, system.cpuFrequency()); latched = 0; counter = 0; active = 0; @@ -37,6 +36,7 @@ Justifier::~Justifier() { Emulator::video.removeSprite(player2.sprite); } +/* auto Justifier::main() -> void { uint next = cpu.vcounter() * 1364 + cpu.hcounter(); @@ -78,6 +78,7 @@ auto Justifier::main() -> void { step(2); synchronize(cpu); } +*/ auto Justifier::data() -> uint2 { if(counter >= 32) return 1; diff --git a/bsnes/sfc/controller/justifier/justifier.hpp b/bsnes/sfc/controller/justifier/justifier.hpp index a4d8ece6..6a70ccf6 100644 --- a/bsnes/sfc/controller/justifier/justifier.hpp +++ b/bsnes/sfc/controller/justifier/justifier.hpp @@ -6,7 +6,6 @@ struct Justifier : Controller { Justifier(uint port, bool chained); ~Justifier(); - auto main() -> void; auto data() -> uint2; auto latch(bool data) -> void; diff --git a/bsnes/sfc/controller/super-scope/super-scope.cpp b/bsnes/sfc/controller/super-scope/super-scope.cpp index 6dacb712..58ef1e8b 100644 --- a/bsnes/sfc/controller/super-scope/super-scope.cpp +++ b/bsnes/sfc/controller/super-scope/super-scope.cpp @@ -11,7 +11,6 @@ //Note that no commercial game ever utilizes a Super Scope in port 1. SuperScope::SuperScope(uint port) : Controller(port) { - create(Controller::Enter, system.cpuFrequency()); sprite = Emulator::video.createSprite(32, 32); sprite->setPixels(Resource::Sprite::CrosshairGreen); @@ -39,6 +38,7 @@ SuperScope::~SuperScope() { Emulator::video.removeSprite(sprite); } +/* auto SuperScope::main() -> void { uint next = cpu.vcounter() * 1364 + cpu.hcounter(); @@ -68,6 +68,7 @@ auto SuperScope::main() -> void { step(2); synchronize(cpu); } +*/ auto SuperScope::data() -> uint2 { if(counter >= 8) return 1; diff --git a/bsnes/sfc/controller/super-scope/super-scope.hpp b/bsnes/sfc/controller/super-scope/super-scope.hpp index 6bb23bc8..32590667 100644 --- a/bsnes/sfc/controller/super-scope/super-scope.hpp +++ b/bsnes/sfc/controller/super-scope/super-scope.hpp @@ -8,7 +8,6 @@ struct SuperScope : Controller { SuperScope(uint port); ~SuperScope(); - auto main() -> void; auto data() -> uint2; auto latch(bool data) -> void; diff --git a/bsnes/sfc/coprocessor/coprocessor.cpp b/bsnes/sfc/coprocessor/coprocessor.cpp new file mode 100644 index 00000000..8fe8ec44 --- /dev/null +++ b/bsnes/sfc/coprocessor/coprocessor.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include diff --git a/bsnes/sfc/cpu/cpu.cpp b/bsnes/sfc/cpu/cpu.cpp index a7677eab..53b87529 100644 --- a/bsnes/sfc/cpu/cpu.cpp +++ b/bsnes/sfc/cpu/cpu.cpp @@ -30,12 +30,12 @@ auto CPU::main() -> void { interrupt(); } else if(status.resetPending) { status.resetPending = false; - step(132); + for(uint repeat : range(22)) step<6,0>(); //step(132); r.vector = 0xfffc; interrupt(); } else if(status.powerPending) { 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(1) = bus.read(0xfffd, r.mdr); } diff --git a/bsnes/sfc/cpu/cpu.hpp b/bsnes/sfc/cpu/cpu.hpp index 82e38f96..d1d745fe 100644 --- a/bsnes/sfc/cpu/cpu.hpp +++ b/bsnes/sfc/cpu/cpu.hpp @@ -24,7 +24,6 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { auto idle() -> void override; auto read(uint24 addr) -> uint8 override; auto write(uint24 addr, uint8 data) -> void override; - alwaysinline auto wait(uint24 addr) const -> uint; auto readDisassembler(uint24 addr) -> uint8 override; //io.cpp @@ -42,8 +41,9 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { inline auto dmaCounter() const -> uint; inline auto joypadCounter() const -> uint; - auto step(uint clocks) -> void; - inline auto stepIdle(uint clocks) -> void; + alwaysinline auto stepOnce() -> void; + alwaysinline auto step(uint clocks) -> void; + template auto step() -> void; auto scanline() -> void; alwaysinline auto aluEdge() -> void; @@ -67,7 +67,6 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { uint8 wram[128 * 1024]; vector coprocessors; - vector peripherals; private: uint version = 2; //allowed: 1, 2 @@ -146,7 +145,7 @@ private: uint9 vtime = 0x1ff; //$420d - uint romSpeed = 8; + uint1 fastROM = 0; //$4214-$4217 uint16 rddiv; @@ -167,7 +166,7 @@ private: struct Channel { //dma.cpp - inline auto step(uint clocks) -> void; + template inline auto step() -> void; inline auto edge() -> void; inline auto validA(uint24 address) -> bool; diff --git a/bsnes/sfc/cpu/dma.cpp b/bsnes/sfc/cpu/dma.cpp index a2ed19e4..ea318dbe 100644 --- a/bsnes/sfc/cpu/dma.cpp +++ b/bsnes/sfc/cpu/dma.cpp @@ -14,7 +14,7 @@ auto CPU::hdmaActive() -> bool { } auto CPU::dmaRun() -> void { - step(8); + step<8,0>(); dmaEdge(); for(auto& channel : channels) channel.dmaRun(); status.irqLock = true; @@ -25,13 +25,13 @@ auto CPU::hdmaReset() -> void { } auto CPU::hdmaSetup() -> void { - step(8); + step<8,0>(); for(auto& channel : channels) channel.hdmaSetup(); status.irqLock = true; } auto CPU::hdmaRun() -> void { - step(8); + step<8,0>(); for(auto& channel : channels) channel.hdmaTransfer(); for(auto& channel : channels) channel.hdmaAdvance(); status.irqLock = true; @@ -39,7 +39,8 @@ auto CPU::hdmaRun() -> void { // -auto CPU::Channel::step(uint clocks) -> void { return cpu.step(clocks); } +template +auto CPU::Channel::step() -> void { return cpu.step(); } auto CPU::Channel::edge() -> void { return cpu.dmaEdge(); } 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 { - step(4); + step<4,1>(); cpu.r.mdr = validA(address) ? bus.read(address, cpu.r.mdr) : (uint8)0x00; - step(4); + step<4,1>(); return cpu.r.mdr; } 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; - step(4); + step<4,1>(); return cpu.r.mdr; } @@ -97,7 +98,7 @@ auto CPU::Channel::transfer(uint24 addressA, uint2 index) -> void { auto CPU::Channel::dmaRun() -> void { if(!dmaEnable) return; - step(8); + step<8,0>(); edge(); uint2 index = 0; diff --git a/bsnes/sfc/cpu/io.cpp b/bsnes/sfc/cpu/io.cpp index b976ecd2..8d47049c 100644 --- a/bsnes/sfc/cpu/io.cpp +++ b/bsnes/sfc/cpu/io.cpp @@ -203,7 +203,7 @@ auto CPU::writeCPU(uint24 addr, uint8 data) -> void { return; case 0x420d: //MEMSEL - io.romSpeed = data.bit(0) ? 6 : 8; + io.fastROM = data.bit(0); return; } diff --git a/bsnes/sfc/cpu/memory.cpp b/bsnes/sfc/cpu/memory.cpp index 4047f998..217fc805 100644 --- a/bsnes/sfc/cpu/memory.cpp +++ b/bsnes/sfc/cpu/memory.cpp @@ -1,17 +1,45 @@ auto CPU::idle() -> void { + status.irqLock = false; status.clockCount = 6; dmaEdge(); - stepIdle(6); + step<6,0>(); aluEdge(); } auto CPU::read(uint24 address) -> uint8 { - status.clockCount = wait(address); - dmaEdge(); - r.mar = address; - step(status.clockCount - 4); + status.irqLock = false; + + if(address & 0x408000) { + 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); - step(4); + step<4,0>(); aluEdge(); //$00-3f,80-bf:4000-43ff reads are internal to CPU, and do not update the MDR 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 { + status.irqLock = false; 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) return address & 0x800000 ? io.romSpeed : 8; - if(address + 0x6000 & 0x4000) return 8; - if(address - 0x4000 & 0x7e00) return 6; - return 12; + if(address & 0x408000) { + if(address & 0x800000 && io.fastROM) { + status.clockCount = 6; + dmaEdge(); + 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 { diff --git a/bsnes/sfc/cpu/serialization.cpp b/bsnes/sfc/cpu/serialization.cpp index 66e04b7e..e2cfe57f 100644 --- a/bsnes/sfc/cpu/serialization.cpp +++ b/bsnes/sfc/cpu/serialization.cpp @@ -69,7 +69,7 @@ auto CPU::serialize(serializer& s) -> void { s.integer(io.htime); s.integer(io.vtime); - s.integer(io.romSpeed); + s.integer(io.fastROM); s.integer(io.rddiv); s.integer(io.rdmpy); diff --git a/bsnes/sfc/cpu/timing.cpp b/bsnes/sfc/cpu/timing.cpp index ed47585d..b6763fb2 100644 --- a/bsnes/sfc/cpu/timing.cpp +++ b/bsnes/sfc/cpu/timing.cpp @@ -17,53 +17,48 @@ auto CPU::joypadCounter() const -> uint { return counter.cpu & 255; } -auto CPU::step(uint clocks) -> void { - status.irqLock = false; - uint ticks = clocks >> 1; - while(ticks--) { - counter.cpu += 2; - 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::stepOnce() -> void { + counter.cpu += 2; + tick(); + if(hcounter() & 2) pollInterrupts(); + if(joypadCounter() == 0) joypadEdge(); } -auto CPU::stepIdle(uint clocks) -> void { - status.irqLock = false; - uint ticks = clocks >> 1; - while(ticks--) { - counter.cpu += 2; - tick(); - if(hcounter() & 2) pollInterrupts(); - if(joypadCounter() == 0) joypadEdge(); - } - - Thread::step(clocks); +template +auto CPU::step() -> void { + static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12); + if constexpr(Clocks >= 2) stepOnce(); + if constexpr(Clocks >= 4) stepOnce(); + if constexpr(Clocks >= 6) stepOnce(); + if constexpr(Clocks >= 8) stepOnce(); + if constexpr(Clocks >= 10) stepOnce(); + if constexpr(Clocks >= 12) stepOnce(); + Thread::step(Clocks); 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(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); + status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); 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>(); } } diff --git a/bsnes/sfc/dsp/dsp.cpp b/bsnes/sfc/dsp/dsp.cpp index 33029e1e..324ac5a0 100644 --- a/bsnes/sfc/dsp/dsp.cpp +++ b/bsnes/sfc/dsp/dsp.cpp @@ -19,7 +19,7 @@ void DSP::main() { signed count = spc_dsp.sample_count(); if(count > 0) { 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); } diff --git a/bsnes/sfc/expansion/expansion.cpp b/bsnes/sfc/expansion/expansion.cpp index fc0aad87..497a8f13 100644 --- a/bsnes/sfc/expansion/expansion.cpp +++ b/bsnes/sfc/expansion/expansion.cpp @@ -1,24 +1,15 @@ #include +#include +//#include namespace SuperFamicom { ExpansionPort expansionPort; Expansion::Expansion() { - if(!handle()) create(Expansion::Enter, 1); } 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: case ID::Device::None: device = new Expansion; 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 { diff --git a/bsnes/sfc/expansion/expansion.hpp b/bsnes/sfc/expansion/expansion.hpp index 194ecad5..78e0e0e3 100644 --- a/bsnes/sfc/expansion/expansion.hpp +++ b/bsnes/sfc/expansion/expansion.hpp @@ -1,8 +1,6 @@ struct Expansion : Thread { Expansion(); virtual ~Expansion(); - static auto Enter() -> void; - virtual auto main() -> void; }; struct ExpansionPort { @@ -18,4 +16,4 @@ struct ExpansionPort { extern ExpansionPort expansionPort; #include -#include +//#include diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index 2167c6a4..93827edc 100644 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -245,9 +245,57 @@ auto Interface::unserialize(serializer& s) -> bool { } auto Interface::cheats(const vector& list) -> void { - cheat.reset(); 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 { diff --git a/bsnes/sfc/memory/memory-inline.hpp b/bsnes/sfc/memory/memory-inline.hpp index 1eccb359..26230679 100644 --- a/bsnes/sfc/memory/memory-inline.hpp +++ b/bsnes/sfc/memory/memory-inline.hpp @@ -24,12 +24,7 @@ auto Bus::reduce(uint addr, uint mask) -> uint { } auto Bus::read(uint24 addr, uint8 data) -> uint8 { - data = 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; + return reader[lookup[addr]](target[addr], data); } auto Bus::write(uint24 addr, uint8 data) -> void { diff --git a/bsnes/sfc/memory/memory.cpp b/bsnes/sfc/memory/memory.cpp index ca4fffc8..4872e86e 100644 --- a/bsnes/sfc/memory/memory.cpp +++ b/bsnes/sfc/memory/memory.cpp @@ -2,6 +2,7 @@ namespace SuperFamicom { +bool Memory::GlobalWriteEnable = false; Bus bus; Bus::~Bus() { diff --git a/bsnes/sfc/memory/memory.hpp b/bsnes/sfc/memory/memory.hpp index 6f28b587..fbf60e45 100644 --- a/bsnes/sfc/memory/memory.hpp +++ b/bsnes/sfc/memory/memory.hpp @@ -1,4 +1,6 @@ struct Memory { + static bool GlobalWriteEnable; + virtual ~Memory() { reset(); } inline explicit operator bool() const { return size() > 0; } diff --git a/bsnes/sfc/memory/protectable.hpp b/bsnes/sfc/memory/protectable.hpp index e0e1ff69..94d2c7b1 100644 --- a/bsnes/sfc/memory/protectable.hpp +++ b/bsnes/sfc/memory/protectable.hpp @@ -32,8 +32,9 @@ struct ProtectableMemory : Memory { } inline auto write(uint24 address, uint8 data) -> void override { - if(!self.writable) return; - self.data[address] = data; + if(self.writable || Memory::GlobalWriteEnable) { + self.data[address] = data; + } } inline auto operator[](uint24 address) const -> uint8 { diff --git a/bsnes/sfc/memory/readable.hpp b/bsnes/sfc/memory/readable.hpp index 88d249ac..8810b531 100644 --- a/bsnes/sfc/memory/readable.hpp +++ b/bsnes/sfc/memory/readable.hpp @@ -24,6 +24,9 @@ struct ReadableMemory : Memory { } inline auto write(uint24 address, uint8 data) -> void override { + if(Memory::GlobalWriteEnable) { + self.data[address] = data; + } } inline auto operator[](uint24 address) const -> uint8 { diff --git a/bsnes/sfc/ppu-fast/background.cpp b/bsnes/sfc/ppu-fast/background.cpp index 7a0f8d00..42aa02c8 100644 --- a/bsnes/sfc/ppu-fast/background.cpp +++ b/bsnes/sfc/ppu-fast/background.cpp @@ -120,8 +120,8 @@ auto PPUfast::Line::getTile(PPUfast::IO::Background& self, uint hoffset, uint vo bool hires = io.bgMode == 5 || io.bgMode == 6; uint tileHeight = 3 + self.tileSize; uint tileWidth = !hires ? tileHeight : 4; - uint screenX = self.screenSize.bit(0) ? 32 << 5 : 0; - uint screenY = self.screenSize.bit(1) ? 32 << 5 + self.screenSize.bit(0) : 0; + uint screenX = (self.screenSize & 1) ? 32 << 5 : 0; + uint screenY = (self.screenSize & 2) ? 32 << 5 + (self.screenSize & 1) : 0; uint tileX = hoffset >> tileWidth; uint tileY = voffset >> tileHeight; uint offset = (tileY & 0x1f) << 5 | (tileX & 0x1f); diff --git a/bsnes/sfc/ppu-fast/io.cpp b/bsnes/sfc/ppu-fast/io.cpp index f8baab29..f7ee297b 100644 --- a/bsnes/sfc/ppu-fast/io.cpp +++ b/bsnes/sfc/ppu-fast/io.cpp @@ -5,7 +5,7 @@ auto PPUfast::latchCounters() -> void { } auto PPUfast::vramAddress() const -> uint15 { //uint15 for 64K VRAM; uint16 for 128K VRAM - uint16 address = io.vramAddress; + uint15 address = io.vramAddress; switch(io.vramMapping) { case 0: return address; 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]; } -auto PPUfast::writeVRAM(uint1 byte, uint8 data) -> void { +template +auto PPUfast::writeVRAM(uint8_t data) -> void { if(!io.displayDisable && cpu.vcounter() < vdisp()) return; Line::flush(); 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); } -auto PPUfast::updateTiledata(uint15 address) -> void { +auto PPUfast::updateTiledata(uint address) -> void { auto word = vram[address]; - auto line2bpp = tilecache[TileMode::BPP2] + (address.bits(3,14) << 6) + (address.bits(0,2) << 3); - auto line4bpp = tilecache[TileMode::BPP4] + (address.bits(4,14) << 6) + (address.bits(0,2) << 3); - auto line8bpp = tilecache[TileMode::BPP8] + (address.bits(5,14) << 6) + (address.bits(0,2) << 3); - uint plane4bpp = address.bit(3) << 1; - uint plane8bpp = address.bit(3) << 1 | address.bit(4) << 2; + auto line2bpp = tilecache[TileMode::BPP2] + ((address & 0x7fff) << 3); + auto line4bpp = tilecache[TileMode::BPP4] + ((address & 0x7ff0) << 2) + ((address & 7) << 3); + auto line8bpp = tilecache[TileMode::BPP8] + ((address & 0x7fe0) << 1) + ((address & 7) << 3); + uint plane4bpp = address >> 2 & 2; + uint plane8bpp = address >> 2 & 6; for(uint x : range(8)) { - line2bpp[7 - x].bit( 0) = word.bit(x + 0); - line2bpp[7 - x].bit( 1) = word.bit(x + 8); - line4bpp[7 - x].bit(plane4bpp + 0) = word.bit(x + 0); - 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); + line2bpp[7 - x] = word >> x & 1 | word >> x + 7 & 2; + line4bpp[7 - x] = line4bpp[7 - x] & ~(3 << plane4bpp) | (word >> x & 1) << plane4bpp | (word >> x + 7 & 2) << plane4bpp; + line8bpp[7 - x] = line8bpp[7 - x] & ~(3 << plane8bpp) | (word >> x & 1) << plane8bpp | (word >> x + 7 & 2) << plane8bpp; } } @@ -58,12 +61,18 @@ auto PPUfast::writeOAM(uint10 address, uint8 data) -> void { return writeObject(address, data); } -auto PPUfast::readCGRAM(uint1 byte, uint8 address) -> uint8 { +template +auto PPUfast::readCGRAM(uint8_t address) -> uint8 { if(!io.displayDisable && cpu.vcounter() > 0 && cpu.vcounter() < vdisp() && cpu.hcounter() >= 88 && cpu.hcounter() < 1096 ) 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 { @@ -114,7 +123,7 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 { } case 0x2139: { //VMDATALREAD - data = latch.vram.byte(0); + data = latch.vram & 0xff; if(io.vramIncrementMode == 0) { latch.vram = readVRAM(); io.vramAddress += io.vramIncrementSize; @@ -123,7 +132,7 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 { } case 0x213a: { //VMDATAHREAD - data = latch.vram.byte(1); + data = latch.vram >> 8; if(io.vramIncrementMode == 1) { latch.vram = readVRAM(); io.vramAddress += io.vramIncrementSize; @@ -133,26 +142,30 @@ auto PPUfast::readIO(uint24 address, uint8 data) -> uint8 { case 0x213b: { //CGDATAREAD 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 { - latch.ppu2.mdr.bits(0,6) = readCGRAM(1, io.cgramAddress++); + latch.ppu2.mdr.bits(0,6) = readCGRAM<1>(io.cgramAddress++); } return latch.ppu2.mdr; } 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); } else { + latch.hcounter = 0; latch.ppu2.mdr.bit(0) = io.hcounter.bit(8); } return latch.ppu2.mdr; } 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); } else { + latch.vcounter = 0; latch.ppu2.mdr.bit(0) = io.vcounter.bit(8); } return latch.ppu2.mdr; @@ -355,25 +368,25 @@ auto PPUfast::writeIO(uint24 address, uint8 data) -> void { } case 0x2116: { //VMADDL - io.vramAddress.byte(0) = data; + io.vramAddress = io.vramAddress & 0xff00 | data << 0; latch.vram = readVRAM(); return; } case 0x2117: { //VMADDH - io.vramAddress.byte(1) = data; + io.vramAddress = io.vramAddress & 0x00ff | data << 8; latch.vram = readVRAM(); return; } case 0x2118: { //VMDATAL - writeVRAM(0, data); + writeVRAM<0>(data); if(io.vramIncrementMode == 0) io.vramAddress += io.vramIncrementSize; return; } case 0x2119: { //VMDATAH - writeVRAM(1, data); + writeVRAM<1>(data); if(io.vramIncrementMode == 1) io.vramAddress += io.vramIncrementSize; return; } diff --git a/bsnes/sfc/ppu-fast/line.cpp b/bsnes/sfc/ppu-fast/line.cpp index 3b2fb785..73b22cbf 100644 --- a/bsnes/sfc/ppu-fast/line.cpp +++ b/bsnes/sfc/ppu-fast/line.cpp @@ -13,6 +13,8 @@ auto PPUfast::Line::flush() -> void { } auto PPUfast::Line::render() -> void { + uint y = this->y + (!ppufast.latch.overscan ? 7 : 0); + auto hd = ppufast.hd(); auto ss = ppufast.ss(); auto scale = ppufast.hdScale(); @@ -25,13 +27,13 @@ auto PPUfast::Line::render() -> void { : (256 * scale * scale)); if(io.displayDisable) { - memory::fill(output, width); + memory::fill(output, width); return; } bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6; 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 xb = !(hd || ss) ? 256 : ppufast.interlace() && !ppufast.field() ? 256 * scale * scale / 2 : 256 * scale * scale; 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(!windowBelow[x]) 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); } -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(!halve) { 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 //paletteColor = BBGGGRRR //output = 0 BBb00 GGGg0 RRRr0 diff --git a/bsnes/sfc/ppu-fast/mode7.cpp b/bsnes/sfc/ppu-fast/mode7.cpp index dd4540e0..2471537d 100644 --- a/bsnes/sfc/ppu-fast/mode7.cpp +++ b/bsnes/sfc/ppu-fast/mode7.cpp @@ -43,8 +43,8 @@ auto PPUfast::Line::renderMode7(PPUfast::IO::Background& self, uint source) -> v bool outOfBounds = (pixelX | pixelY) & ~1023; uint15 tileAddress = tileY * 128 + tileX; uint15 paletteAddress = ((pixelY & 7) << 3) + (pixelX & 7); - uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppufast.vram[tileAddress].byte(0); - uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppufast.vram[paletteAddress + (tile << 6)].byte(1); + 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)] >> 8); uint priority; if(source == Source::BG1) { diff --git a/bsnes/sfc/ppu-fast/mode7hd.cpp b/bsnes/sfc/ppu-fast/mode7hd.cpp index 8a5fb65f..93d0adbf 100644 --- a/bsnes/sfc/ppu-fast/mode7hd.cpp +++ b/bsnes/sfc/ppu-fast/mode7hd.cpp @@ -80,8 +80,8 @@ auto PPUfast::Line::renderMode7HD(PPUfast::IO::Background& self, uint source) -> //only compute color again when coordinates have changed 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 palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : ppufast.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)].byte(1); + 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)] >> 8); uint priority; if(!extbg) { diff --git a/bsnes/sfc/ppu-fast/ppu.cpp b/bsnes/sfc/ppu-fast/ppu.cpp index 3583ca6f..680ed132 100644 --- a/bsnes/sfc/ppu-fast/ppu.cpp +++ b/bsnes/sfc/ppu-fast/ppu.cpp @@ -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; } PPUfast::PPUfast() { - output = new uint16[2304 * 2304] + 72 * 2304; //overscan offset - for(uint l : range(16)) { for(uint r : range(32)) { for(uint g : range(32)) { @@ -42,9 +40,9 @@ PPUfast::PPUfast() { } } - tilecache[TileMode::BPP2] = new uint8[4096 * 8 * 8]; - tilecache[TileMode::BPP4] = new uint8[2048 * 8 * 8]; - tilecache[TileMode::BPP8] = new uint8[1024 * 8 * 8]; + tilecache[TileMode::BPP2] = new uint8_t[4096 * 8 * 8]; + tilecache[TileMode::BPP4] = new uint8_t[2048 * 8 * 8]; + tilecache[TileMode::BPP8] = new uint8_t[1024 * 8 * 8]; for(uint y : range(lines.size())) { lines[y].y = y; @@ -52,7 +50,6 @@ PPUfast::PPUfast() { } PPUfast::~PPUfast() { - delete[] (output - 72 * 2304); //overscan offset delete[] tilecache[TileMode::BPP2]; delete[] tilecache[TileMode::BPP4]; delete[] tilecache[TileMode::BPP8]; @@ -93,6 +90,7 @@ auto PPUfast::scanline() -> void { if(vcounter() == 0) { ppubase.display.interlace = io.interlace; ppubase.display.overscan = io.overscan; + latch.overscan = io.overscan; latch.hires = false; latch.hd = false; latch.ss = false; @@ -121,17 +119,31 @@ auto PPUfast::refresh() -> void { auto output = this->output; uint pitch, width, height; if(!hd()) { - if(!overscan()) output -= 7 * 1024; pitch = 512 << !interlace(); width = 256 << hires(); height = 240 << interlace(); } else { - if(!overscan()) output -= 7 * 256 * hdScale() * hdScale(); pitch = 256 * hdScale(); width = 256 * 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(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; } @@ -143,7 +155,7 @@ auto PPUfast::load() -> bool { auto PPUfast::power(bool reset) -> void { create(Enter, system.cpuFrequency()); PPUcounter::reset(); - memory::fill(output, 1024 * 960); + memory::fill(output, 1024 * 960); function uint8> reader{&PPUfast::readIO, this}; function void> writer{&PPUfast::writeIO, this}; @@ -167,6 +179,8 @@ auto PPUfast::power(bool reset) -> void { Line::start = 0; Line::count = 0; + + frame = {}; } } diff --git a/bsnes/sfc/ppu-fast/ppu.hpp b/bsnes/sfc/ppu-fast/ppu.hpp index b903955d..b019c098 100644 --- a/bsnes/sfc/ppu-fast/ppu.hpp +++ b/bsnes/sfc/ppu-fast/ppu.hpp @@ -41,30 +41,30 @@ public: //serialization.cpp auto serialize(serializer&) -> void; - uint1 interlace; - uint1 overscan; - uint1 hires; - uint1 hd; - uint1 ss; + bool interlace = 0; + bool overscan = 0; + bool hires = 0; + bool hd = 0; + bool ss = 0; - uint16 vram; - uint8 oam; - uint8 cgram; + uint16_t vram = 0; + uint8_t oam = 0; + uint8_t cgram = 0; - uint10 oamAddress; - uint8 cgramAddress; + uint10 oamAddress = 0; + uint8_t cgramAddress = 0; - uint8 mode7; - uint1 counters; - uint1 hcounter; //hdot - uint1 vcounter; + uint8_t mode7 = 0; + bool counters = 0; + bool hcounter = 0; //hdot + bool vcounter = 0; struct PPU { //serialization.cpp auto serialize(serializer&) -> void; uint8 mdr; - uint8 bgofs; + uint8_t bgofs = 0; } ppu1, ppu2; }; @@ -72,78 +72,78 @@ public: //serialization.cpp auto serialize(serializer&) -> void; - uint1 displayDisable; - uint4 displayBrightness; + bool displayDisable = 1; + uint4 displayBrightness; uint10 oamBaseAddress; uint10 oamAddress; - uint1 oamPriority; - uint1 bgPriority; - uint3 bgMode; - uint4 mosaicSize; - uint1 vramIncrementMode; - uint2 vramMapping; - uint8 vramIncrementSize; - uint16 vramAddress; - uint8 cgramAddress; - uint1 cgramAddressLatch; - uint9 hcounter; //hdot - uint9 vcounter; - uint1 interlace; - uint1 overscan; - uint1 pseudoHires; - uint1 extbg; + bool oamPriority = 0; + bool bgPriority = 0; + uint3 bgMode; + uint4 mosaicSize; + bool vramIncrementMode = 0; + uint2 vramMapping; + uint8_t vramIncrementSize = 0; + uint16_t vramAddress = 0; + uint8_t cgramAddress = 0; + uint1 cgramAddressLatch; + uint9 hcounter; //hdot + uint9 vcounter; + bool interlace = 0; + bool overscan = 0; + bool pseudoHires = 0; + bool extbg = 0; struct Mode7 { //serialization.cpp auto serialize(serializer&) -> void; - uint1 hflip; - uint1 vflip; - uint2 repeat; - uint16 a; - uint16 b; - uint16 c; - uint16 d; - uint16 x; - uint16 y; - uint16 hoffset; - uint16 voffset; + bool hflip = 0; + bool vflip = 0; + uint repeat = 0; + uint16_t a = 0; + uint16_t b = 0; + uint16_t c = 0; + uint16_t d = 0; + uint16_t x = 0; + uint16_t y = 0; + uint16_t hoffset = 0; + uint16_t voffset = 0; } mode7; struct Window { //serialization.cpp auto serialize(serializer&) -> void; - uint8 oneLeft; - uint8 oneRight; - uint8 twoLeft; - uint8 twoRight; + uint8_t oneLeft = 0; + uint8_t oneRight = 0; + uint8_t twoLeft = 0; + uint8_t twoRight = 0; } window; struct WindowLayer { //serialization.cpp auto serialize(serializer&) -> void; - uint1 oneEnable; - uint1 oneInvert; - uint1 twoEnable; - uint1 twoInvert; - uint2 mask; - uint1 aboveEnable; - uint1 belowEnable; + bool oneEnable = 0; + bool oneInvert = 0; + bool twoEnable = 0; + bool twoInvert = 0; + uint mask = 0; + bool aboveEnable = 0; + bool belowEnable = 0; }; struct WindowColor { //serialization.cpp auto serialize(serializer&) -> void; - uint1 oneEnable; - uint1 oneInvert; - uint1 twoEnable; - uint1 twoInvert; - uint2 mask; - uint2 aboveMask; - uint2 belowMask; + bool oneEnable = 0; + bool oneInvert = 0; + bool twoEnable = 0; + bool twoInvert = 0; + uint mask = 0; + uint aboveMask = 0; + uint belowMask = 0; }; struct Background { @@ -151,17 +151,17 @@ public: auto serialize(serializer&) -> void; WindowLayer window; - uint1 aboveEnable; - uint1 belowEnable; - uint1 mosaicEnable; + bool aboveEnable = 0; + bool belowEnable = 0; + bool mosaicEnable = 0; uint15 tiledataAddress; uint15 screenAddress; - uint2 screenSize; - uint1 tileSize; + uint2 screenSize; + bool tileSize = 0; uint16 hoffset; uint16 voffset; - uint3 tileMode; - uint4 priority[2]; + uint3 tileMode; + uint4 priority[2]; } bg1, bg2, bg3, bg4; struct Object { @@ -169,16 +169,16 @@ public: auto serialize(serializer&) -> void; WindowLayer window; - uint1 aboveEnable; - uint1 belowEnable; - uint1 interlace; - uint3 baseSize; - uint2 nameselect; + bool aboveEnable = 0; + bool belowEnable = 0; + bool interlace = 0; + uint3 baseSize; + uint2 nameselect; uint15 tiledataAddress; - uint7 first; - uint1 rangeOver; - uint1 timeOver; - uint4 priority[4]; + uint7 first; + bool rangeOver = 0; + bool timeOver = 0; + uint4 priority[4]; } obj; struct Color { @@ -186,11 +186,11 @@ public: auto serialize(serializer&) -> void; WindowColor window; - uint1 enable[7]; - uint1 directColor; - uint1 blendMode; //0 = fixed; 1 = pixel - uint1 halve; - uint1 mathMode; //0 = add; 1 = sub + bool enable[7] = {}; + bool directColor = 0; + bool blendMode = 0; //0 = fixed; 1 = pixel + bool halve = 0; + bool mathMode = 0; //0 = add; 1 = sub uint15 fixedColor; } col; }; @@ -200,48 +200,48 @@ public: auto serialize(serializer&) -> void; uint9 x; - uint8 y; + uint8_t y; uint8 character; - uint1 nameselect; - uint1 vflip; - uint1 hflip; + bool nameselect; + bool vflip; + bool hflip; uint2 priority; uint3 palette; - uint1 size; + bool size; }; struct ObjectItem { - uint1 valid; + bool valid; uint7 index; - uint8 width; - uint8 height; + uint8_t width; + uint8_t height; }; struct ObjectTile { - uint1 valid; - uint9 x; - uint8 y; - uint2 priority; - uint8 palette; - uint1 hflip; + bool valid; + uint9 x; + uint8_t y; + uint2 priority; + uint8_t palette; + bool hflip; uint11 number; }; struct Pixel { - uint8 source; - uint8 priority; - uint15 color; + uint source; + uint priority; + uint color; }; //io.cpp auto latchCounters() -> void; alwaysinline auto vramAddress() const -> uint15; alwaysinline auto readVRAM() -> uint16; - alwaysinline auto writeVRAM(uint1 byte, uint8 data) -> void; - alwaysinline auto updateTiledata(uint15 address) -> void; + template alwaysinline auto writeVRAM(uint8_t data) -> void; + alwaysinline auto updateTiledata(uint address) -> void; alwaysinline auto readOAM(uint10 address) -> uint8; alwaysinline auto writeOAM(uint10 address, uint8 data) -> void; - alwaysinline auto readCGRAM(uint1 byte, uint8 address) -> uint8; + template alwaysinline auto readCGRAM(uint8_t address) -> uint8; alwaysinline auto writeCGRAM(uint8 address, uint15 data) -> void; auto readIO(uint24 address, uint8 data) -> uint8; auto writeIO(uint24 address, uint8 data) -> void; @@ -257,24 +257,24 @@ public: Latch latch; IO io; - uint16 vram[32 * 1024]; - uint15 cgram[256]; - Object objects[128]; + uint16_t vram[32 * 1024]; + uint16_t cgram[256]; + Object objects[128]; //[unserialized] - uint16* output; - uint16 lightTable[16][32768]; - uint8* tilecache[3]; //bitplane -> bitmap tiledata - uint32 ItemLimit; - uint32 TileLimit; + uint16_t output[2304 * 2160]; + uint16_t lightTable[16][32768]; + uint8_t* tilecache[3]; //bitplane -> bitmap tiledata + uint ItemLimit; + uint TileLimit; struct Line { //line.cpp static auto flush() -> void; auto render() -> void; - auto pixel(uint x, Pixel above, Pixel below) const -> uint15; - auto blend(uint x, uint y, bool halve) const -> uint15; - alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint15; + auto pixel(uint x, Pixel above, Pixel below) const -> uint16_t; + auto blend(uint x, uint y, bool halve) const -> uint16_t; + alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint16_t; 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 plotHD(Pixel*, uint x, uint source, uint priority, uint color, bool hires, bool subpixel) -> void; @@ -301,7 +301,7 @@ public: uint9 y; //constant IO io; - array cgram; + array cgram; array items; //32 on real hardware array tiles; //34 on real hardware; 1024 max (128 * 64-width tiles) @@ -317,6 +317,13 @@ public: static uint count; }; array 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; diff --git a/bsnes/sfc/ppu/ppu.cpp b/bsnes/sfc/ppu/ppu.cpp index a0124e83..0ee658f8 100644 --- a/bsnes/sfc/ppu/ppu.cpp +++ b/bsnes/sfc/ppu/ppu.cpp @@ -20,9 +20,6 @@ bg4(Background::ID::BG4) { ppu1.version = 1; //allowed values: 1 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 r = 0; r < 32; r++) { for(uint g = 0; g < 32; g++) { @@ -42,9 +39,6 @@ PPU::~PPU() { if(system.fastPPU()) { setHandle(nullptr); } - - output -= 16 * 512; - delete[] output; } auto PPU::step(uint clocks) -> void { @@ -253,12 +247,10 @@ auto PPU::refresh() -> void { } auto output = this->output; - if(!overscan()) output -= 14 * 512; - auto pitch = 512; - auto width = 512; + auto pitch = 512; + auto width = 512; auto height = 480; -//Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, configuration.video.blurEmulation); - platform->videoFrame(output, pitch * sizeof(uint16), width, height); + platform->videoFrame(output, pitch * sizeof(uint16), width, height, /* scale = */ 1); } } diff --git a/bsnes/sfc/ppu/ppu.hpp b/bsnes/sfc/ppu/ppu.hpp index 37e1ba25..ecc5e2dc 100644 --- a/bsnes/sfc/ppu/ppu.hpp +++ b/bsnes/sfc/ppu/ppu.hpp @@ -40,8 +40,8 @@ private: uint16 mask = 0x7fff; } vram; - uint16* output = nullptr; - uint16 lightTable[16][32768]; + uint16_t output[512 * 480]; + uint16_t lightTable[16][32768]; struct { bool interlace; diff --git a/bsnes/sfc/ppu/screen.cpp b/bsnes/sfc/ppu/screen.cpp index f6499908..2b188275 100644 --- a/bsnes/sfc/ppu/screen.cpp +++ b/bsnes/sfc/ppu/screen.cpp @@ -1,5 +1,7 @@ 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); if(ppu.display.interlace && ppu.field()) lineA += 512, lineB += 512; diff --git a/bsnes/sfc/ppu/screen.hpp b/bsnes/sfc/ppu/screen.hpp index 13ea8174..fa0cc77d 100644 --- a/bsnes/sfc/ppu/screen.hpp +++ b/bsnes/sfc/ppu/screen.hpp @@ -13,8 +13,8 @@ struct Screen { auto serialize(serializer&) -> void; - uint16* lineA; - uint16* lineB; + uint16_t* lineA; + uint16_t* lineB; uint15 cgram[256]; diff --git a/bsnes/sfc/slot/slot.cpp b/bsnes/sfc/slot/slot.cpp new file mode 100644 index 00000000..0e1d4ab6 --- /dev/null +++ b/bsnes/sfc/slot/slot.cpp @@ -0,0 +1,2 @@ +#include +#include diff --git a/bsnes/sfc/system/system.cpp b/bsnes/sfc/system/system.cpp index 4c826dbd..512731d0 100644 --- a/bsnes/sfc/system/system.cpp +++ b/bsnes/sfc/system/system.cpp @@ -9,7 +9,18 @@ Cheat cheat; #include "serialization.cpp" 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 { @@ -17,7 +28,6 @@ auto System::runToSave() -> void { scheduler.synchronize(smp); scheduler.synchronize(ppu); for(auto coprocessor : cpu.coprocessors) scheduler.synchronize(*coprocessor); - for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral); } auto System::load(Emulator::Interface* interface) -> bool { @@ -57,7 +67,6 @@ auto System::save() -> void { auto System::unload() -> void { if(!loaded()) return; - cpu.peripherals.reset(); controllerPort1.unload(); controllerPort2.unload(); expansionPort.unload(); diff --git a/bsnes/target-bsnes/program/filter.cpp b/bsnes/target-bsnes/program/filter.cpp new file mode 100644 index 00000000..2ca623cd --- /dev/null +++ b/bsnes/target-bsnes/program/filter.cpp @@ -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; +} diff --git a/bsnes/target-bsnes/program/platform.cpp b/bsnes/target-bsnes/program/platform.cpp index 613d67a6..389a4386 100644 --- a/bsnes/target-bsnes/program/platform.cpp +++ b/bsnes/target-bsnes/program/platform.cpp @@ -207,123 +207,28 @@ auto Program::load(uint id, string name, string type, vector options) -> 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 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 - screenshot.data = data; - screenshot.pitch = pitch; - screenshot.width = width; + screenshot.data = data; + screenshot.pitch = pitch; + screenshot.width = width; screenshot.height = height; + screenshot.scale = scale; pitch >>= 1; - if(!presentation.showOverscanArea.checked()) { - data += (height / 30) * pitch; - height -= height / 15; + if(!settings.video.overscan) { + uint multiplier = height / 240; + data += 8 * multiplier * pitch; + height -= 16 * multiplier; } - uint videoWidth = 256 * (settings.video.aspectCorrection ? 8.0 / 7.0 : 1.0); - uint videoHeight = (settings.video.overscan ? 240.0 : 224.0); + uint outputWidth, outputHeight; + viewportSize(outputWidth, outputHeight, scale); - 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; - } - - 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); + uint filterWidth = width, filterHeight = height; + auto filterRender = filterSelect(filterWidth, filterHeight, scale); if(auto [output, length] = video.acquire(filterWidth, filterHeight); output) { filterRender(palette, output, length, (const uint16_t*)data, pitch << 1, width, height); diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index 313090a8..a02ab4ac 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -11,6 +11,7 @@ #include "utility.cpp" #include "patch.cpp" #include "hacks.cpp" +#include "filter.cpp" #include "viewport.cpp" Program program; @@ -78,7 +79,7 @@ auto Program::main() -> void { audio.clear(); if(!Application::modal()) { usleep(20 * 1000); - refreshViewport(); + viewportRefresh(); } return; } diff --git a/bsnes/target-bsnes/program/program.hpp b/bsnes/target-bsnes/program/program.hpp index 9d3dd91d..f7db9aa1 100644 --- a/bsnes/target-bsnes/program/program.hpp +++ b/bsnes/target-bsnes/program/program.hpp @@ -9,7 +9,7 @@ struct Program : Lock, Emulator::Platform { //platform.cpp 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 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 inputPoll(uint port, uint device, uint input) -> int16 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& data) -> void; auto hackOverclockSuperFX() -> void; + //filter.cpp + auto filterSelect(uint& width, uint& height, uint scale) -> Filter::Render; + //viewport.cpp - auto refreshViewport() -> void; + auto viewportSize(uint& width, uint& height, uint scale) -> void; + auto viewportRefresh() -> void; public: struct Game { @@ -142,12 +146,14 @@ public: vector gameQueue; uint32_t palette[32768]; + uint32_t palettePaused[32768]; struct Screenshot { - const uint16* data = nullptr; - uint pitch = 0; - uint width = 0; + const uint16_t* data = nullptr; + uint pitch = 0; + uint width = 0; uint height = 0; + uint scale = 0; } screenshot; bool frameAdvance = false; diff --git a/bsnes/target-bsnes/program/video.cpp b/bsnes/target-bsnes/program/video.cpp index 6bd4303d..200e3c15 100644 --- a/bsnes/target-bsnes/program/video.cpp +++ b/bsnes/target-bsnes/program/video.cpp @@ -99,6 +99,15 @@ auto Program::updateVideoPalette() -> void { 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; } + + 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); diff --git a/bsnes/target-bsnes/program/viewport.cpp b/bsnes/target-bsnes/program/viewport.cpp index 771c38bc..93984d90 100644 --- a/bsnes/target-bsnes/program/viewport.cpp +++ b/bsnes/target-bsnes/program/viewport.cpp @@ -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, 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, @@ -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 }; -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, 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, @@ -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, 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(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); - } -} diff --git a/bsnes/target-bsnes/tools/cheat-editor.cpp b/bsnes/target-bsnes/tools/cheat-editor.cpp index 8d677593..46297a37 100644 --- a/bsnes/target-bsnes/tools/cheat-editor.cpp +++ b/bsnes/target-bsnes/tools/cheat-editor.cpp @@ -115,6 +115,9 @@ auto CheatEditor::create() -> void { cheatList.setHeadered(); cheatList.setSortable(); 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(); }); cheatList.onChange([&] { @@ -123,6 +126,7 @@ auto CheatEditor::create() -> void { removeButton.setEnabled(batched.size() >= 1); }); cheatList.onToggle([&](TableViewCell cell) { + activateTimeout = chrono::timestamp(); if(auto item = cell->parentTableViewItem()) { cheats[item->offset()].enable = cell.checked(); synchronizeCodes(); diff --git a/bsnes/target-bsnes/tools/tools.hpp b/bsnes/target-bsnes/tools/tools.hpp index a6a9743b..e38f4e46 100644 --- a/bsnes/target-bsnes/tools/tools.hpp +++ b/bsnes/target-bsnes/tools/tools.hpp @@ -59,6 +59,7 @@ struct CheatEditor : TabFrameItem { public: vector cheats; + uint64_t activateTimeout = 0; VerticalLayout layout{this}; TableView cheatList{&layout, Size{~0, ~0}};