mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
d87a0f633d
commit
a03d91882c
|
@ -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")
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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/";
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
if(address & 0x408000) {
|
||||||
|
if(address & 0x800000 && io.fastROM) {
|
||||||
|
status.clockCount = 6;
|
||||||
dmaEdge();
|
dmaEdge();
|
||||||
r.mar = address;
|
r.mar = address;
|
||||||
step(status.clockCount - 4);
|
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);
|
|
||||||
|
if(address & 0x408000) {
|
||||||
|
if(address & 0x800000 && io.fastROM) {
|
||||||
|
status.clockCount = 6;
|
||||||
dmaEdge();
|
dmaEdge();
|
||||||
r.mar = address;
|
r.mar = address;
|
||||||
step(status.clockCount);
|
step<6,1>();
|
||||||
bus.write(address, r.mdr = data);
|
} 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>();
|
||||||
|
}
|
||||||
|
|
||||||
auto CPU::wait(uint24 address) const -> uint {
|
bus.write(address, r.mdr = data);
|
||||||
if(address & 0x408000) return address & 0x800000 ? io.romSpeed : 8;
|
|
||||||
if(address + 0x6000 & 0x4000) return 8;
|
|
||||||
if(address - 0x4000 & 0x7e00) return 6;
|
|
||||||
return 12;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CPU::readDisassembler(uint24 address) -> uint8 {
|
auto CPU::readDisassembler(uint24 address) -> uint8 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
||||||
uint ticks = clocks >> 1;
|
|
||||||
while(ticks--) {
|
|
||||||
counter.cpu += 2;
|
counter.cpu += 2;
|
||||||
tick();
|
tick();
|
||||||
if(hcounter() & 2) pollInterrupts();
|
if(hcounter() & 2) pollInterrupts();
|
||||||
if(joypadCounter() == 0) joypadEdge();
|
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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace SuperFamicom {
|
namespace SuperFamicom {
|
||||||
|
|
||||||
|
bool Memory::GlobalWriteEnable = false;
|
||||||
Bus bus;
|
Bus bus;
|
||||||
|
|
||||||
Bus::~Bus() {
|
Bus::~Bus() {
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,10 @@ 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 {
|
||||||
return self.data[address];
|
return self.data[address];
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,13 +151,13 @@ 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;
|
||||||
|
@ -169,15 +169,15 @@ 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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include <sfc/slot/bsmemory/bsmemory.cpp>
|
||||||
|
#include <sfc/slot/sufamiturbo/sufamiturbo.cpp>
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -207,7 +207,7 @@ 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
|
||||||
|
@ -215,115 +215,20 @@ auto Program::videoFrame(const uint16* data, uint pitch, uint width, uint height
|
||||||
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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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}};
|
||||||
|
|
Loading…
Reference in New Issue