mirror of https://github.com/bsnes-emu/bsnes.git
Update to v097r01 release.
byuu says: A minor WIP to get us started. Changelog: - System::Video merged to PPU::Video - System::Audio merged to DSP::Audio - System::Configuration merged to Interface::Settings - created emulator/emulator.cpp and accompanying object file for shared code between all cores Currently, emulator.cpp just holds a videoColor() function that takes R16G16B16, performs gamma/saturation/luma adjust, and outputs (currently) A8R8G8B8. It's basically an internal function call for cores to use when generating palette entries. This code used to exist inside ui-tomoko/program/interface.cpp, but we have to move it internal for software display emulation. But in the future, we could add other useful cross-core functionality here.
This commit is contained in:
parent
1fdd0582fc
commit
f1ebef2ea8
|
@ -0,0 +1,3 @@
|
||||||
|
objects += emulator
|
||||||
|
|
||||||
|
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator/)
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include <emulator/emulator.hpp>
|
||||||
|
|
||||||
|
namespace Emulator {
|
||||||
|
|
||||||
|
auto Interface::videoColor(uint16 r, uint16 g, uint16 b) -> uint32 {
|
||||||
|
double saturation = 1.0;
|
||||||
|
double gamma = 1.0;
|
||||||
|
double luminance = 1.0;
|
||||||
|
|
||||||
|
if(saturation != 1.0) {
|
||||||
|
uint16 grayscale = uclamp<16>((r + g + b) / 3);
|
||||||
|
double inverse = max(0.0, 1.0 - saturation);
|
||||||
|
r = uclamp<16>(r * saturation + grayscale * inverse);
|
||||||
|
g = uclamp<16>(g * saturation + grayscale * inverse);
|
||||||
|
b = uclamp<16>(b * saturation + grayscale * inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(gamma != 1.0) {
|
||||||
|
double reciprocal = 1.0 / 32767.0;
|
||||||
|
r = r > 32767 ? r : 32767 * pow(r * reciprocal, gamma);
|
||||||
|
g = g > 32767 ? g : 32767 * pow(g * reciprocal, gamma);
|
||||||
|
b = b > 32767 ? b : 32767 * pow(b * reciprocal, gamma);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(luminance != 1.0) {
|
||||||
|
r = uclamp<16>(r * luminance);
|
||||||
|
g = uclamp<16>(g * luminance);
|
||||||
|
b = uclamp<16>(b * luminance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 255 << 24 | (r >> 8) << 16 | (g >> 8) << 8 | (b >> 8) << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "097";
|
static const string Version = "097.01";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "http://byuu.org/";
|
static const string Website = "http://byuu.org/";
|
||||||
|
|
|
@ -108,6 +108,9 @@ struct Interface {
|
||||||
virtual auto cap(const string& name) -> bool { return false; }
|
virtual auto cap(const string& name) -> bool { return false; }
|
||||||
virtual auto get(const string& name) -> any { return {}; }
|
virtual auto get(const string& name) -> any { return {}; }
|
||||||
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
virtual auto set(const string& name, const any& value) -> bool { return false; }
|
||||||
|
|
||||||
|
//shared functions
|
||||||
|
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
#ifndef PROCESSOR_HPP
|
#pragma once
|
||||||
#define PROCESSOR_HPP
|
|
||||||
|
|
||||||
#include <emulator/emulator.hpp>
|
#include <emulator/emulator.hpp>
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ auto ICD2::enter() -> void {
|
||||||
step(GameBoy::system.clocks_executed);
|
step(GameBoy::system.clocks_executed);
|
||||||
GameBoy::system.clocks_executed = 0;
|
GameBoy::system.clocks_executed = 0;
|
||||||
} else { //DMG halted
|
} else { //DMG halted
|
||||||
audio.coprocessor_sample(0x0000, 0x0000);
|
dsp.audio.coprocessorSample(0, 0);
|
||||||
step(1);
|
step(1);
|
||||||
}
|
}
|
||||||
synchronizeCPU();
|
synchronizeCPU();
|
||||||
|
@ -44,8 +44,8 @@ auto ICD2::unload() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ICD2::power() -> void {
|
auto ICD2::power() -> void {
|
||||||
audio.coprocessor_enable(true);
|
dsp.audio.coprocessorEnable(true);
|
||||||
audio.coprocessor_frequency(2 * 1024 * 1024);
|
dsp.audio.coprocessorFrequency(2 * 1024 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ICD2::reset() -> void {
|
auto ICD2::reset() -> void {
|
||||||
|
|
|
@ -93,7 +93,7 @@ auto ICD2::videoRefresh(const uint32* data, uint pitch, uint width, uint height)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ICD2::audioSample(int16 left, int16 right) -> void {
|
auto ICD2::audioSample(int16 left, int16 right) -> void {
|
||||||
audio.coprocessor_sample(left, right);
|
dsp.audio.coprocessorSample(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ICD2::inputPoll(uint port, uint device, uint id) -> int16 {
|
auto ICD2::inputPoll(uint port, uint device, uint id) -> int16 {
|
||||||
|
|
|
@ -41,7 +41,7 @@ auto MSU1::enter() -> void {
|
||||||
right = sclamp<16>(rchannel);
|
right = sclamp<16>(rchannel);
|
||||||
if(dsp.mute()) left = 0, right = 0;
|
if(dsp.mute()) left = 0, right = 0;
|
||||||
|
|
||||||
audio.coprocessor_sample(left, right);
|
dsp.audio.coprocessorSample(left, right);
|
||||||
step(1);
|
step(1);
|
||||||
synchronizeCPU();
|
synchronizeCPU();
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ auto MSU1::unload() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MSU1::power() -> void {
|
auto MSU1::power() -> void {
|
||||||
audio.coprocessor_enable(true);
|
dsp.audio.coprocessorEnable(true);
|
||||||
audio.coprocessor_frequency(44100.0);
|
dsp.audio.coprocessorFrequency(44100.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MSU1::reset() -> void {
|
auto MSU1::reset() -> void {
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
auto DSP::Audio::coprocessorEnable(bool enable) -> void {
|
||||||
|
mixer.clear();
|
||||||
|
mixerEnable = enable;
|
||||||
|
dsp.read = dsp.write = 0;
|
||||||
|
mix.read = mix.write = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DSP::Audio::coprocessorFrequency(double frequency) -> void {
|
||||||
|
mixer.setFrequency(frequency);
|
||||||
|
mixer.setResampler(nall::DSP::ResampleEngine::Sinc);
|
||||||
|
mixer.setResamplerFrequency(system.apuFrequency() / 768.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DSP::Audio::sample(int16 left, int16 right) -> void {
|
||||||
|
if(!mixerEnable) return interface->audioSample(left, right);
|
||||||
|
|
||||||
|
dsp.left[dsp.write] = left;
|
||||||
|
dsp.right[dsp.write] = right;
|
||||||
|
dsp.write++;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DSP::Audio::coprocessorSample(int16 left, int16 right) -> void {
|
||||||
|
int samples[] = {left, right};
|
||||||
|
mixer.sample(samples);
|
||||||
|
while(mixer.pending()) {
|
||||||
|
mixer.read(samples);
|
||||||
|
mix.left[mix.write] = samples[0];
|
||||||
|
mix.right[mix.write] = samples[1];
|
||||||
|
mix.write++;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto DSP::Audio::flush() -> void {
|
||||||
|
while(dsp.read != dsp.write && mix.read != mix.write) {
|
||||||
|
interface->audioSample(
|
||||||
|
sclamp<16>(dsp.left[dsp.read] + mix.left[mix.read]),
|
||||||
|
sclamp<16>(dsp.right[dsp.read] + mix.right[mix.read])
|
||||||
|
);
|
||||||
|
dsp.read++;
|
||||||
|
mix.read++;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
struct Audio {
|
||||||
|
auto sample(int16 left, int16 right) -> void;
|
||||||
|
|
||||||
|
auto coprocessorEnable(bool enable) -> void;
|
||||||
|
auto coprocessorFrequency(double frequency) -> void;
|
||||||
|
auto coprocessorSample(int16 left, int16 right) -> void;
|
||||||
|
auto flush() -> void;
|
||||||
|
|
||||||
|
private:
|
||||||
|
nall::DSP mixer;
|
||||||
|
bool mixerEnable = false;
|
||||||
|
struct Buffer {
|
||||||
|
int16 left[256];
|
||||||
|
int16 right[256];
|
||||||
|
uint8 read;
|
||||||
|
uint8 write;
|
||||||
|
} dsp, mix;
|
||||||
|
};
|
|
@ -7,7 +7,6 @@ DSP dsp;
|
||||||
#define REG(n) state.regs[n]
|
#define REG(n) state.regs[n]
|
||||||
#define VREG(n) state.regs[v.vidx + n]
|
#define VREG(n) state.regs[v.vidx + n]
|
||||||
|
|
||||||
#include "serialization.cpp"
|
|
||||||
#include "gaussian.cpp"
|
#include "gaussian.cpp"
|
||||||
#include "counter.cpp"
|
#include "counter.cpp"
|
||||||
#include "envelope.cpp"
|
#include "envelope.cpp"
|
||||||
|
@ -15,6 +14,8 @@ DSP dsp;
|
||||||
#include "misc.cpp"
|
#include "misc.cpp"
|
||||||
#include "voice.cpp"
|
#include "voice.cpp"
|
||||||
#include "echo.cpp"
|
#include "echo.cpp"
|
||||||
|
#include "serialization.cpp"
|
||||||
|
#include "audio.cpp"
|
||||||
|
|
||||||
DSP::DSP() {
|
DSP::DSP() {
|
||||||
static_assert(sizeof(signed) >= 32 / 8, "signed >= 32-bits");
|
static_assert(sizeof(signed) >= 32 / 8, "signed >= 32-bits");
|
||||||
|
@ -283,6 +284,8 @@ auto DSP::power() -> void {
|
||||||
voice[n].hiddenEnvelope = 0;
|
voice[n].hiddenEnvelope = 0;
|
||||||
voice[n]._envxOut = 0;
|
voice[n]._envxOut = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio.coprocessorEnable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto DSP::reset() -> void {
|
auto DSP::reset() -> void {
|
||||||
|
|
|
@ -5,7 +5,7 @@ struct DSP : Thread {
|
||||||
|
|
||||||
DSP();
|
DSP();
|
||||||
|
|
||||||
alwaysinline auto step(unsigned clocks) -> void;
|
alwaysinline auto step(uint clocks) -> void;
|
||||||
alwaysinline auto synchronizeSMP() -> void;
|
alwaysinline auto synchronizeSMP() -> void;
|
||||||
|
|
||||||
auto mute() const -> bool;
|
auto mute() const -> bool;
|
||||||
|
@ -18,10 +18,13 @@ struct DSP : Thread {
|
||||||
|
|
||||||
auto serialize(serializer&) -> void;
|
auto serialize(serializer&) -> void;
|
||||||
|
|
||||||
|
#include "audio.hpp"
|
||||||
|
Audio audio;
|
||||||
|
|
||||||
privileged:
|
privileged:
|
||||||
#include "modulo-array.hpp"
|
#include "modulo-array.hpp"
|
||||||
|
|
||||||
enum GlobalRegister : unsigned {
|
enum GlobalRegister : uint {
|
||||||
MVOLL = 0x0c, MVOLR = 0x1c,
|
MVOLL = 0x0c, MVOLR = 0x1c,
|
||||||
EVOLL = 0x2c, EVOLR = 0x3c,
|
EVOLL = 0x2c, EVOLR = 0x3c,
|
||||||
KON = 0x4c, KOFF = 0x5c,
|
KON = 0x4c, KOFF = 0x5c,
|
||||||
|
@ -32,7 +35,7 @@ privileged:
|
||||||
EDL = 0x7d, FIR = 0x0f, //8 coefficients at 0x0f, 0x1f, ... 0x7f
|
EDL = 0x7d, FIR = 0x0f, //8 coefficients at 0x0f, 0x1f, ... 0x7f
|
||||||
};
|
};
|
||||||
|
|
||||||
enum VoiceRegister : unsigned {
|
enum VoiceRegister : uint {
|
||||||
VOLL = 0x00, VOLR = 0x01,
|
VOLL = 0x00, VOLR = 0x01,
|
||||||
PITCHL = 0x02, PITCHH = 0x03,
|
PITCHL = 0x02, PITCHH = 0x03,
|
||||||
SRCN = 0x04, ADSR0 = 0x05,
|
SRCN = 0x04, ADSR0 = 0x05,
|
||||||
|
@ -40,14 +43,14 @@ privileged:
|
||||||
ENVX = 0x08, OUTX = 0x09,
|
ENVX = 0x08, OUTX = 0x09,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum EnvelopeMode : unsigned {
|
enum EnvelopeMode : uint {
|
||||||
EnvelopeRelease,
|
EnvelopeRelease,
|
||||||
EnvelopeAttack,
|
EnvelopeAttack,
|
||||||
EnvelopeDecay,
|
EnvelopeDecay,
|
||||||
EnvelopeSustain,
|
EnvelopeSustain,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum : unsigned {
|
enum : uint {
|
||||||
EchoHistorySize = 8,
|
EchoHistorySize = 8,
|
||||||
BrrBufferSize = 12,
|
BrrBufferSize = 12,
|
||||||
BrrBlockSize = 9,
|
BrrBlockSize = 9,
|
||||||
|
@ -57,77 +60,77 @@ privileged:
|
||||||
struct State {
|
struct State {
|
||||||
uint8 regs[128];
|
uint8 regs[128];
|
||||||
|
|
||||||
ModuloArray<signed, EchoHistorySize> echoHistory[2]; //echo history keeps most recent 8 samples
|
ModuloArray<int, EchoHistorySize> echoHistory[2]; //echo history keeps most recent 8 samples
|
||||||
signed echoHistoryOffset;
|
int echoHistoryOffset;
|
||||||
|
|
||||||
bool everyOtherSample; //toggles every sample
|
bool everyOtherSample; //toggles every sample
|
||||||
signed kon; //KON value when last checked
|
int kon; //KON value when last checked
|
||||||
signed noise;
|
int noise;
|
||||||
signed counter;
|
int counter;
|
||||||
signed echoOffset; //offset from ESA in echo buffer
|
int echoOffset; //offset from ESA in echo buffer
|
||||||
signed echoLength; //number of bytes that echo_offset will stop at
|
int echoLength; //number of bytes that echo_offset will stop at
|
||||||
|
|
||||||
//hidden registers also written to when main register is written to
|
//hidden registers also written to when main register is written to
|
||||||
signed konBuffer;
|
int konBuffer;
|
||||||
signed endxBuffer;
|
int endxBuffer;
|
||||||
signed envxBuffer;
|
int envxBuffer;
|
||||||
signed outxBuffer;
|
int outxBuffer;
|
||||||
|
|
||||||
//temporary state between clocks (prefixed with _)
|
//temporary state between clocks (prefixed with _)
|
||||||
|
|
||||||
//read once per sample
|
//read once per sample
|
||||||
signed _pmon;
|
int _pmon;
|
||||||
signed _non;
|
int _non;
|
||||||
signed _eon;
|
int _eon;
|
||||||
signed _dir;
|
int _dir;
|
||||||
signed _koff;
|
int _koff;
|
||||||
|
|
||||||
//read a few clocks ahead before used
|
//read a few clocks ahead before used
|
||||||
signed _brrNextAddress;
|
int _brrNextAddress;
|
||||||
signed _adsr0;
|
int _adsr0;
|
||||||
signed _brrHeader;
|
int _brrHeader;
|
||||||
signed _brrByte;
|
int _brrByte;
|
||||||
signed _srcn;
|
int _srcn;
|
||||||
signed _esa;
|
int _esa;
|
||||||
signed _echoDisabled;
|
int _echoDisabled;
|
||||||
|
|
||||||
//internal state that is recalculated every sample
|
//internal state that is recalculated every sample
|
||||||
signed _dirAddress;
|
int _dirAddress;
|
||||||
signed _pitch;
|
int _pitch;
|
||||||
signed _output;
|
int _output;
|
||||||
signed _looped;
|
int _looped;
|
||||||
signed _echoPointer;
|
int _echoPointer;
|
||||||
|
|
||||||
//left/right sums
|
//left/right sums
|
||||||
signed _mainOut[2];
|
int _mainOut[2];
|
||||||
signed _echoOut[2];
|
int _echoOut[2];
|
||||||
signed _echoIn [2];
|
int _echoIn [2];
|
||||||
} state;
|
} state;
|
||||||
|
|
||||||
struct Voice {
|
struct Voice {
|
||||||
ModuloArray<signed, BrrBufferSize> buffer; //decoded samples
|
ModuloArray<int, BrrBufferSize> buffer; //decoded samples
|
||||||
signed bufferOffset; //place in buffer where next samples will be decoded
|
int bufferOffset; //place in buffer where next samples will be decoded
|
||||||
signed gaussianOffset; //relative fractional position in sample (0x1000 = 1.0)
|
int gaussianOffset; //relative fractional position in sample (0x1000 = 1.0)
|
||||||
signed brrAddress; //address of current BRR block
|
int brrAddress; //address of current BRR block
|
||||||
signed brrOffset; //current decoding offset in BRR block
|
int brrOffset; //current decoding offset in BRR block
|
||||||
signed vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc
|
int vbit; //bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc
|
||||||
signed vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc
|
int vidx; //voice channel register index: 0x00 for voice 0, 0x10 for voice 1, etc
|
||||||
signed konDelay; //KON delay/current setup phase
|
int konDelay; //KON delay/current setup phase
|
||||||
signed envelopeMode;
|
int envelopeMode;
|
||||||
signed envelope; //current envelope level
|
int envelope; //current envelope level
|
||||||
signed hiddenEnvelope; //used by GAIN mode 7, very obscure quirk
|
int hiddenEnvelope; //used by GAIN mode 7, very obscure quirk
|
||||||
signed _envxOut;
|
int _envxOut;
|
||||||
} voice[8];
|
} voice[8];
|
||||||
|
|
||||||
//gaussian
|
//gaussian
|
||||||
static const int16 GaussianTable[512];
|
static const int16 GaussianTable[512];
|
||||||
auto gaussianInterpolate(const Voice& v) -> signed;
|
auto gaussianInterpolate(const Voice& v) -> int;
|
||||||
|
|
||||||
//counter
|
//counter
|
||||||
static const uint16 CounterRate[32];
|
static const uint16 CounterRate[32];
|
||||||
static const uint16 CounterOffset[32];
|
static const uint16 CounterOffset[32];
|
||||||
auto counterTick() -> void;
|
auto counterTick() -> void;
|
||||||
auto counterPoll(unsigned rate) -> bool;
|
auto counterPoll(uint rate) -> bool;
|
||||||
|
|
||||||
//envelope
|
//envelope
|
||||||
auto envelopeRun(Voice& v) -> void;
|
auto envelopeRun(Voice& v) -> void;
|
||||||
|
@ -157,8 +160,8 @@ privileged:
|
||||||
auto voice9 (Voice& v) -> void;
|
auto voice9 (Voice& v) -> void;
|
||||||
|
|
||||||
//echo
|
//echo
|
||||||
auto calculateFIR(signed i, bool channel) -> signed;
|
auto calculateFIR(int i, bool channel) -> int;
|
||||||
auto echoOutput(bool channel) -> signed;
|
auto echoOutput(bool channel) -> int;
|
||||||
auto echoRead(bool channel) -> void;
|
auto echoRead(bool channel) -> void;
|
||||||
auto echoWrite(bool channel) -> void;
|
auto echoWrite(bool channel) -> void;
|
||||||
auto echo22() -> void;
|
auto echo22() -> void;
|
||||||
|
|
|
@ -128,6 +128,11 @@ struct Settings {
|
||||||
bool blurEmulation = true;
|
bool blurEmulation = true;
|
||||||
bool colorEmulation = true;
|
bool colorEmulation = true;
|
||||||
bool scanlineEmulation = true;
|
bool scanlineEmulation = true;
|
||||||
|
|
||||||
|
uint controllerPort1 = 0;
|
||||||
|
uint controllerPort2 = 0;
|
||||||
|
uint expansionPort = 0;
|
||||||
|
bool random = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Interface* interface;
|
extern Interface* interface;
|
||||||
|
|
|
@ -10,6 +10,7 @@ PPU ppu;
|
||||||
#include "sprite/sprite.cpp"
|
#include "sprite/sprite.cpp"
|
||||||
#include "window/window.cpp"
|
#include "window/window.cpp"
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
|
#include "video.cpp"
|
||||||
|
|
||||||
PPU::PPU() :
|
PPU::PPU() :
|
||||||
bg1(*this, Background::ID::BG1),
|
bg1(*this, Background::ID::BG1),
|
||||||
|
@ -119,6 +120,7 @@ auto PPU::reset() -> void {
|
||||||
sprite.reset();
|
sprite.reset();
|
||||||
window.reset();
|
window.reset();
|
||||||
screen.reset();
|
screen.reset();
|
||||||
|
video.reset();
|
||||||
|
|
||||||
frame();
|
frame();
|
||||||
}
|
}
|
||||||
|
@ -141,7 +143,7 @@ auto PPU::scanline() -> void {
|
||||||
screen.scanline();
|
screen.scanline();
|
||||||
|
|
||||||
if(vcounter() == 241) {
|
if(vcounter() == 241) {
|
||||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
video.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ privileged:
|
||||||
#include "screen/screen.hpp"
|
#include "screen/screen.hpp"
|
||||||
#include "sprite/sprite.hpp"
|
#include "sprite/sprite.hpp"
|
||||||
#include "window/window.hpp"
|
#include "window/window.hpp"
|
||||||
|
#include "video.hpp"
|
||||||
|
|
||||||
Background bg1;
|
Background bg1;
|
||||||
Background bg2;
|
Background bg2;
|
||||||
|
@ -46,6 +47,7 @@ privileged:
|
||||||
Sprite sprite;
|
Sprite sprite;
|
||||||
Window window;
|
Window window;
|
||||||
Screen screen;
|
Screen screen;
|
||||||
|
Video video;
|
||||||
|
|
||||||
static auto Enter() -> void;
|
static auto Enter() -> void;
|
||||||
alwaysinline auto add_clocks(uint) -> void;
|
alwaysinline auto add_clocks(uint) -> void;
|
||||||
|
@ -57,7 +59,6 @@ privileged:
|
||||||
friend class PPU::Sprite;
|
friend class PPU::Sprite;
|
||||||
friend class PPU::Window;
|
friend class PPU::Window;
|
||||||
friend class PPU::Screen;
|
friend class PPU::Screen;
|
||||||
friend class Video;
|
|
||||||
|
|
||||||
struct Debugger {
|
struct Debugger {
|
||||||
hook<void (uint16, uint8)> vram_read;
|
hook<void (uint16, uint8)> vram_read;
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
Video video;
|
PPU::Video::Video() {
|
||||||
|
|
||||||
Video::Video() {
|
|
||||||
output = new uint32[512 * 512]();
|
output = new uint32[512 * 512]();
|
||||||
|
output += 16 * 512; //overscan padding
|
||||||
|
|
||||||
|
paletteLiteral = new uint32[1 << 19];
|
||||||
paletteStandard = new uint32[1 << 19];
|
paletteStandard = new uint32[1 << 19];
|
||||||
paletteEmulation = new uint32[1 << 19];
|
paletteEmulation = new uint32[1 << 19];
|
||||||
|
|
||||||
output += 16 * 512; //overscan padding
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Video::~Video() {
|
PPU::Video::~Video() {
|
||||||
output -= 16 * 512;
|
output -= 16 * 512;
|
||||||
delete[] output;
|
delete[] output;
|
||||||
|
|
||||||
|
delete[] paletteLiteral;
|
||||||
delete[] paletteStandard;
|
delete[] paletteStandard;
|
||||||
delete[] paletteEmulation;
|
delete[] paletteEmulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Video::reset() -> void {
|
auto PPU::Video::reset() -> void {
|
||||||
memory::fill(output, 512 * 480 * sizeof(uint32)); //padding area already cleared
|
memory::fill(output, 512 * 480 * sizeof(uint32));
|
||||||
|
|
||||||
for(auto color : range(1 << 19)) {
|
for(auto color : range(1 << 19)) {
|
||||||
uint l = (uint4)(color >> 15);
|
uint l = (uint4)(color >> 15);
|
||||||
|
@ -26,28 +27,31 @@ auto Video::reset() -> void {
|
||||||
|
|
||||||
double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.5);
|
double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.5);
|
||||||
|
|
||||||
{ uint R = L * image::normalize(r, 5, 8);
|
{ paletteLiteral[color] = color;
|
||||||
uint G = L * image::normalize(g, 5, 8);
|
|
||||||
uint B = L * image::normalize(b, 5, 8);
|
|
||||||
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{ uint R = L * gammaRamp[r];
|
{ uint R = L * image::normalize(r, 5, 16);
|
||||||
uint G = L * gammaRamp[g];
|
uint G = L * image::normalize(g, 5, 16);
|
||||||
uint B = L * gammaRamp[b];
|
uint B = L * image::normalize(b, 5, 16);
|
||||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for(auto color : range(1 << 19)) {
|
{ static const uint8 gammaRamp[32] = {
|
||||||
uint l = (uint4)(color >> 15);
|
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
|
||||||
uint b = (uint5)(color >> 10);
|
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
|
||||||
uint g = (uint5)(color >> 5);
|
0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
|
||||||
uint r = (uint5)(color >> 0);
|
0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint R = L * gammaRamp[r] * 0x0101;
|
||||||
|
uint G = L * gammaRamp[g] * 0x0101;
|
||||||
|
uint B = L * gammaRamp[b] * 0x0101;
|
||||||
|
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Video::refresh() -> void {
|
auto PPU::Video::refresh() -> void {
|
||||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||||
|
|
||||||
if(settings.scanlineEmulation) {
|
if(settings.scanlineEmulation) {
|
||||||
|
@ -109,13 +113,29 @@ auto Video::refresh() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCursors();
|
drawCursors();
|
||||||
|
|
||||||
interface->videoRefresh(output - (ppu.overscan() ? 0 : 7 * 1024), 512 * sizeof(uint32), 512, 480);
|
interface->videoRefresh(output - (ppu.overscan() ? 0 : 7 * 1024), 512 * sizeof(uint32), 512, 480);
|
||||||
|
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
//internal
|
auto PPU::Video::drawCursor(uint32 color, int x, int y) -> void {
|
||||||
|
static const uint8 cursor[15 * 15] = {
|
||||||
|
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
|
||||||
|
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
|
||||||
|
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
|
||||||
|
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
|
||||||
|
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
|
||||||
|
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
|
||||||
|
1,2,2,1,1,2,2,2,2,2,1,1,2,2,1,
|
||||||
|
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
|
||||||
|
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
|
||||||
|
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
|
||||||
|
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
|
||||||
|
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
|
||||||
|
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
|
||||||
|
};
|
||||||
|
|
||||||
auto Video::drawCursor(uint32 color, int x, int y) -> void {
|
|
||||||
auto data = (uint32*)output;
|
auto data = (uint32*)output;
|
||||||
if(ppu.interlace() && ppu.field()) data += 512;
|
if(ppu.interlace() && ppu.field()) data += 512;
|
||||||
|
|
||||||
|
@ -138,8 +158,8 @@ auto Video::drawCursor(uint32 color, int x, int y) -> void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Video::drawCursors() -> void {
|
auto PPU::Video::drawCursors() -> void {
|
||||||
switch(configuration.controllerPort2) {
|
switch((Device::ID)settings.controllerPort2) {
|
||||||
case Device::ID::SuperScope:
|
case Device::ID::SuperScope:
|
||||||
if(dynamic_cast<SuperScope*>(device.controllerPort2)) {
|
if(dynamic_cast<SuperScope*>(device.controllerPort2)) {
|
||||||
auto& controller = (SuperScope&)*device.controllerPort2;
|
auto& controller = (SuperScope&)*device.controllerPort2;
|
||||||
|
@ -157,28 +177,3 @@ auto Video::drawCursors() -> void {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8 Video::gammaRamp[32] = {
|
|
||||||
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
|
|
||||||
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
|
|
||||||
0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
|
|
||||||
0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
|
|
||||||
};
|
|
||||||
|
|
||||||
const uint8 Video::cursor[15 * 15] = {
|
|
||||||
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
|
|
||||||
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
|
|
||||||
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
|
|
||||||
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
|
|
||||||
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
|
|
||||||
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
|
|
||||||
1,2,2,1,1,2,2,2,2,2,1,1,2,2,1,
|
|
||||||
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
|
|
||||||
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
|
|
||||||
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
|
|
||||||
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
|
|
||||||
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
|
|
||||||
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
|
|
||||||
};
|
|
|
@ -5,18 +5,12 @@ struct Video {
|
||||||
auto reset() -> void;
|
auto reset() -> void;
|
||||||
auto refresh() -> void;
|
auto refresh() -> void;
|
||||||
|
|
||||||
uint32* output = nullptr;
|
|
||||||
uint32* paletteStandard = nullptr;
|
|
||||||
uint32* paletteEmulation = nullptr;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto drawCursor(uint32 color, int x, int y) -> void;
|
auto drawCursor(uint32 color, int x, int y) -> void;
|
||||||
auto drawCursors() -> void;
|
auto drawCursors() -> void;
|
||||||
|
|
||||||
static const uint8 gammaRamp[32];
|
uint32* output = nullptr;
|
||||||
static const uint8 cursor[15 * 15];
|
uint32* paletteLiteral = nullptr;
|
||||||
|
uint32* paletteStandard = nullptr;
|
||||||
friend class System;
|
uint32* paletteEmulation = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Video video;
|
|
|
@ -1,62 +0,0 @@
|
||||||
Audio audio;
|
|
||||||
|
|
||||||
auto Audio::coprocessor_enable(bool state) -> void {
|
|
||||||
coprocessor = state;
|
|
||||||
dspaudio.clear();
|
|
||||||
|
|
||||||
dsp_rdoffset = cop_rdoffset = 0;
|
|
||||||
dsp_wroffset = cop_wroffset = 0;
|
|
||||||
dsp_length = cop_length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Audio::coprocessor_frequency(double input_frequency) -> void {
|
|
||||||
dspaudio.setFrequency(input_frequency);
|
|
||||||
dspaudio.setResampler(nall::DSP::ResampleEngine::Sinc);
|
|
||||||
dspaudio.setResamplerFrequency(system.apuFrequency() / 768.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Audio::sample(int16 lsample, int16 rsample) -> void {
|
|
||||||
if(coprocessor == false) return interface->audioSample(lsample, rsample);
|
|
||||||
|
|
||||||
dsp_buffer[dsp_wroffset] = ((uint16)lsample << 0) + ((uint16)rsample << 16);
|
|
||||||
dsp_wroffset = (dsp_wroffset + 1) & buffer_mask;
|
|
||||||
dsp_length = (dsp_length + 1) & buffer_mask;
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Audio::coprocessor_sample(int16 lsample, int16 rsample) -> void {
|
|
||||||
int samples[] = {lsample, rsample};
|
|
||||||
dspaudio.sample(samples);
|
|
||||||
while(dspaudio.pending()) {
|
|
||||||
dspaudio.read(samples);
|
|
||||||
|
|
||||||
cop_buffer[cop_wroffset] = ((uint16)samples[0] << 0) + ((uint16)samples[1] << 16);
|
|
||||||
cop_wroffset = (cop_wroffset + 1) & buffer_mask;
|
|
||||||
cop_length = (cop_length + 1) & buffer_mask;
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Audio::flush() -> void {
|
|
||||||
while(dsp_length > 0 && cop_length > 0) {
|
|
||||||
uint32 dsp_sample = dsp_buffer[dsp_rdoffset];
|
|
||||||
uint32 cop_sample = cop_buffer[cop_rdoffset];
|
|
||||||
|
|
||||||
dsp_rdoffset = (dsp_rdoffset + 1) & buffer_mask;
|
|
||||||
cop_rdoffset = (cop_rdoffset + 1) & buffer_mask;
|
|
||||||
|
|
||||||
dsp_length--;
|
|
||||||
cop_length--;
|
|
||||||
|
|
||||||
int dsp_left = (int16)(dsp_sample >> 0);
|
|
||||||
int dsp_right = (int16)(dsp_sample >> 16);
|
|
||||||
|
|
||||||
int cop_left = (int16)(cop_sample >> 0);
|
|
||||||
int cop_right = (int16)(cop_sample >> 16);
|
|
||||||
|
|
||||||
interface->audioSample(
|
|
||||||
sclamp<16>(dsp_left + cop_left ),
|
|
||||||
sclamp<16>(dsp_right + cop_right)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
struct Audio {
|
|
||||||
auto coprocessor_enable(bool state) -> void;
|
|
||||||
auto coprocessor_frequency(double frequency) -> void;
|
|
||||||
auto sample(int16 lsample, int16 rsample) -> void;
|
|
||||||
auto coprocessor_sample(int16 lsample, int16 rsample) -> void;
|
|
||||||
|
|
||||||
private:
|
|
||||||
auto flush() -> void;
|
|
||||||
|
|
||||||
nall::DSP dspaudio;
|
|
||||||
bool coprocessor;
|
|
||||||
enum : uint { buffer_size = 256, buffer_mask = buffer_size - 1 };
|
|
||||||
uint32 dsp_buffer[buffer_size], cop_buffer[buffer_size];
|
|
||||||
uint dsp_rdoffset, cop_rdoffset;
|
|
||||||
uint dsp_wroffset, cop_wroffset;
|
|
||||||
uint dsp_length, cop_length;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Audio audio;
|
|
|
@ -32,12 +32,12 @@ auto Device::connect(uint port, Device::ID id) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(port) {
|
switch(port) {
|
||||||
case 0: configuration.controllerPort1 = id; break;
|
case 0: settings.controllerPort1 = (uint)id; break;
|
||||||
case 1: configuration.controllerPort2 = id; break;
|
case 1: settings.controllerPort2 = (uint)id; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(port == 2) {
|
if(port == 2) {
|
||||||
configuration.expansionPort = id;
|
settings.expansionPort = (uint)id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
Random random;
|
||||||
|
|
||||||
|
auto Random::seed(uint seed) -> void {
|
||||||
|
iter = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Random::operator()(uint result) -> uint {
|
||||||
|
if(!settings.random) return result;
|
||||||
|
return iter = (iter >> 1) ^ (((iter & 1) - 1) & 0xedb88320);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Random::serialize(serializer& s) -> void {
|
||||||
|
s.integer(iter);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
auto System::serialize() -> serializer {
|
auto System::serialize() -> serializer {
|
||||||
serializer s(serializeSize);
|
serializer s(_serializeSize);
|
||||||
|
|
||||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||||
char hash[64], description[512], profile[16];
|
char hash[64], description[512], profile[16];
|
||||||
|
@ -37,13 +37,11 @@ auto System::unserialize(serializer& s) -> bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//========
|
|
||||||
//internal
|
//internal
|
||||||
//========
|
|
||||||
|
|
||||||
auto System::serialize(serializer& s) -> void {
|
auto System::serialize(serializer& s) -> void {
|
||||||
s.integer((uint&)region);
|
s.integer((uint&)_region);
|
||||||
s.integer((uint&)expansionPort);
|
s.integer((uint&)_expansionPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::serializeAll(serializer& s) -> void {
|
auto System::serializeAll(serializer& s) -> void {
|
||||||
|
@ -89,5 +87,5 @@ auto System::serializeInit() -> void {
|
||||||
s.array(description);
|
s.array(description);
|
||||||
|
|
||||||
serializeAll(s);
|
serializeAll(s);
|
||||||
serializeSize = s.size();
|
_serializeSize = s.size();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,53 +3,46 @@
|
||||||
namespace SuperFamicom {
|
namespace SuperFamicom {
|
||||||
|
|
||||||
System system;
|
System system;
|
||||||
Configuration configuration;
|
|
||||||
Random random;
|
|
||||||
|
|
||||||
#include "video.cpp"
|
|
||||||
#include "audio.cpp"
|
|
||||||
#include "device.cpp"
|
#include "device.cpp"
|
||||||
|
#include "random.cpp"
|
||||||
#include "serialization.cpp"
|
#include "serialization.cpp"
|
||||||
|
|
||||||
#include <sfc/scheduler/scheduler.cpp>
|
#include <sfc/scheduler/scheduler.cpp>
|
||||||
|
|
||||||
System::System() {
|
auto System::region() const -> Region { return _region; }
|
||||||
region = Region::Autodetect;
|
auto System::expansionPort() const -> Device::ID { return _expansionPort; }
|
||||||
expansionPort = Device::ID::eBoot;
|
auto System::cpuFrequency() const -> uint { return _cpuFrequency; }
|
||||||
}
|
auto System::apuFrequency() const -> uint { return _apuFrequency; }
|
||||||
|
|
||||||
auto System::run() -> void {
|
auto System::run() -> void {
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||||
|
|
||||||
scheduler.enter();
|
scheduler.enter();
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::runToSave() -> void {
|
auto System::runToSave() -> void {
|
||||||
if(CPU::Threaded == true) {
|
if(CPU::Threaded) {
|
||||||
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
||||||
runThreadToSave();
|
runThreadToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SMP::Threaded == true) {
|
if(SMP::Threaded) {
|
||||||
scheduler.thread = smp.thread;
|
scheduler.thread = smp.thread;
|
||||||
runThreadToSave();
|
runThreadToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(PPU::Threaded == true) {
|
if(PPU::Threaded) {
|
||||||
scheduler.thread = ppu.thread;
|
scheduler.thread = ppu.thread;
|
||||||
runThreadToSave();
|
runThreadToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(DSP::Threaded == true) {
|
if(DSP::Threaded) {
|
||||||
scheduler.thread = dsp.thread;
|
scheduler.thread = dsp.thread;
|
||||||
runThreadToSave();
|
runThreadToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
for(unsigned i = 0; i < cpu.coprocessors.size(); i++) {
|
for(uint n = 0; n < cpu.coprocessors.size(); n++) {
|
||||||
auto& chip = *cpu.coprocessors[i];
|
auto& chip = *cpu.coprocessors[n];
|
||||||
scheduler.thread = chip.thread;
|
scheduler.thread = chip.thread;
|
||||||
runThreadToSave();
|
runThreadToSave();
|
||||||
}
|
}
|
||||||
|
@ -59,9 +52,6 @@ auto System::runThreadToSave() -> void {
|
||||||
while(true) {
|
while(true) {
|
||||||
scheduler.enter();
|
scheduler.enter();
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
|
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
|
||||||
video.refresh();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,8 +79,9 @@ auto System::init() -> void {
|
||||||
|
|
||||||
bsmemory.init();
|
bsmemory.init();
|
||||||
|
|
||||||
device.connect(0, configuration.controllerPort1);
|
device.connect(0, (Device::ID)settings.controllerPort1);
|
||||||
device.connect(1, configuration.controllerPort2);
|
device.connect(1, (Device::ID)settings.controllerPort2);
|
||||||
|
device.connect(2, (Device::ID)settings.expansionPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto System::term() -> void {
|
auto System::term() -> void {
|
||||||
|
@ -104,15 +95,10 @@ auto System::load() -> void {
|
||||||
interface->loadRequest(ID::IPLROM, iplrom, true);
|
interface->loadRequest(ID::IPLROM, iplrom, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
region = configuration.region;
|
_region = cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL;
|
||||||
if(region == Region::Autodetect) {
|
_expansionPort = (Device::ID)settings.expansionPort;
|
||||||
region = (cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL);
|
_cpuFrequency = region() == Region::NTSC ? 21477272 : 21281370;
|
||||||
}
|
_apuFrequency = 24606720;
|
||||||
expansionPort = configuration.expansionPort;
|
|
||||||
cpuFrequency = region() == Region::NTSC ? 21477272 : 21281370;
|
|
||||||
apuFrequency = 24606720;
|
|
||||||
|
|
||||||
audio.coprocessor_enable(false);
|
|
||||||
|
|
||||||
bus.reset();
|
bus.reset();
|
||||||
bus.map();
|
bus.map();
|
||||||
|
@ -240,10 +226,10 @@ auto System::reset() -> void {
|
||||||
if(cartridge.hasSPC7110()) cpu.coprocessors.append(&spc7110);
|
if(cartridge.hasSPC7110()) cpu.coprocessors.append(&spc7110);
|
||||||
if(cartridge.hasMSU1()) cpu.coprocessors.append(&msu1);
|
if(cartridge.hasMSU1()) cpu.coprocessors.append(&msu1);
|
||||||
|
|
||||||
video.reset();
|
|
||||||
scheduler.init();
|
scheduler.init();
|
||||||
device.connect(0, configuration.controllerPort1);
|
device.connect(0, (Device::ID)settings.controllerPort1);
|
||||||
device.connect(1, configuration.controllerPort2);
|
device.connect(1, (Device::ID)settings.controllerPort2);
|
||||||
|
device.connect(2, (Device::ID)settings.expansionPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
struct Interface;
|
struct Interface;
|
||||||
|
|
||||||
#include "video.hpp"
|
|
||||||
#include "audio.hpp"
|
|
||||||
#include "device.hpp"
|
#include "device.hpp"
|
||||||
|
|
||||||
struct System : property<System> {
|
struct System {
|
||||||
enum class Region : uint { NTSC = 0, PAL = 1, Autodetect = 2 };
|
enum class Region : bool { NTSC = 0, PAL = 1 };
|
||||||
|
|
||||||
System();
|
auto region() const -> Region;
|
||||||
|
auto expansionPort() const -> Device::ID;
|
||||||
|
auto cpuFrequency() const -> uint;
|
||||||
|
auto apuFrequency() const -> uint;
|
||||||
|
|
||||||
auto run() -> void;
|
auto run() -> void;
|
||||||
auto runToSave() -> void;
|
auto runToSave() -> void;
|
||||||
|
@ -19,14 +20,6 @@ struct System : property<System> {
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
auto reset() -> void;
|
auto reset() -> void;
|
||||||
|
|
||||||
//return *active* system information (settings are cached upon power-on)
|
|
||||||
readonly<Region> region;
|
|
||||||
readonly<Device::ID> expansionPort;
|
|
||||||
|
|
||||||
readonly<uint> cpuFrequency;
|
|
||||||
readonly<uint> apuFrequency;
|
|
||||||
readonly<uint> serializeSize;
|
|
||||||
|
|
||||||
auto serialize() -> serializer;
|
auto serialize() -> serializer;
|
||||||
auto unserialize(serializer&) -> bool;
|
auto unserialize(serializer&) -> bool;
|
||||||
|
|
||||||
|
@ -41,9 +34,13 @@ private:
|
||||||
auto serializeAll(serializer&) -> void;
|
auto serializeAll(serializer&) -> void;
|
||||||
auto serializeInit() -> void;
|
auto serializeInit() -> void;
|
||||||
|
|
||||||
|
Region _region = Region::NTSC;
|
||||||
|
Device::ID _expansionPort = Device::ID::None;
|
||||||
|
uint _cpuFrequency = 0;
|
||||||
|
uint _apuFrequency = 0;
|
||||||
|
uint _serializeSize = 0;
|
||||||
|
|
||||||
friend class Cartridge;
|
friend class Cartridge;
|
||||||
friend class Video;
|
|
||||||
friend class Audio;
|
|
||||||
friend class Device;
|
friend class Device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,29 +48,10 @@ extern System system;
|
||||||
|
|
||||||
#include <sfc/scheduler/scheduler.hpp>
|
#include <sfc/scheduler/scheduler.hpp>
|
||||||
|
|
||||||
struct Configuration {
|
|
||||||
Device::ID controllerPort1 = Device::ID::None;
|
|
||||||
Device::ID controllerPort2 = Device::ID::None;
|
|
||||||
Device::ID expansionPort = Device::ID::None;
|
|
||||||
System::Region region = System::Region::Autodetect;
|
|
||||||
bool random = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Configuration configuration;
|
|
||||||
|
|
||||||
struct Random {
|
struct Random {
|
||||||
auto seed(uint seed) -> void {
|
auto seed(uint seed) -> void;
|
||||||
iter = seed;
|
auto operator()(uint result) -> uint;
|
||||||
}
|
auto serialize(serializer& s) -> void;
|
||||||
|
|
||||||
auto operator()(uint result) -> uint {
|
|
||||||
if(configuration.random == false) return result;
|
|
||||||
return iter = (iter >> 1) ^ (((iter & 1) - 1) & 0xedb88320);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto serialize(serializer& s) -> void {
|
|
||||||
s.integer(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint iter = 0;
|
uint iter = 0;
|
||||||
|
|
|
@ -3,6 +3,7 @@ name := higan
|
||||||
processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050
|
processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050
|
||||||
include processor/GNUmakefile
|
include processor/GNUmakefile
|
||||||
|
|
||||||
|
include emulator/GNUmakefile
|
||||||
include fc/GNUmakefile
|
include fc/GNUmakefile
|
||||||
include sfc/GNUmakefile
|
include sfc/GNUmakefile
|
||||||
include gb/GNUmakefile
|
include gb/GNUmakefile
|
||||||
|
|
|
@ -47,7 +47,11 @@ auto Icarus::famicomImport(vector<uint8>& buffer, string location) -> string {
|
||||||
uint chrrom = 0;
|
uint chrrom = 0;
|
||||||
auto markup = famicomManifest(buffer, location, &prgrom, &chrrom);
|
auto markup = famicomManifest(buffer, location, &prgrom, &chrrom);
|
||||||
if(!markup) return failure("failed to parse ROM image");
|
if(!markup) return failure("failed to parse ROM image");
|
||||||
|
|
||||||
if(!directory::create(target)) return failure("library path unwritable");
|
if(!directory::create(target)) return failure("library path unwritable");
|
||||||
|
if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) {
|
||||||
|
file::copy({source, name, ".sav"}, {target, "save.ram"});
|
||||||
|
}
|
||||||
|
|
||||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
||||||
file::write({target, "ines.rom"}, buffer.data(), 16);
|
file::write({target, "ines.rom"}, buffer.data(), 16);
|
||||||
|
|
|
@ -39,7 +39,11 @@ auto Icarus::gameBoyAdvanceImport(vector<uint8>& buffer, string location) -> str
|
||||||
|
|
||||||
auto markup = gameBoyAdvanceManifest(buffer, location);
|
auto markup = gameBoyAdvanceManifest(buffer, location);
|
||||||
if(!markup) return failure("failed to parse ROM image");
|
if(!markup) return failure("failed to parse ROM image");
|
||||||
|
|
||||||
if(!directory::create(target)) return failure("library path unwritable");
|
if(!directory::create(target)) return failure("library path unwritable");
|
||||||
|
if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) {
|
||||||
|
file::copy({source, name, ".sav"}, {target, "save.ram"});
|
||||||
|
}
|
||||||
|
|
||||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
||||||
file::write({target, "program.rom"}, buffer);
|
file::write({target, "program.rom"}, buffer);
|
||||||
|
|
|
@ -39,7 +39,11 @@ auto Icarus::gameBoyColorImport(vector<uint8>& buffer, string location) -> strin
|
||||||
|
|
||||||
auto markup = gameBoyColorManifest(buffer, location);
|
auto markup = gameBoyColorManifest(buffer, location);
|
||||||
if(!markup) return failure("failed to parse ROM image");
|
if(!markup) return failure("failed to parse ROM image");
|
||||||
|
|
||||||
if(!directory::create(target)) return failure("library path unwritable");
|
if(!directory::create(target)) return failure("library path unwritable");
|
||||||
|
if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) {
|
||||||
|
file::copy({source, name, ".sav"}, {target, "save.ram"});
|
||||||
|
}
|
||||||
|
|
||||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
||||||
file::write({target, "program.rom"}, buffer);
|
file::write({target, "program.rom"}, buffer);
|
||||||
|
|
|
@ -39,7 +39,11 @@ auto Icarus::gameBoyImport(vector<uint8>& buffer, string location) -> string {
|
||||||
|
|
||||||
auto markup = gameBoyManifest(buffer, location);
|
auto markup = gameBoyManifest(buffer, location);
|
||||||
if(!markup) return failure("failed to parse ROM image");
|
if(!markup) return failure("failed to parse ROM image");
|
||||||
|
|
||||||
if(!directory::create(target)) return failure("library path unwritable");
|
if(!directory::create(target)) return failure("library path unwritable");
|
||||||
|
if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) {
|
||||||
|
file::copy({source, name, ".sav"}, {target, "save.ram"});
|
||||||
|
}
|
||||||
|
|
||||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
||||||
file::write({target, "program.rom"}, buffer);
|
file::write({target, "program.rom"}, buffer);
|
||||||
|
|
|
@ -66,6 +66,9 @@ auto Icarus::superFamicomImport(vector<uint8>& buffer, string location) -> strin
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!directory::create(target)) return failure("library path unwritable");
|
if(!directory::create(target)) return failure("library path unwritable");
|
||||||
|
if(file::exists({source, name, ".srm"}) && !file::exists({target, "save.ram"})) {
|
||||||
|
file::copy({source, name, ".srm"}, {target, "save.ram"});
|
||||||
|
}
|
||||||
|
|
||||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup);
|
||||||
uint offset = (buffer.size() & 0x7fff) == 512 ? 512 : 0; //skip header if present
|
uint offset = (buffer.size() & 0x7fff) == 512 ? 512 : 0; //skip header if present
|
||||||
|
|
|
@ -114,12 +114,9 @@ template<typename... P> inline auto execute(const string& name, P&&... p) -> str
|
||||||
nullptr, nullptr, &si, &pi
|
nullptr, nullptr, &si, &pi
|
||||||
)) return "";
|
)) return "";
|
||||||
|
|
||||||
while(true) {
|
if(WaitForSingleObject(pi.hProcess, INFINITE)) return "";
|
||||||
DWORD exitCode;
|
CloseHandle(pi.hThread);
|
||||||
GetExitCodeProcess(pi.hProcess, &exitCode);
|
CloseHandle(pi.hProcess);
|
||||||
Sleep(1);
|
|
||||||
if(exitCode != STILL_ACTIVE) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
string result;
|
string result;
|
||||||
while(true) {
|
while(true) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ auto activepath() -> string {
|
||||||
char path[PATH_MAX] = "";
|
char path[PATH_MAX] = "";
|
||||||
auto unused = getcwd(path, PATH_MAX);
|
auto unused = getcwd(path, PATH_MAX);
|
||||||
string result = path;
|
string result = path;
|
||||||
if(result.empty()) result = ".";
|
if(!result) result = ".";
|
||||||
result.transform("\\", "/");
|
result.transform("\\", "/");
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
|
@ -16,7 +16,7 @@ auto realpath(rstring name) -> string {
|
||||||
string result;
|
string result;
|
||||||
char path[PATH_MAX] = "";
|
char path[PATH_MAX] = "";
|
||||||
if(::realpath(name, path)) result = pathname(string{path}.transform("\\", "/"));
|
if(::realpath(name, path)) result = pathname(string{path}.transform("\\", "/"));
|
||||||
if(result.empty()) return activepath();
|
if(!result) return activepath();
|
||||||
result.transform("\\", "/");
|
result.transform("\\", "/");
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
|
@ -48,7 +48,7 @@ auto userpath() -> string {
|
||||||
struct passwd* userinfo = getpwuid(getuid());
|
struct passwd* userinfo = getpwuid(getuid());
|
||||||
string result = userinfo->pw_dir;
|
string result = userinfo->pw_dir;
|
||||||
#endif
|
#endif
|
||||||
if(result.empty()) result = ".";
|
if(!result) result = ".";
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -66,12 +66,12 @@ auto configpath() -> string {
|
||||||
#else
|
#else
|
||||||
string result = {userpath(), ".config/"};
|
string result = {userpath(), ".config/"};
|
||||||
#endif
|
#endif
|
||||||
if(result.empty()) result = ".";
|
if(!result) result = ".";
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /home/username/.local/
|
// /home/username/.local/share/
|
||||||
// c:/users/username/appdata/local/
|
// c:/users/username/appdata/local/
|
||||||
auto localpath() -> string {
|
auto localpath() -> string {
|
||||||
#if defined(PLATFORM_WINDOWS)
|
#if defined(PLATFORM_WINDOWS)
|
||||||
|
@ -82,9 +82,9 @@ auto localpath() -> string {
|
||||||
#elif defined(PLATFORM_MACOSX)
|
#elif defined(PLATFORM_MACOSX)
|
||||||
string result = {userpath(), "Library/Application Support/"};
|
string result = {userpath(), "Library/Application Support/"};
|
||||||
#else
|
#else
|
||||||
string result = {userpath(), ".local/"};
|
string result = {userpath(), ".local/share/"};
|
||||||
#endif
|
#endif
|
||||||
if(result.empty()) result = ".";
|
if(!result) result = ".";
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ auto sharedpath() -> string {
|
||||||
#else
|
#else
|
||||||
string result = "/usr/share/";
|
string result = "/usr/share/";
|
||||||
#endif
|
#endif
|
||||||
if(result.empty()) result = ".";
|
if(!result) result = ".";
|
||||||
if(result.endsWith("/") == false) result.append("/");
|
if(result.endsWith("/") == false) result.append("/");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue