Update to bsnes v107r5 beta release.

byuu says:

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

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ struct Platform {
virtual auto path(uint id) -> string { return ""; }
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
virtual auto load(uint id, string name, string type, vector<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 inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -99,6 +99,15 @@ auto Program::updateVideoPalette() -> void {
case 24: palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
case 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);

View File

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

View File

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

View File

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