o cleanup
This commit is contained in:
10
tools/bsnes/snes/audio/audio.cpp
Executable file
10
tools/bsnes/snes/audio/audio.cpp
Executable file
@@ -0,0 +1,10 @@
|
||||
#ifdef SNES_CPP
|
||||
|
||||
void SNES::Audio::update(uint16 l_sample, uint16 r_sample) {
|
||||
snesinterface.audio_sample(l_sample, r_sample);
|
||||
}
|
||||
|
||||
void SNES::Audio::init() {
|
||||
}
|
||||
|
||||
#endif //ifdef SNES_CPP
|
||||
7
tools/bsnes/snes/audio/audio.hpp
Executable file
7
tools/bsnes/snes/audio/audio.hpp
Executable file
@@ -0,0 +1,7 @@
|
||||
class Audio {
|
||||
public:
|
||||
void update(uint16 l_sample, uint16 r_sample);
|
||||
void init();
|
||||
|
||||
friend class SNES;
|
||||
} audio;
|
||||
341
tools/bsnes/snes/input/input.cpp
Executable file
341
tools/bsnes/snes/input/input.cpp
Executable file
@@ -0,0 +1,341 @@
|
||||
#ifdef SNES_CPP
|
||||
|
||||
uint8 SNES::Input::port_read(bool portnumber) {
|
||||
port_t &p = port[portnumber];
|
||||
|
||||
switch(p.device) {
|
||||
case DeviceJoypad: {
|
||||
if(p.counter0 >= 16) return 1;
|
||||
unsigned deviceid = (portnumber == 0 ? DeviceIDJoypad1 : DeviceIDJoypad2);
|
||||
return snesinterface.input_poll(deviceid, p.counter0++);
|
||||
} //case DeviceJoypad
|
||||
|
||||
case DeviceMultitap: {
|
||||
if(cpu.joylatch()) return 2; //when latch is high -- data2 = 1, data1 = 0
|
||||
|
||||
unsigned deviceidx, deviceid0, deviceid1;
|
||||
if(portnumber == 0) {
|
||||
if(cpu.pio() & 0x40) {
|
||||
deviceidx = p.counter0;
|
||||
if(deviceidx >= 16) return 3;
|
||||
p.counter0++;
|
||||
|
||||
deviceid0 = DeviceIDMultitap1A;
|
||||
deviceid1 = DeviceIDMultitap1B;
|
||||
} else {
|
||||
deviceidx = p.counter1;
|
||||
if(deviceidx >= 16) return 3;
|
||||
p.counter1++;
|
||||
|
||||
deviceid0 = DeviceIDMultitap1C;
|
||||
deviceid1 = DeviceIDMultitap1D;
|
||||
}
|
||||
} else {
|
||||
if(cpu.pio() & 0x80) {
|
||||
deviceidx = p.counter0;
|
||||
if(deviceidx >= 16) return 3;
|
||||
p.counter0++;
|
||||
|
||||
deviceid0 = DeviceIDMultitap2A;
|
||||
deviceid1 = DeviceIDMultitap2B;
|
||||
} else {
|
||||
deviceidx = p.counter1;
|
||||
if(deviceidx >= 16) return 3;
|
||||
p.counter1++;
|
||||
|
||||
deviceid0 = DeviceIDMultitap2C;
|
||||
deviceid1 = DeviceIDMultitap2D;
|
||||
}
|
||||
}
|
||||
|
||||
return (snesinterface.input_poll(deviceid0, deviceidx) << 0)
|
||||
| (snesinterface.input_poll(deviceid1, deviceidx) << 1);
|
||||
} //case DeviceMultitap
|
||||
|
||||
case DeviceMouse: {
|
||||
if(p.counter0 >= 32) return 1;
|
||||
unsigned deviceid = (portnumber == 0 ? DeviceIDMouse1 : DeviceIDMouse2);
|
||||
|
||||
int position_x = snesinterface.input_poll(deviceid, MouseX); //-n = left, 0 = center, +n = right
|
||||
int position_y = snesinterface.input_poll(deviceid, MouseY); //-n = up, 0 = center, +n = right
|
||||
|
||||
bool direction_x = position_x < 0; //0 = right, 1 = left
|
||||
bool direction_y = position_y < 0; //0 = down, 1 = up
|
||||
|
||||
if(position_x < 0) position_x = -position_x; //abs(position_x)
|
||||
if(position_y < 0) position_y = -position_y; //abs(position_x)
|
||||
|
||||
position_x = min(127, position_x); //range = 0 - 127
|
||||
position_y = min(127, position_y); //range = 0 - 127
|
||||
|
||||
switch(p.counter0++) { default:
|
||||
case 0: return 0;
|
||||
case 1: return 0;
|
||||
case 2: return 0;
|
||||
case 3: return 0;
|
||||
case 4: return 0;
|
||||
case 5: return 0;
|
||||
case 6: return 0;
|
||||
case 7: return 0;
|
||||
|
||||
case 8: return snesinterface.input_poll(deviceid, MouseRight);
|
||||
case 9: return snesinterface.input_poll(deviceid, MouseLeft);
|
||||
case 10: return 0; //speed (0 = slow, 1 = normal, 2 = fast, 3 = unused)
|
||||
case 11: return 0; // ||
|
||||
|
||||
case 12: return 0; //signature
|
||||
case 13: return 0; // ||
|
||||
case 14: return 0; // ||
|
||||
case 15: return 1; // ||
|
||||
|
||||
case 16: return (direction_y) & 1;
|
||||
case 17: return (position_y >> 6) & 1;
|
||||
case 18: return (position_y >> 5) & 1;
|
||||
case 19: return (position_y >> 4) & 1;
|
||||
case 20: return (position_y >> 3) & 1;
|
||||
case 21: return (position_y >> 2) & 1;
|
||||
case 22: return (position_y >> 1) & 1;
|
||||
case 23: return (position_y >> 0) & 1;
|
||||
|
||||
case 24: return (direction_x) & 1;
|
||||
case 25: return (position_x >> 6) & 1;
|
||||
case 26: return (position_x >> 5) & 1;
|
||||
case 27: return (position_x >> 4) & 1;
|
||||
case 28: return (position_x >> 3) & 1;
|
||||
case 29: return (position_x >> 2) & 1;
|
||||
case 30: return (position_x >> 1) & 1;
|
||||
case 31: return (position_x >> 0) & 1;
|
||||
}
|
||||
} //case DeviceMouse
|
||||
|
||||
case DeviceSuperScope: {
|
||||
if(portnumber == 0) break; //Super Scope in port 1 not supported ...
|
||||
if(p.counter0 >= 8) return 1;
|
||||
|
||||
if(p.counter0 == 0) {
|
||||
//turbo is a switch; toggle is edge sensitive
|
||||
bool turbo = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTurbo);
|
||||
if(turbo && !p.superscope.turbolock) {
|
||||
p.superscope.turbo = !p.superscope.turbo; //toggle state
|
||||
p.superscope.turbolock = true;
|
||||
} else if(!turbo) {
|
||||
p.superscope.turbolock = false;
|
||||
}
|
||||
|
||||
//trigger is a button
|
||||
//if turbo is active, trigger is level sensitive; otherwise it is edge sensitive
|
||||
p.superscope.trigger = false;
|
||||
bool trigger = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTrigger);
|
||||
if(trigger && (p.superscope.turbo || !p.superscope.triggerlock)) {
|
||||
p.superscope.trigger = true;
|
||||
p.superscope.triggerlock = true;
|
||||
} else if(!trigger) {
|
||||
p.superscope.triggerlock = false;
|
||||
}
|
||||
|
||||
//cursor is a button; it is always level sensitive
|
||||
p.superscope.cursor = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeCursor);
|
||||
|
||||
//pause is a button; it is always edge sensitive
|
||||
p.superscope.pause = false;
|
||||
bool pause = snesinterface.input_poll(DeviceIDSuperScope, SuperScopePause);
|
||||
if(pause && !p.superscope.pauselock) {
|
||||
p.superscope.pause = true;
|
||||
p.superscope.pauselock = true;
|
||||
} else if(!pause) {
|
||||
p.superscope.pauselock = false;
|
||||
}
|
||||
|
||||
p.superscope.offscreen =
|
||||
p.superscope.x < 0 || p.superscope.x >= 256
|
||||
|| p.superscope.y < 0 || p.superscope.y >= (ppu.overscan() ? 240 : 225);
|
||||
}
|
||||
|
||||
switch(p.counter0++) {
|
||||
case 0: return p.superscope.trigger;
|
||||
case 1: return p.superscope.cursor;
|
||||
case 2: return p.superscope.turbo;
|
||||
case 3: return p.superscope.pause;
|
||||
case 4: return 0;
|
||||
case 5: return 0;
|
||||
case 6: return p.superscope.offscreen;
|
||||
case 7: return 0; //noise (1 = yes)
|
||||
}
|
||||
} //case DeviceSuperScope
|
||||
|
||||
case DeviceJustifier:
|
||||
case DeviceJustifiers: {
|
||||
if(portnumber == 0) break; //Justifier in port 1 not supported ...
|
||||
if(p.counter0 >= 32) return 1;
|
||||
|
||||
if(p.counter0 == 0) {
|
||||
p.justifier.trigger1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierTrigger);
|
||||
p.justifier.start1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierStart);
|
||||
|
||||
if(p.device == DeviceJustifiers) {
|
||||
p.justifier.trigger2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierTrigger);
|
||||
p.justifier.start2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierStart);
|
||||
} else {
|
||||
p.justifier.x2 = -1;
|
||||
p.justifier.y2 = -1;
|
||||
|
||||
p.justifier.trigger2 = false;
|
||||
p.justifier.start2 = false;
|
||||
}
|
||||
}
|
||||
|
||||
switch(p.counter0++) {
|
||||
case 0: return 0;
|
||||
case 1: return 0;
|
||||
case 2: return 0;
|
||||
case 3: return 0;
|
||||
case 4: return 0;
|
||||
case 5: return 0;
|
||||
case 6: return 0;
|
||||
case 7: return 0;
|
||||
case 8: return 0;
|
||||
case 9: return 0;
|
||||
case 10: return 0;
|
||||
case 11: return 0;
|
||||
|
||||
case 12: return 1; //signature
|
||||
case 13: return 1; // ||
|
||||
case 14: return 1; // ||
|
||||
case 15: return 0; // ||
|
||||
|
||||
case 16: return 0;
|
||||
case 17: return 1;
|
||||
case 18: return 0;
|
||||
case 19: return 1;
|
||||
case 20: return 0;
|
||||
case 21: return 1;
|
||||
case 22: return 0;
|
||||
case 23: return 1;
|
||||
|
||||
case 24: return p.justifier.trigger1;
|
||||
case 25: return p.justifier.trigger2;
|
||||
case 26: return p.justifier.start1;
|
||||
case 27: return p.justifier.start2;
|
||||
case 28: return p.justifier.active;
|
||||
|
||||
case 29: return 0;
|
||||
case 30: return 0;
|
||||
case 31: return 0;
|
||||
}
|
||||
} //case DeviceJustifier(s)
|
||||
} //switch(p.device)
|
||||
|
||||
//no device connected
|
||||
return 0;
|
||||
}
|
||||
|
||||
//scan all input; update cursor positions if needed
|
||||
void SNES::Input::update() {
|
||||
snesinterface.input_poll();
|
||||
port_t &p = port[1];
|
||||
|
||||
switch(p.device) {
|
||||
case DeviceSuperScope: {
|
||||
int x = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeX);
|
||||
int y = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeY);
|
||||
x += p.superscope.x;
|
||||
y += p.superscope.y;
|
||||
p.superscope.x = max(-16, min(256 + 16, x));
|
||||
p.superscope.y = max(-16, min(240 + 16, y));
|
||||
|
||||
latchx = p.superscope.x;
|
||||
latchy = p.superscope.y;
|
||||
} break;
|
||||
|
||||
case DeviceJustifier:
|
||||
case DeviceJustifiers: {
|
||||
int x1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierX);
|
||||
int y1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierY);
|
||||
x1 += p.justifier.x1;
|
||||
y1 += p.justifier.y1;
|
||||
p.justifier.x1 = max(-16, min(256 + 16, x1));
|
||||
p.justifier.y1 = max(-16, min(240 + 16, y1));
|
||||
|
||||
int x2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierX);
|
||||
int y2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierY);
|
||||
x2 += p.justifier.x2;
|
||||
y2 += p.justifier.y2;
|
||||
p.justifier.x2 = max(-16, min(256 + 16, x2));
|
||||
p.justifier.y2 = max(-16, min(240 + 16, y2));
|
||||
|
||||
if(p.justifier.active == 0) {
|
||||
latchx = p.justifier.x1;
|
||||
latchy = p.justifier.y1;
|
||||
} else {
|
||||
latchx = (p.device == DeviceJustifiers ? p.justifier.x2 : -1);
|
||||
latchy = (p.device == DeviceJustifiers ? p.justifier.y2 : -1);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::Input::port_set_device(bool portnumber, unsigned device) {
|
||||
port_t &p = port[portnumber];
|
||||
|
||||
p.device = device;
|
||||
p.counter0 = 0;
|
||||
p.counter1 = 0;
|
||||
|
||||
//set iobit to true if device is capable of latching PPU counters
|
||||
iobit = port[1].device == DeviceSuperScope
|
||||
|| port[1].device == DeviceJustifier
|
||||
|| port[1].device == DeviceJustifiers;
|
||||
latchx = -1;
|
||||
latchy = -1;
|
||||
|
||||
if(device == DeviceSuperScope) {
|
||||
p.superscope.x = 256 / 2;
|
||||
p.superscope.y = 240 / 2;
|
||||
|
||||
p.superscope.trigger = false;
|
||||
p.superscope.cursor = false;
|
||||
p.superscope.turbo = false;
|
||||
p.superscope.pause = false;
|
||||
p.superscope.offscreen = false;
|
||||
|
||||
p.superscope.turbolock = false;
|
||||
p.superscope.triggerlock = false;
|
||||
p.superscope.pauselock = false;
|
||||
} else if(device == DeviceJustifier) {
|
||||
p.justifier.active = 0;
|
||||
p.justifier.x1 = 256 / 2;
|
||||
p.justifier.y1 = 240 / 2;
|
||||
p.justifier.x2 = -1;
|
||||
p.justifier.y2 = -1;
|
||||
|
||||
p.justifier.trigger1 = false;
|
||||
p.justifier.trigger2 = false;
|
||||
p.justifier.start1 = false;
|
||||
p.justifier.start2 = false;
|
||||
} else if(device == DeviceJustifiers) {
|
||||
p.justifier.active = 0;
|
||||
p.justifier.x1 = 256 / 2 - 16;
|
||||
p.justifier.y1 = 240 / 2;
|
||||
p.justifier.x2 = 256 / 2 + 16;
|
||||
p.justifier.y2 = 240 / 2;
|
||||
|
||||
p.justifier.trigger1 = false;
|
||||
p.justifier.trigger2 = false;
|
||||
p.justifier.start1 = false;
|
||||
p.justifier.start2 = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::Input::poll() {
|
||||
port[0].counter0 = 0;
|
||||
port[0].counter1 = 0;
|
||||
port[1].counter0 = 0;
|
||||
port[1].counter1 = 0;
|
||||
|
||||
port[1].justifier.active = !port[1].justifier.active;
|
||||
}
|
||||
|
||||
void SNES::Input::init() {
|
||||
}
|
||||
|
||||
#endif //ifdef SNES_CPP
|
||||
114
tools/bsnes/snes/input/input.hpp
Executable file
114
tools/bsnes/snes/input/input.hpp
Executable file
@@ -0,0 +1,114 @@
|
||||
class Input {
|
||||
public:
|
||||
enum Device {
|
||||
DeviceNone,
|
||||
DeviceJoypad,
|
||||
DeviceMultitap,
|
||||
DeviceMouse,
|
||||
DeviceSuperScope,
|
||||
DeviceJustifier,
|
||||
DeviceJustifiers,
|
||||
};
|
||||
|
||||
enum DeviceID {
|
||||
DeviceIDNone,
|
||||
DeviceIDJoypad1,
|
||||
DeviceIDJoypad2,
|
||||
DeviceIDMultitap1A,
|
||||
DeviceIDMultitap1B,
|
||||
DeviceIDMultitap1C,
|
||||
DeviceIDMultitap1D,
|
||||
DeviceIDMultitap2A,
|
||||
DeviceIDMultitap2B,
|
||||
DeviceIDMultitap2C,
|
||||
DeviceIDMultitap2D,
|
||||
DeviceIDMouse1,
|
||||
DeviceIDMouse2,
|
||||
DeviceIDSuperScope,
|
||||
DeviceIDJustifier1,
|
||||
DeviceIDJustifier2,
|
||||
};
|
||||
|
||||
enum JoypadID {
|
||||
JoypadB = 0, JoypadY = 1,
|
||||
JoypadSelect = 2, JoypadStart = 3,
|
||||
JoypadUp = 4, JoypadDown = 5,
|
||||
JoypadLeft = 6, JoypadRight = 7,
|
||||
JoypadA = 8, JoypadX = 9,
|
||||
JoypadL = 10, JoypadR = 11,
|
||||
};
|
||||
|
||||
enum MouseID {
|
||||
MouseX = 0, MouseY = 1,
|
||||
MouseLeft = 2, MouseRight = 3,
|
||||
};
|
||||
|
||||
enum SuperScopeID {
|
||||
SuperScopeX = 0, SuperScopeY = 1,
|
||||
SuperScopeTrigger = 2, SuperScopeCursor = 3,
|
||||
SuperScopeTurbo = 4, SuperScopePause = 5,
|
||||
};
|
||||
|
||||
enum JustifierID {
|
||||
JustifierX = 0, JustifierY = 1,
|
||||
JustifierTrigger = 2, JustifierStart = 3,
|
||||
};
|
||||
|
||||
uint8 port_read(bool port);
|
||||
void port_set_device(bool port, unsigned device);
|
||||
void init();
|
||||
void poll();
|
||||
void update();
|
||||
|
||||
//light guns (Super Scope, Justifier(s)) strobe IOBit whenever the CRT
|
||||
//beam cannon is detected. this needs to be tested at the cycle level
|
||||
//(hence inlining here for speed) to avoid 'dead space' during DRAM refresh.
|
||||
//iobit is updated during port_set_device(),
|
||||
//latchx, latchy are updated during update() (once per frame)
|
||||
alwaysinline void tick() {
|
||||
//only test if Super Scope or Justifier is connected
|
||||
if(iobit) {
|
||||
if(ppu.vcounter() == latchy //test Y cursor position
|
||||
&& ppu.hcounter() == latchx << 2 //test X cursor position (cycles == pixels << 2)
|
||||
&& latchy < (ppu.overscan() ? 240 : 225) //verify Y is not offscreen
|
||||
&& latchx < 256 //verify X is not offscreen
|
||||
) ppu.latch_counters();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool iobit;
|
||||
uint16_t latchx, latchy;
|
||||
|
||||
struct port_t {
|
||||
unsigned device;
|
||||
unsigned counter0; //read counters
|
||||
unsigned counter1;
|
||||
|
||||
struct superscope_t {
|
||||
int x, y;
|
||||
|
||||
bool trigger;
|
||||
bool cursor;
|
||||
bool turbo;
|
||||
bool pause;
|
||||
bool offscreen;
|
||||
|
||||
bool turbolock;
|
||||
bool triggerlock;
|
||||
bool pauselock;
|
||||
} superscope;
|
||||
|
||||
struct justifier_t {
|
||||
bool active;
|
||||
|
||||
int x1, x2;
|
||||
int y1, y2;
|
||||
|
||||
bool trigger1, trigger2;
|
||||
bool start1, start2;
|
||||
} justifier;
|
||||
} port[2];
|
||||
|
||||
friend class SNES;
|
||||
} input;
|
||||
17
tools/bsnes/snes/interface/interface.hpp
Executable file
17
tools/bsnes/snes/interface/interface.hpp
Executable file
@@ -0,0 +1,17 @@
|
||||
//====================
|
||||
//SNES interface class
|
||||
//====================
|
||||
//Interfaces SNES core with platform-specific functionality (video, audio, input, ...)
|
||||
|
||||
class SNESInterface {
|
||||
public:
|
||||
void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height);
|
||||
void audio_sample(uint16_t l_sample, uint16_t r_sample);
|
||||
void input_poll();
|
||||
int16_t input_poll(unsigned deviceid, unsigned id);
|
||||
|
||||
void init();
|
||||
void term();
|
||||
};
|
||||
|
||||
extern SNESInterface snesinterface;
|
||||
56
tools/bsnes/snes/scheduler/scheduler.cpp
Executable file
56
tools/bsnes/snes/scheduler/scheduler.cpp
Executable file
@@ -0,0 +1,56 @@
|
||||
#ifdef SNES_CPP
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
void threadentry_cpu() { cpu.enter(); }
|
||||
void threadentry_smp() { smp.enter(); }
|
||||
void threadentry_ppu() { ppu.enter(); }
|
||||
void threadentry_dsp() { dsp.enter(); }
|
||||
|
||||
void Scheduler::enter() {
|
||||
switch(clock.active) {
|
||||
case THREAD_CPU: co_switch(thread_cpu); break;
|
||||
case THREAD_SMP: co_switch(thread_smp); break;
|
||||
case THREAD_PPU: co_switch(thread_ppu); break;
|
||||
case THREAD_DSP: co_switch(thread_dsp); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::exit() {
|
||||
co_switch(thread_snes);
|
||||
}
|
||||
|
||||
void Scheduler::init() {
|
||||
clock.cpu_freq = snes.region() == SNES::NTSC
|
||||
? snes.config.cpu.ntsc_clock_rate
|
||||
: snes.config.cpu.pal_clock_rate;
|
||||
clock.smp_freq = snes.region() == SNES::NTSC
|
||||
? snes.config.smp.ntsc_clock_rate
|
||||
: snes.config.smp.pal_clock_rate;
|
||||
|
||||
clock.active = THREAD_CPU;
|
||||
clock.cpuppu = 0;
|
||||
clock.cpusmp = 0;
|
||||
clock.smpdsp = 0;
|
||||
|
||||
if(thread_cpu) co_delete(thread_cpu);
|
||||
if(thread_smp) co_delete(thread_smp);
|
||||
if(thread_ppu) co_delete(thread_ppu);
|
||||
if(thread_dsp) co_delete(thread_dsp);
|
||||
|
||||
thread_snes = co_active();
|
||||
thread_cpu = co_create(65536 * sizeof(void*), threadentry_cpu);
|
||||
thread_smp = co_create(65536 * sizeof(void*), threadentry_smp);
|
||||
thread_ppu = co_create(65536 * sizeof(void*), threadentry_ppu);
|
||||
thread_dsp = co_create(65536 * sizeof(void*), threadentry_dsp);
|
||||
}
|
||||
|
||||
Scheduler::Scheduler() {
|
||||
thread_snes = 0;
|
||||
thread_cpu = 0;
|
||||
thread_smp = 0;
|
||||
thread_ppu = 0;
|
||||
thread_dsp = 0;
|
||||
}
|
||||
|
||||
#endif //ifdef SNES_CPP
|
||||
123
tools/bsnes/snes/scheduler/scheduler.hpp
Executable file
123
tools/bsnes/snes/scheduler/scheduler.hpp
Executable file
@@ -0,0 +1,123 @@
|
||||
class Scheduler {
|
||||
public:
|
||||
cothread_t thread_snes;
|
||||
cothread_t thread_cpu;
|
||||
cothread_t thread_smp;
|
||||
cothread_t thread_ppu;
|
||||
cothread_t thread_dsp;
|
||||
|
||||
enum ActiveThread {
|
||||
THREAD_CPU,
|
||||
THREAD_SMP,
|
||||
THREAD_PPU,
|
||||
THREAD_DSP,
|
||||
};
|
||||
|
||||
struct {
|
||||
unsigned cpu_freq;
|
||||
unsigned smp_freq;
|
||||
|
||||
ActiveThread active;
|
||||
int64 cpuppu;
|
||||
int64 cpusmp;
|
||||
int64 smpdsp;
|
||||
} clock;
|
||||
|
||||
//==========
|
||||
//CPU <> PPU
|
||||
//==========
|
||||
|
||||
alwaysinline void sync_cpuppu() {
|
||||
if(clock.cpuppu < 0) {
|
||||
clock.active = THREAD_PPU;
|
||||
co_switch(thread_ppu);
|
||||
}
|
||||
}
|
||||
|
||||
alwaysinline void sync_ppucpu() {
|
||||
if(clock.cpuppu >= 0) {
|
||||
clock.active = THREAD_CPU;
|
||||
co_switch(thread_cpu);
|
||||
}
|
||||
}
|
||||
|
||||
//==========
|
||||
//CPU <> SMP
|
||||
//==========
|
||||
|
||||
alwaysinline void sync_cpusmp() {
|
||||
if(clock.cpusmp < 0) {
|
||||
clock.active = THREAD_SMP;
|
||||
co_switch(thread_smp);
|
||||
}
|
||||
}
|
||||
|
||||
alwaysinline void sync_smpcpu() {
|
||||
if(clock.cpusmp >= 0) {
|
||||
clock.active = THREAD_CPU;
|
||||
co_switch(thread_cpu);
|
||||
}
|
||||
}
|
||||
|
||||
//==========
|
||||
//SMP <> DSP
|
||||
//==========
|
||||
|
||||
alwaysinline void sync_smpdsp() {
|
||||
if(clock.smpdsp < 0) {
|
||||
clock.active = THREAD_DSP;
|
||||
co_switch(thread_dsp);
|
||||
}
|
||||
}
|
||||
|
||||
alwaysinline void sync_dspsmp() {
|
||||
if(clock.smpdsp >= 0) {
|
||||
clock.active = THREAD_SMP;
|
||||
co_switch(thread_smp);
|
||||
}
|
||||
}
|
||||
|
||||
//======
|
||||
//timing
|
||||
//======
|
||||
|
||||
alwaysinline void addclocks_cpu(unsigned clocks) {
|
||||
clock.cpuppu -= clocks;
|
||||
sync_cpuppu();
|
||||
|
||||
clock.cpusmp -= clocks * (uint64)clock.smp_freq;
|
||||
if(clock.cpusmp < -(20000 * (int64)24000000)) sync_cpusmp();
|
||||
}
|
||||
|
||||
alwaysinline void addclocks_ppu(unsigned clocks) {
|
||||
clock.cpuppu += clocks;
|
||||
sync_ppucpu();
|
||||
}
|
||||
|
||||
alwaysinline void addclocks_smp(unsigned clocks) {
|
||||
clock.cpusmp += clocks * (uint64)clock.cpu_freq;
|
||||
if(clock.cpusmp > +(20000 * (int64)24000000)) sync_smpcpu();
|
||||
|
||||
clock.smpdsp -= clocks;
|
||||
#if !defined(USE_STATE_MACHINE)
|
||||
sync_smpdsp();
|
||||
#else
|
||||
while(clock.smpdsp < 0) dsp.enter();
|
||||
#endif
|
||||
}
|
||||
|
||||
alwaysinline void addclocks_dsp(unsigned clocks) {
|
||||
clock.smpdsp += clocks;
|
||||
#if !defined(USE_STATE_MACHINE)
|
||||
sync_dspsmp();
|
||||
#endif
|
||||
}
|
||||
|
||||
void enter();
|
||||
void exit();
|
||||
void init();
|
||||
|
||||
Scheduler();
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
210
tools/bsnes/snes/snes.cpp
Executable file
210
tools/bsnes/snes/snes.cpp
Executable file
@@ -0,0 +1,210 @@
|
||||
#include <../base.hpp>
|
||||
#include <../chip/chip.hpp>
|
||||
#include <../cart/cart.hpp>
|
||||
#define SNES_CPP
|
||||
|
||||
SNES snes;
|
||||
BUSCORE bus;
|
||||
CPUCORE cpu;
|
||||
SMPCORE smp;
|
||||
DSPCORE dsp;
|
||||
PPUCORE ppu;
|
||||
|
||||
BSXBase bsxbase;
|
||||
BSXCart bsxcart;
|
||||
BSXFlash bsxflash;
|
||||
SRTC srtc;
|
||||
SDD1 sdd1;
|
||||
SPC7110 spc7110;
|
||||
Cx4 cx4;
|
||||
DSP1 dsp1;
|
||||
DSP2 dsp2;
|
||||
DSP3 dsp3;
|
||||
DSP4 dsp4;
|
||||
OBC1 obc1;
|
||||
ST010 st010;
|
||||
|
||||
#include "scheduler/scheduler.cpp"
|
||||
#include "tracer/tracer.cpp"
|
||||
|
||||
#include "video/video.cpp"
|
||||
#include "audio/audio.cpp"
|
||||
#include "input/input.cpp"
|
||||
|
||||
void SNES::run() {
|
||||
}
|
||||
|
||||
void SNES::runtoframe() {
|
||||
scheduler.enter();
|
||||
}
|
||||
|
||||
void SNES::init() {
|
||||
bsxbase.init();
|
||||
bsxcart.init();
|
||||
bsxflash.init();
|
||||
srtc.init();
|
||||
sdd1.init();
|
||||
spc7110.init();
|
||||
cx4.init();
|
||||
dsp1.init();
|
||||
dsp2.init();
|
||||
dsp3.init();
|
||||
dsp4.init();
|
||||
obc1.init();
|
||||
st010.init();
|
||||
|
||||
video.init();
|
||||
audio.init();
|
||||
input.init();
|
||||
snesinterface.init();
|
||||
}
|
||||
|
||||
void SNES::term() {
|
||||
snesinterface.term();
|
||||
}
|
||||
|
||||
void SNES::power() {
|
||||
snes_region = max(0, min(2, snes.config.region));
|
||||
snes_expansion = max(0, min(1, snes.config.expansion_port));
|
||||
|
||||
if(snes_region == Autodetect) {
|
||||
snes_region = (cartridge.region() == Cartridge::NTSC ? NTSC : PAL);
|
||||
}
|
||||
|
||||
scheduler.init();
|
||||
|
||||
ppu.PPUcounter::reset();
|
||||
cpu.power();
|
||||
smp.power();
|
||||
dsp.power();
|
||||
ppu.power();
|
||||
bus.power();
|
||||
|
||||
if(expansion() == ExpansionBSX) bsxbase.power();
|
||||
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.power();
|
||||
if(cartridge.bsx_flash_loaded()) bsxflash.power();
|
||||
|
||||
if(cartridge.has_srtc()) srtc.power();
|
||||
if(cartridge.has_sdd1()) sdd1.power();
|
||||
if(cartridge.has_spc7110()) spc7110.power();
|
||||
if(cartridge.has_cx4()) cx4.power();
|
||||
if(cartridge.has_dsp1()) dsp1.power();
|
||||
if(cartridge.has_dsp2()) dsp2.power();
|
||||
if(cartridge.has_dsp3()) dsp3.power();
|
||||
if(cartridge.has_dsp4()) dsp4.power();
|
||||
if(cartridge.has_obc1()) obc1.power();
|
||||
if(cartridge.has_st010()) st010.power();
|
||||
|
||||
for(unsigned i = 0x2100; i <= 0x213f; i++) memory::mmio.map(i, ppu);
|
||||
for(unsigned i = 0x2140; i <= 0x217f; i++) memory::mmio.map(i, cpu);
|
||||
for(unsigned i = 0x2180; i <= 0x2183; i++) memory::mmio.map(i, cpu);
|
||||
for(unsigned i = 0x4016; i <= 0x4017; i++) memory::mmio.map(i, cpu);
|
||||
for(unsigned i = 0x4200; i <= 0x421f; i++) memory::mmio.map(i, cpu);
|
||||
for(unsigned i = 0x4300; i <= 0x437f; i++) memory::mmio.map(i, cpu);
|
||||
|
||||
if(expansion() == ExpansionBSX) bsxbase.enable();
|
||||
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.enable();
|
||||
if(cartridge.bsx_flash_loaded()) bsxflash.enable();
|
||||
|
||||
if(cartridge.has_srtc()) srtc.enable();
|
||||
if(cartridge.has_sdd1()) sdd1.enable();
|
||||
if(cartridge.has_spc7110()) spc7110.enable();
|
||||
if(cartridge.has_cx4()) cx4.enable();
|
||||
if(cartridge.has_dsp1()) dsp1.enable();
|
||||
if(cartridge.has_dsp2()) dsp2.enable();
|
||||
if(cartridge.has_dsp3()) dsp3.enable();
|
||||
if(cartridge.has_dsp4()) dsp4.enable();
|
||||
if(cartridge.has_obc1()) obc1.enable();
|
||||
if(cartridge.has_st010()) st010.enable();
|
||||
|
||||
input.port_set_device(0, snes.config.controller_port1);
|
||||
input.port_set_device(1, snes.config.controller_port2);
|
||||
input.update();
|
||||
video.update();
|
||||
}
|
||||
|
||||
void SNES::reset() {
|
||||
scheduler.init();
|
||||
|
||||
ppu.PPUcounter::reset();
|
||||
cpu.reset();
|
||||
smp.reset();
|
||||
dsp.reset();
|
||||
ppu.reset();
|
||||
bus.reset();
|
||||
|
||||
if(expansion() == ExpansionBSX) bsxbase.reset();
|
||||
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.reset();
|
||||
if(cartridge.bsx_flash_loaded()) bsxflash.reset();
|
||||
|
||||
if(cartridge.has_srtc()) srtc.reset();
|
||||
if(cartridge.has_sdd1()) sdd1.reset();
|
||||
if(cartridge.has_spc7110()) spc7110.reset();
|
||||
if(cartridge.has_cx4()) cx4.reset();
|
||||
if(cartridge.has_dsp1()) dsp1.reset();
|
||||
if(cartridge.has_dsp2()) dsp2.reset();
|
||||
if(cartridge.has_dsp3()) dsp3.reset();
|
||||
if(cartridge.has_dsp4()) dsp4.reset();
|
||||
if(cartridge.has_obc1()) obc1.reset();
|
||||
if(cartridge.has_st010()) st010.reset();
|
||||
|
||||
input.port_set_device(0, snes.config.controller_port1);
|
||||
input.port_set_device(1, snes.config.controller_port2);
|
||||
input.update();
|
||||
video.update();
|
||||
}
|
||||
|
||||
void SNES::scanline() {
|
||||
video.scanline();
|
||||
|
||||
if(ppu.vcounter() == 241) {
|
||||
input.update();
|
||||
video.update();
|
||||
scheduler.exit();
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::frame() {
|
||||
}
|
||||
|
||||
SNES::Region SNES::region() const {
|
||||
return (SNES::Region)snes_region;
|
||||
}
|
||||
|
||||
SNES::ExpansionPortDevice SNES::expansion() const {
|
||||
return (SNES::ExpansionPortDevice)snes_expansion;
|
||||
}
|
||||
|
||||
SNES::SNES() : snes_region(NTSC), snes_expansion(ExpansionNone) {
|
||||
config.controller_port1 = Input::DeviceJoypad;
|
||||
config.controller_port2 = Input::DeviceJoypad;
|
||||
config.expansion_port = ExpansionBSX;
|
||||
config.region = Autodetect;
|
||||
|
||||
config.file.autodetect_type = false;
|
||||
config.file.bypass_patch_crc32 = false;
|
||||
|
||||
config.path.base = "";
|
||||
config.path.user = "";
|
||||
config.path.current = "";
|
||||
config.path.rom = "";
|
||||
config.path.save = "";
|
||||
config.path.patch = "";
|
||||
config.path.cheat = "";
|
||||
config.path.data = "";
|
||||
config.path.bsx = "";
|
||||
config.path.st = "";
|
||||
|
||||
config.cpu.version = 2;
|
||||
config.cpu.ntsc_clock_rate = 21477272;
|
||||
config.cpu.pal_clock_rate = 21281370;
|
||||
config.cpu.alu_mul_delay = 2;
|
||||
config.cpu.alu_div_delay = 2;
|
||||
config.cpu.wram_init_value = 0x55;
|
||||
|
||||
config.smp.ntsc_clock_rate = 32041 * 768;
|
||||
config.smp.pal_clock_rate = 32041 * 768;
|
||||
|
||||
config.ppu1.version = 1;
|
||||
config.ppu2.version = 3;
|
||||
}
|
||||
84
tools/bsnes/snes/snes.hpp
Executable file
84
tools/bsnes/snes/snes.hpp
Executable file
@@ -0,0 +1,84 @@
|
||||
#include "interface/interface.hpp"
|
||||
#include "scheduler/scheduler.hpp"
|
||||
#include "tracer/tracer.hpp"
|
||||
|
||||
class VideoFilter;
|
||||
|
||||
class SNES {
|
||||
public:
|
||||
enum Region { NTSC = 0, PAL = 1 };
|
||||
enum RegionAutodetect { Autodetect = 2 };
|
||||
enum ExpansionPortDevice { ExpansionNone = 0, ExpansionBSX = 1 };
|
||||
|
||||
struct Config {
|
||||
unsigned controller_port1;
|
||||
unsigned controller_port2;
|
||||
unsigned expansion_port;
|
||||
unsigned region;
|
||||
|
||||
struct File {
|
||||
bool autodetect_type;
|
||||
bool bypass_patch_crc32;
|
||||
} file;
|
||||
|
||||
struct Path {
|
||||
string base; //binary path
|
||||
string user; //user profile path (bsnes.cfg, ...)
|
||||
string current; //current working directory (path to currently loaded cartridge)
|
||||
string rom, save, patch, cheat, data;
|
||||
string bsx, st;
|
||||
} path;
|
||||
|
||||
struct CPU {
|
||||
unsigned version;
|
||||
unsigned ntsc_clock_rate;
|
||||
unsigned pal_clock_rate;
|
||||
unsigned alu_mul_delay;
|
||||
unsigned alu_div_delay;
|
||||
unsigned wram_init_value;
|
||||
} cpu;
|
||||
|
||||
struct SMP {
|
||||
unsigned ntsc_clock_rate;
|
||||
unsigned pal_clock_rate;
|
||||
} smp;
|
||||
|
||||
struct PPU1 {
|
||||
unsigned version;
|
||||
} ppu1;
|
||||
|
||||
struct PPU2 {
|
||||
unsigned version;
|
||||
} ppu2;
|
||||
} config;
|
||||
|
||||
//system functions
|
||||
virtual void run();
|
||||
virtual void runtoframe();
|
||||
|
||||
virtual void init();
|
||||
virtual void term();
|
||||
virtual void power();
|
||||
virtual void reset();
|
||||
|
||||
virtual void frame();
|
||||
virtual void scanline();
|
||||
|
||||
//return *active* region / expansion port device information
|
||||
//settings cached upon power-on
|
||||
Region region() const;
|
||||
ExpansionPortDevice expansion() const;
|
||||
|
||||
#include "video/video.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "input/input.hpp"
|
||||
|
||||
SNES();
|
||||
virtual ~SNES() {}
|
||||
|
||||
private:
|
||||
unsigned snes_region;
|
||||
unsigned snes_expansion;
|
||||
};
|
||||
|
||||
extern SNES snes;
|
||||
94
tools/bsnes/snes/tracer/tracer.cpp
Executable file
94
tools/bsnes/snes/tracer/tracer.cpp
Executable file
@@ -0,0 +1,94 @@
|
||||
#ifdef SNES_CPP
|
||||
|
||||
Tracer tracer;
|
||||
|
||||
void tprintf(const char *s, ...) {
|
||||
if(tracer.enabled() == false) return;
|
||||
|
||||
char str[4096];
|
||||
va_list args;
|
||||
va_start(args, s);
|
||||
vsprintf(str, s, args);
|
||||
va_end(args);
|
||||
fprintf(tracer.fp, "%s\r\n", str);
|
||||
}
|
||||
|
||||
void Tracer::trace_cpuop() {
|
||||
if(enabled() == false) return;
|
||||
if(cpuop_enabled() == false) return;
|
||||
if(cpu.in_opcode() == true) return;
|
||||
|
||||
if(cpuopmask_enabled() == true) {
|
||||
unsigned addr = cpu.regs.pc.d;
|
||||
if(settings.cpuopmasktbl[addr >> 3] & 0x80 >> (addr & 7)) return;
|
||||
settings.cpuopmasktbl[addr >> 3] |= 0x80 >> (addr & 7);
|
||||
}
|
||||
|
||||
char t[1024];
|
||||
cpu.disassemble_opcode(t);
|
||||
fprintf(fp, "%s\r\n", t);
|
||||
}
|
||||
|
||||
void Tracer::trace_smpop() {
|
||||
if(enabled() == false) return;
|
||||
if(smpop_enabled() == false) return;
|
||||
if(smp.in_opcode() == true) return;
|
||||
|
||||
if(smpopmask_enabled() == true) {
|
||||
unsigned addr = smp.regs.pc;
|
||||
if(settings.smpopmasktbl[addr >> 3] & 0x80 >> (addr & 7)) return;
|
||||
settings.smpopmasktbl[addr >> 3] |= 0x80 >> (addr & 7);
|
||||
}
|
||||
|
||||
char t[1024];
|
||||
smp.disassemble_opcode(t);
|
||||
fprintf(fp, "%s\r\n", t);
|
||||
}
|
||||
|
||||
void Tracer::enable(bool en) {
|
||||
if(en == true && enabled() == false) {
|
||||
fp = fopen(Cartridge::filepath("trace.log", snes.config.path.data), "wb");
|
||||
} else if(en == false && enabled() == true) {
|
||||
fclose(fp);
|
||||
fp = 0;
|
||||
}
|
||||
|
||||
settings.enabled = en;
|
||||
}
|
||||
|
||||
void Tracer::cpuopmask_enable(bool en) {
|
||||
if(en == true && cpuopmask_enabled() == false) {
|
||||
settings.cpuopmasktbl = new(zeromemory) uint8_t[0x200000];
|
||||
} else if(en == false && cpuopmask_enabled() == true) {
|
||||
delete[] settings.cpuopmasktbl;
|
||||
}
|
||||
|
||||
settings.cpuopmask = en;
|
||||
}
|
||||
|
||||
void Tracer::smpopmask_enable(bool en) {
|
||||
if(en == true && smpopmask_enabled() == false) {
|
||||
settings.smpopmasktbl = new(zeromemory) uint8_t[0x2000];
|
||||
} else if(en == false && smpopmask_enabled() == true) {
|
||||
delete[] settings.smpopmasktbl;
|
||||
}
|
||||
|
||||
settings.smpopmask = en;
|
||||
}
|
||||
|
||||
Tracer::Tracer() {
|
||||
fp = 0;
|
||||
|
||||
settings.cpuop = false;
|
||||
settings.cpuopmask = false;
|
||||
settings.cpuopmasktbl = 0;
|
||||
|
||||
settings.smpop = false;
|
||||
settings.smpopmask = false;
|
||||
settings.smpopmasktbl = 0;
|
||||
}
|
||||
|
||||
Tracer::~Tracer() {
|
||||
}
|
||||
|
||||
#endif //ifdef SNES_CPP
|
||||
45
tools/bsnes/snes/tracer/tracer.hpp
Executable file
45
tools/bsnes/snes/tracer/tracer.hpp
Executable file
@@ -0,0 +1,45 @@
|
||||
void tprintf(const char *s, ...);
|
||||
|
||||
class Tracer {
|
||||
private:
|
||||
|
||||
FILE *fp;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
|
||||
bool cpuop;
|
||||
bool cpuopmask;
|
||||
uint8 *cpuopmasktbl;
|
||||
|
||||
bool smpop;
|
||||
bool smpopmask;
|
||||
uint8 *smpopmasktbl;
|
||||
} settings;
|
||||
|
||||
public:
|
||||
void enable(bool en);
|
||||
bool enabled() { return settings.enabled; }
|
||||
|
||||
void cpuop_enable(bool en) { settings.cpuop = en; }
|
||||
bool cpuop_enabled() { return settings.cpuop; }
|
||||
|
||||
void cpuopmask_enable(bool en);
|
||||
bool cpuopmask_enabled() { return settings.cpuopmask; }
|
||||
|
||||
void smpop_enable(bool en) { settings.smpop = en; }
|
||||
bool smpop_enabled() { return settings.smpop; }
|
||||
|
||||
void smpopmask_enable(bool en);
|
||||
bool smpopmask_enabled() { return settings.smpopmask; }
|
||||
|
||||
void trace_cpuop();
|
||||
void trace_smpop();
|
||||
|
||||
Tracer();
|
||||
~Tracer();
|
||||
|
||||
friend void tprintf(const char *s, ...);
|
||||
};
|
||||
|
||||
extern Tracer tracer;
|
||||
103
tools/bsnes/snes/video/video.cpp
Executable file
103
tools/bsnes/snes/video/video.cpp
Executable file
@@ -0,0 +1,103 @@
|
||||
#ifdef SNES_CPP
|
||||
|
||||
const uint8_t SNES::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,
|
||||
};
|
||||
|
||||
void SNES::Video::draw_cursor(uint16_t color, int x, int y) {
|
||||
for(int cy = 0; cy < 15; cy++) {
|
||||
int vy = y + cy - 7;
|
||||
if(vy <= 0 || vy >= 240) continue; //do not draw offscreen
|
||||
|
||||
bool hires = (pline_width[vy] == 512);
|
||||
for(int cx = 0; cx < 15; cx++) {
|
||||
int vx = x + cx - 7;
|
||||
if(vx < 0 || vx >= 256) continue; //do not draw offscreen
|
||||
uint8_t pixel = cursor[cy * 15 + cx];
|
||||
if(pixel == 0) continue;
|
||||
uint16_t pixelcolor = (pixel == 1) ? 0 : color;
|
||||
|
||||
if(hires == false) {
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx) = pixelcolor;
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx) = pixelcolor;
|
||||
} else {
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 0) = pixelcolor;
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 0) = pixelcolor;
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 1) = pixelcolor;
|
||||
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 1) = pixelcolor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::Video::update() {
|
||||
uint16_t *data = (uint16_t*)ppu.output;
|
||||
unsigned width, height;
|
||||
|
||||
switch(snes.input.port[1].device) {
|
||||
case SNES::Input::DeviceSuperScope: draw_cursor(0x001f, snes.input.port[1].superscope.x, snes.input.port[1].superscope.y); break;
|
||||
case SNES::Input::DeviceJustifiers: draw_cursor(0x02e0, snes.input.port[1].justifier.x2, snes.input.port[1].justifier.y2); //fallthrough
|
||||
case SNES::Input::DeviceJustifier: draw_cursor(0x001f, snes.input.port[1].justifier.x1, snes.input.port[1].justifier.y1); break;
|
||||
}
|
||||
|
||||
unsigned yoffset = 1; //scanline 0 is always black, skip this line for video output
|
||||
if(mode == ModeNTSC && ppu.overscan()) yoffset += 8; //NTSC overscan centers x240 height image
|
||||
|
||||
switch(mode) { default:
|
||||
case ModeNTSC: { width = 256; height = 224; } break;
|
||||
case ModePAL: { width = 256; height = 239; } break;
|
||||
}
|
||||
|
||||
if(frame_hires) width <<= 1;
|
||||
if(frame_interlace) height <<= 1;
|
||||
|
||||
snesinterface.video_refresh(
|
||||
data + yoffset * 1024,
|
||||
/* pitch = */ height <= 240 ? 2048 : 1024,
|
||||
/* line[] = */ height <= 240 ? (pline_width + yoffset) : (iline_width + yoffset * 2),
|
||||
width, height
|
||||
);
|
||||
|
||||
frame_hires = false;
|
||||
frame_interlace = false;
|
||||
}
|
||||
|
||||
void SNES::Video::scanline() {
|
||||
unsigned y = ppu.vcounter();
|
||||
if(y >= 240) return;
|
||||
|
||||
unsigned width = (ppu.hires() == false ? 256 : 512);
|
||||
pline_width[y] = width;
|
||||
iline_width[y * 2 + (int)ppu.field()] = width;
|
||||
|
||||
frame_hires |= ppu.hires();
|
||||
frame_interlace |= ppu.interlace();
|
||||
}
|
||||
|
||||
void SNES::Video::set_mode(Mode mode_) {
|
||||
mode = mode_;
|
||||
}
|
||||
|
||||
void SNES::Video::init() {
|
||||
for(unsigned i = 0; i < 240; i++) pline_width[i] = 256;
|
||||
for(unsigned i = 0; i < 480; i++) iline_width[i] = 256;
|
||||
frame_hires = false;
|
||||
frame_interlace = false;
|
||||
set_mode(ModeNTSC);
|
||||
}
|
||||
|
||||
#endif //ifdef SNES_CPP
|
||||
25
tools/bsnes/snes/video/video.hpp
Executable file
25
tools/bsnes/snes/video/video.hpp
Executable file
@@ -0,0 +1,25 @@
|
||||
class Video {
|
||||
public:
|
||||
enum Mode {
|
||||
ModeNTSC,
|
||||
ModePAL,
|
||||
};
|
||||
void set_mode(Mode);
|
||||
|
||||
private:
|
||||
Mode mode;
|
||||
bool frame_hires;
|
||||
bool frame_interlace;
|
||||
|
||||
unsigned pline_width[240]; //progressive
|
||||
unsigned iline_width[480]; //interlace
|
||||
|
||||
void update();
|
||||
void scanline();
|
||||
void init();
|
||||
|
||||
static const uint8_t cursor[15 * 15];
|
||||
void draw_cursor(uint16_t color, int x, int y);
|
||||
|
||||
friend class SNES;
|
||||
} video;
|
||||
Reference in New Issue
Block a user