Update to v096r08 release.

byuu says:

Changelog:
- FC: scanline emulation support added
- SFC: balanced profile compiles again
- SFC: performance profile compiles again
- GB,GBC: more fixes to pass blargg's 07, 08, 11 APU tests
- tomoko: added input loss { pause, allow-input } options
- tomoko: refactored settings video menu options to { Video Scale, Video
  Emulation, Video Shader }
- icarus: connected { About, Preferences, Quit } application menu options
This commit is contained in:
Tim Allen 2016-01-15 21:28:51 +11:00
parent cec33c1d0f
commit 12df278c5b
40 changed files with 197 additions and 220 deletions

View File

@ -6,7 +6,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "096.07";
static const string Version = "096.08";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -169,16 +169,19 @@ auto Interface::cheatSet(const lstring& list) -> void {
auto Interface::cap(const string& name) -> bool {
if(name == "Color Emulation") return true;
if(name == "Scanline Emulation") return true;
return false;
}
auto Interface::get(const string& name) -> any {
if(name == "Color Emulation") return settings.colorEmulation;
if(name == "Scanline Emulation") return settings.scanlineEmulation;
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
return false;
}

View File

@ -58,6 +58,7 @@ private:
struct Settings {
bool colorEmulation = true;
bool scanlineEmulation = true;
};
extern Interface* interface;

View File

@ -7,7 +7,7 @@ namespace Famicom {
Video video;
Video::Video() {
output = new uint32[256 * 240];
output = new uint32[256 * 480];
paletteStandard = new uint32[1 << 9];
paletteEmulation = new uint32[1 << 9];
}
@ -19,7 +19,7 @@ Video::~Video() {
}
auto Video::reset() -> void {
memory::fill(output, 256 * 240);
memory::fill(output, 256 * 480);
for(auto color : range(1 << 9)) {
paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
@ -30,15 +30,28 @@ auto Video::reset() -> void {
auto Video::refresh() -> void {
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
for(uint y = 0; y < 240; y++) {
auto source = ppu.buffer + y * 256;
auto target = output + y * 256;
for(uint x = 0; x < 256; x++) {
*target++ = palette[*source++];
if(settings.scanlineEmulation) {
for(uint y = 0; y < 240; y++) {
auto source = ppu.buffer + y * 256;
auto targetLo = output + y * 512;
auto targetHi = output + y * 512 + 256;
for(uint x = 0; x < 256; x++) {
auto color = palette[*source++];
*targetLo++ = color;
*targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1);
}
}
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 480);
} else {
for(uint y = 0; y < 240; y++) {
auto source = ppu.buffer + y * 256;
auto target = output + y * 256;
for(uint x = 0; x < 256; x++) {
*target++ = palette[*source++];
}
}
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 240);
}
interface->videoRefresh(output, 4 * 256, 256, 240);
}
auto Video::generateColor(

View File

@ -79,7 +79,6 @@ auto APU::power() -> void {
}
auto APU::mmio_read(uint16 addr) -> uint8 {
//if(!master.enable && addr != 0xff26) return 0xff;
if(addr >= 0xff10 && addr <= 0xff14) return square1.read(addr);
if(addr >= 0xff15 && addr <= 0xff19) return square2.read(addr);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.read(addr);
@ -90,7 +89,18 @@ auto APU::mmio_read(uint16 addr) -> uint8 {
}
auto APU::mmio_write(uint16 addr, uint8 data) -> void {
if(!master.enable && addr != 0xff26) return;
if(!master.enable) {
bool valid = addr == 0xff26; //NR52
if(!system.cgb()) {
//NRx1 length is writable only on DMG/SGB; not on CGB
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)
if(addr == 0xff1b) valid = true; //NR31
if(addr == 0xff20) valid = true; //NR41
}
if(!valid) return;
}
if(addr >= 0xff10 && addr <= 0xff14) return square1.write(addr, data);
if(addr >= 0xff15 && addr <= 0xff19) return square2.write(addr, data);
if(addr >= 0xff1a && addr <= 0xff1e) return wave.write(addr, data);

View File

@ -68,10 +68,10 @@ auto APU::Master::read(uint16 addr) -> uint8 {
auto APU::Master::write(uint16 addr, uint8 data) -> void {
if(addr == 0xff24) { //NR50
leftEnable = data & 0x80;
leftVolume = (data >> 4) & 7;
rightEnable = data & 0x08;
rightVolume = (data >> 0) & 7;
leftEnable = (uint1)(data >> 7);
leftVolume = (uint3)(data >> 4);
rightEnable = (uint1)(data >> 3);
rightVolume = (uint3)(data >> 0);
}
if(addr == 0xff25) { //NR51
@ -86,14 +86,19 @@ auto APU::Master::write(uint16 addr, uint8 data) -> void {
}
if(addr == 0xff26) { //NR52
enable = data & 0x80;
if(!enable) {
//power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(system.cgb());
apu.square2.power(system.cgb());
apu.wave.power(system.cgb());
apu.noise.power(system.cgb());
power();
if(enable != (bool)(data & 0x80)) {
enable = data & 0x80;
if(!enable) {
//power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(system.cgb());
apu.square2.power(system.cgb());
apu.wave.power(system.cgb());
apu.noise.power(system.cgb());
power();
} else {
apu.phase = 0;
}
}
}
}

View File

@ -16,7 +16,7 @@ auto APU::Noise::run() -> void {
}
}
uint4 sample = (lfsr & 1) ? (uint4)0 : volume;
uint4 sample = lfsr & 1 ? 0 : (uint)volume;
if(!enable) sample = 0;
output = sample;
@ -30,7 +30,7 @@ auto APU::Noise::clockLength() -> void {
auto APU::Noise::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
@ -90,7 +90,7 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
if(initialize) {
enable = dacEnable();
lfsr = -1;
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {

View File

@ -14,7 +14,7 @@ auto APU::Square1::run() -> void {
}
}
uint4 sample = (dutyOutput ? volume : (uint4)0);
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
@ -54,7 +54,7 @@ auto APU::Square1::clockSweep() -> void {
auto APU::Square1::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
@ -120,7 +120,7 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
if(initialize) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {

View File

@ -14,7 +14,7 @@ auto APU::Square2::run() -> void {
}
}
uint4 sample = (dutyOutput ? volume : (uint4)0);
uint4 sample = dutyOutput ? (uint)volume : 0;
if(!enable) sample = 0;
output = sample;
@ -28,7 +28,7 @@ auto APU::Square2::clockLength() -> void {
auto APU::Square2::clockEnvelope() -> void {
if(enable && envelopeFrequency && --envelopePeriod == 0) {
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
if(envelopeDirection == 0 && volume > 0) volume--;
if(envelopeDirection == 1 && volume < 15) volume++;
}
@ -87,7 +87,7 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
if(initialize) {
enable = dacEnable();
period = 2 * (2048 - frequency);
envelopePeriod = envelopeFrequency;
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
volume = envelopeVolume;
if(!length) {

View File

@ -10,7 +10,7 @@ auto APU::Wave::run() -> void {
static const uint shift[] = {4, 0, 1, 2}; //0%, 100%, 50%, 25%
uint4 sample = patternSample >> shift[volume];
if(enable == false) sample = 0;
if(!enable) sample = 0;
output = sample;
}

View File

@ -60,7 +60,6 @@ auto CPU::scanline() -> void {
synchronizeSMP();
synchronizePPU();
synchronizeCoprocessors();
system.scanline();
if(vcounter() == 0) hdma_init();

View File

@ -10,8 +10,8 @@ PPU ppu;
#include "serialization.cpp"
PPU::PPU() {
surface = new uint32[512 * 512];
output = surface + 16 * 512;
output = new uint32[512 * 512]();
output += 16 * 512; //overscan offset
alloc_tiledata_cache();
@ -38,7 +38,8 @@ PPU::PPU() {
}
PPU::~PPU() {
delete[] surface;
output -= 16 * 512;
delete[] output;
free_tiledata_cache();
}
@ -128,6 +129,10 @@ auto PPU::scanline() -> void {
if(!regs.mosaic_countdown) regs.mosaic_countdown = regs.mosaic_size + 1;
regs.mosaic_countdown--;
}
if(line == 241) {
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
}
auto PPU::render_scanline() -> void {
@ -139,8 +144,6 @@ auto PPU::render_scanline() -> void {
}
auto PPU::frame() -> void {
system.frame();
if(field() == 0) {
display.interlace = regs.interlace;
regs.scanlines = (regs.overscan == false) ? 224 : 239;
@ -375,7 +378,7 @@ auto PPU::power() -> void {
auto PPU::reset() -> void {
create(Enter, system.cpuFrequency());
PPUcounter::reset();
memset(surface, 0, 512 * 512 * sizeof(uint32));
memory::fill(output, 512 * 480 * sizeof(uint32));
frame();

View File

@ -16,7 +16,6 @@ struct PPU : Thread, public PPUcounter {
alwaysinline auto interlace() const -> bool { return display.interlace; }
alwaysinline auto overscan() const -> bool { return display.overscan; }
alwaysinline auto hires() const -> bool { return (regs.pseudo_hires || regs.bg_mode == 5 || regs.bg_mode == 6); }
auto render_line() -> void;
auto update_oam_status() -> void;
@ -42,7 +41,6 @@ struct PPU : Thread, public PPUcounter {
uint8 oam[544];
uint8 cgram[512];
uint32* surface;
uint32* output;
uint ppu1_version = 1;

View File

@ -85,32 +85,23 @@ inline auto PPU::get_pixel_swap(uint32 x) -> uint16 {
}
inline auto PPU::render_line_output() -> void {
uint32* ptr = (uint32*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0);
uint32 curr, prev;
auto ptr = (uint32*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0);
if(!regs.pseudo_hires && regs.bg_mode != 5 && regs.bg_mode != 6) {
for(unsigned x = 0; x < 256; x++) {
curr = (regs.display_brightness << 15) | get_pixel_normal(x);
*ptr++ = curr;
for(uint x = 0; x < 256; x++) {
uint color = (regs.display_brightness << 15) | get_pixel_normal(x);
*ptr++ = color;
*ptr++ = color;
}
} else {
for(unsigned x = 0, prev = 0; x < 256; x++) {
//blending is disabled below, as this should be done via video filtering
//blending code is left for reference purposes
curr = (regs.display_brightness << 15) | get_pixel_swap(x);
*ptr++ = curr; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
//prev = curr;
curr = (regs.display_brightness << 15) | get_pixel_normal(x);
*ptr++ = curr; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
//prev = curr;
for(uint x = 0; x < 256; x++) {
*ptr++ = (regs.display_brightness << 15) | get_pixel_swap(x);
*ptr++ = (regs.display_brightness << 15) | get_pixel_normal(x);
}
}
}
inline auto PPU::render_line_clear() -> void {
uint32* ptr = (uint32*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0);
uint width = (!regs.pseudo_hires && regs.bg_mode != 5 && regs.bg_mode != 6) ? 256 : 512;
memset(ptr, 0, width * 2 * sizeof(uint32));
auto ptr = (uint32*)output + (line * 1024) + ((interlace() && field()) ? 512 : 0);
memory::fill(ptr, 512 * sizeof(uint32));
}

View File

@ -6,7 +6,6 @@ auto PPU::latch_counters() -> void {
auto PPU::interlace() const -> bool { return display.interlace; }
auto PPU::overscan() const -> bool { return display.overscan; }
auto PPU::hires() const -> bool { return regs.pseudo_hires || regs.bgmode == 5 || regs.bgmode == 6; }
auto PPU::get_vram_addr() -> uint16 {
uint16 addr = regs.vram_addr;

View File

@ -20,8 +20,8 @@ bg3(*this, Background::ID::BG3),
bg4(*this, Background::ID::BG4),
sprite(*this),
screen(*this) {
surface = new uint32[512 * 512];
output = surface + 16 * 512;
output = new uint32[512 * 512]();
output += 16 * 512; //overscan offset
display.width = 256;
display.height = 224;
display.frameskip = 0;
@ -29,7 +29,8 @@ screen(*this) {
}
PPU::~PPU() {
delete[] surface;
output -= 16 * 512;
delete[] output;
}
auto PPU::step(uint clocks) -> void {
@ -86,15 +87,18 @@ auto PPU::render_scanline() -> void {
}
auto PPU::scanline() -> void {
display.width = !hires() ? 256 : 512;
display.width = 512;
display.height = !overscan() ? 225 : 240;
if(vcounter() == 0) frame();
if(vcounter() == display.height && regs.display_disable == false) sprite.address_reset();
if(vcounter() == 241) {
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
}
auto PPU::frame() -> void {
sprite.frame();
system.frame();
display.interlace = regs.interlace;
display.overscan = regs.overscan;
display.framecounter = display.frameskip == 0 ? 0 : (display.framecounter + 1) % display.frameskip;
@ -118,7 +122,7 @@ auto PPU::power() -> void {
auto PPU::reset() -> void {
create(Enter, system.cpuFrequency());
PPUcounter::reset();
memset(surface, 0, 512 * 512 * sizeof(uint32));
memset(output, 0, 512 * 480 * sizeof(uint32));
mmio_reset();
display.interlace = false;
display.overscan = false;

View File

@ -10,7 +10,6 @@ struct PPU : Thread, public PPUcounter {
auto latch_counters() -> void;
auto interlace() const -> bool;
auto overscan() const -> bool;
auto hires() const -> bool;
auto enter() -> void;
auto enable() -> void;
@ -29,7 +28,6 @@ struct PPU : Thread, public PPUcounter {
uint8 cgram[512];
private:
uint32* surface;
uint32* output;
#include "mmio/mmio.hpp"

View File

@ -56,9 +56,9 @@ auto PPU::Screen::scanline() -> void {
}
auto PPU::Screen::render_black() -> void {
uint32* data = self.output + self.vcounter() * 1024;
auto data = self.output + self.vcounter() * 1024;
if(self.interlace() && self.field()) data += 512;
memset(data, 0, self.display.width << 2);
memory::fill(data, 512 * sizeof(uint32));
}
auto PPU::Screen::get_pixel_main(uint x) -> uint16 {
@ -116,12 +116,14 @@ auto PPU::Screen::get_pixel_sub(uint x) -> uint16 {
}
auto PPU::Screen::render() -> void {
uint32* data = self.output + self.vcounter() * 1024;
auto data = self.output + self.vcounter() * 1024;
if(self.interlace() && self.field()) data += 512;
if(!self.regs.pseudo_hires && self.regs.bgmode != 5 && self.regs.bgmode != 6) {
for(uint i = 0; i < 256; i++) {
data[i] = self.regs.display_brightness << 15 | get_pixel_main(i);
uint32 color = self.regs.display_brightness << 15 | get_pixel_main(i);
*data++ = color;
*data++ = color;
}
} else {
for(uint i = 0; i < 256; i++) {

View File

@ -42,7 +42,6 @@ auto CPU::scanline() -> void {
synchronizeSMP();
synchronizePPU();
synchronizeCoprocessors();
system.scanline();
if(vcounter() == 0) {
//HDMA init triggers once every frame

View File

@ -6,10 +6,6 @@ auto PPU::overscan() const -> bool {
return display.overscan;
}
auto PPU::hires() const -> bool {
return true;
}
auto PPU::latch_counters() -> void {
cpu.synchronizePPU();
regs.hcounter = hdot();

View File

@ -139,10 +139,13 @@ auto PPU::scanline() -> void {
sprite.scanline();
window.scanline();
screen.scanline();
if(vcounter() == 241) {
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
}
auto PPU::frame() -> void {
system.frame();
sprite.frame();
display.interlace = regs.interlace;

View File

@ -10,7 +10,6 @@ struct PPU : Thread, public PPUcounter {
auto latch_counters() -> void;
auto interlace() const -> bool;
auto overscan() const -> bool;
auto hires() const -> bool;
auto enter() -> void;
auto enable() -> void;

View File

@ -37,9 +37,6 @@ auto Audio::coprocessor_sample(int16 lsample, int16 rsample) -> void {
}
}
auto Audio::init() -> void {
}
auto Audio::flush() -> void {
while(dsp_length > 0 && cop_length > 0) {
uint32 dsp_sample = dsp_buffer[dsp_rdoffset];

View File

@ -3,7 +3,6 @@ struct Audio {
auto coprocessor_frequency(double frequency) -> void;
auto sample(int16 lsample, int16 rsample) -> void;
auto coprocessor_sample(int16 lsample, int16 rsample) -> void;
auto init() -> void;
private:
auto flush() -> void;

View File

@ -89,9 +89,6 @@ auto System::init() -> void {
bsmemory.init();
video.init();
audio.init();
device.connect(0, configuration.controllerPort1);
device.connect(1, configuration.controllerPort2);
}
@ -249,12 +246,4 @@ auto System::reset() -> void {
device.connect(1, configuration.controllerPort2);
}
auto System::scanline() -> void {
video.scanline();
if(cpu.vcounter() == 241) scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
auto System::frame() -> void {
}
}

View File

@ -19,9 +19,6 @@ struct System : property<System> {
auto power() -> void;
auto reset() -> void;
auto frame() -> void;
auto scanline() -> void;
//return *active* system information (settings are cached upon power-on)
readonly<Region> region;
readonly<Device::ID> expansionPort;

View File

@ -32,9 +32,9 @@ auto Video::reset() -> void {
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
}
{ uint R = L * gamma_ramp[r];
uint G = L * gamma_ramp[g];
uint B = L * gamma_ramp[b];
{ uint R = L * gammaRamp[r];
uint G = L * gammaRamp[g];
uint B = L * gammaRamp[b];
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
}
}
@ -110,36 +110,7 @@ auto Video::refresh() -> void {
drawCursors();
#if defined(PROFILE_ACCURACY)
interface->videoRefresh(output - (ppu.overscan() ? 0 : 7 * 1024), 512 * sizeof(uint32), 512, 480);
#endif
#if defined(PROFILE_BALANCED) || defined(PROFILE_PERFORMANCE)
if(hires) {
//normalize line widths
auto data = (uint32*)output;
if(ppu.interlace() && ppu.field()) data += 512;
for(uint y = 0; y < 240; y++) {
if(line_width[y] == 512) continue;
uint32* buffer = data + y * 1024;
for(int x = 255; x >= 0; x--) {
buffer[(x * 2) + 0] = buffer[(x * 2) + 1] = buffer[x];
}
}
}
//overscan: when disabled, shift image down (by scrolling video buffer up) to center image onscreen
//(memory before ppu.output is filled with black scanlines)
interface->videoRefresh(
output - (ppu.overscan() ? 0 : 7 * 1024),
4 * (1024 >> ppu.interlace()),
256 << hires,
240 << ppu.interlace()
);
hires = false;
#endif
}
//internal
@ -152,7 +123,6 @@ auto Video::drawCursor(uint32 color, int x, int y) -> void {
int vy = y + cy - 7;
if(vy <= 0 || vy >= 240) continue; //do not draw offscreen
bool hires = (line_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
@ -160,14 +130,10 @@ auto Video::drawCursor(uint32 color, int x, int y) -> void {
if(pixel == 0) continue;
uint32 pixelcolor = pixel == 1 ? 0xff000000 : color;
if(!hires) {
*(data + vy * 1024 + vx) = pixelcolor;
} else {
*(data + vy * 1024 + vx * 2 + 0) = pixelcolor;
*(data + vy * 1024 + vx * 2 + 1) = pixelcolor;
*(data + vy * 1024 + 512 + vx * 2 + 0) = pixelcolor;
*(data + vy * 1024 + 512 + vx * 2 + 1) = pixelcolor;
}
*(data + vy * 1024 + vx * 2 + 0) = pixelcolor;
*(data + vy * 1024 + vx * 2 + 1) = pixelcolor;
*(data + vy * 1024 + 512 + vx * 2 + 0) = pixelcolor;
*(data + vy * 1024 + 512 + vx * 2 + 1) = pixelcolor;
}
}
}
@ -192,21 +158,7 @@ auto Video::drawCursors() -> void {
}
}
auto Video::scanline() -> void {
uint y = cpu.vcounter();
if(y >= 240) return;
hires |= ppu.hires();
uint width = ppu.hires() ? 512 : 256;
line_width[y] = width;
}
auto Video::init() -> void {
hires = false;
for(auto& n : line_width) n = 256;
}
const uint8 Video::gamma_ramp[32] = {
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,

View File

@ -10,17 +10,10 @@ struct Video {
uint32* paletteEmulation = nullptr;
private:
bool hires;
uint line_width[240];
auto update() -> void;
auto scanline() -> void;
auto init() -> void;
auto drawCursor(uint32 color, int x, int y) -> void;
auto drawCursors() -> void;
static const uint8 gamma_ramp[32];
static const uint8 gammaRamp[32];
static const uint8 cursor[15 * 15];
friend class System;

View File

@ -41,6 +41,8 @@ Settings::Settings() {
set("Audio/Resampler", "Sinc");
set("Input/Driver", ruby::Input::optimalDriver());
set("Input/FocusLoss/Pause", false);
set("Input/FocusLoss/AllowInput", false);
set("Timing/Video", 60.0);
set("Timing/Audio", 48000.0);

View File

@ -63,32 +63,33 @@ Presentation::Presentation() {
settings["Video/AspectCorrection"].setValue(aspectCorrection.checked());
resizeViewport();
});
videoFilterMenu.setText("Video Filter");
if(settings["Video/Filter"].text() == "None") videoFilterNone.setChecked();
if(settings["Video/Filter"].text() == "Blur") videoFilterBlur.setChecked();
videoFilterNone.setText("None").onActivate([&] {
settings["Video/Filter"].setValue("None");
program->updateVideoFilter();
});
videoFilterBlur.setText("Blur").onActivate([&] {
settings["Video/Filter"].setValue("Blur");
program->updateVideoFilter();
});
blurEmulation.setText("Blur Emulation").setChecked(settings["Video/BlurEmulation"].boolean()).onToggle([&] {
videoEmulationMenu.setText("Video Emulation");
blurEmulation.setText("Blurring").setChecked(settings["Video/BlurEmulation"].boolean()).onToggle([&] {
settings["Video/BlurEmulation"].setValue(blurEmulation.checked());
if(emulator) emulator->set("Blur Emulation", blurEmulation.checked());
});
colorEmulation.setText("Color Emulation").setChecked(settings["Video/ColorEmulation"].boolean()).onToggle([&] {
colorEmulation.setText("Colors").setChecked(settings["Video/ColorEmulation"].boolean()).onToggle([&] {
settings["Video/ColorEmulation"].setValue(colorEmulation.checked());
if(emulator) emulator->set("Color Emulation", colorEmulation.checked());
});
scanlineEmulation.setText("Scanline Emulation").setChecked(settings["Video/ScanlineEmulation"].boolean()).onToggle([&] {
scanlineEmulation.setText("Scanlines").setChecked(settings["Video/ScanlineEmulation"].boolean()).onToggle([&] {
settings["Video/ScanlineEmulation"].setValue(scanlineEmulation.checked());
if(emulator) emulator->set("Scanline Emulation", scanlineEmulation.checked());
});
maskOverscan.setText("Mask Overscan").setChecked(settings["Video/Overscan/Mask"].boolean()).onToggle([&] {
settings["Video/Overscan/Mask"].setValue(maskOverscan.checked());
});
videoShaderMenu.setText("Video Shader");
if(settings["Video/Shader"].text() == "None") videoShaderNone.setChecked();
if(settings["Video/Shader"].text() == "Blur") videoShaderBlur.setChecked();
videoShaderNone.setText("None").onActivate([&] {
settings["Video/Shader"].setValue("None");
program->updateVideoShader();
});
videoShaderBlur.setText("Blur").onActivate([&] {
settings["Video/Shader"].setValue("Blur");
program->updateVideoShader();
});
loadShaders();
synchronizeVideo.setText("Synchronize Video").setChecked(settings["Video/Synchronize"].boolean()).onToggle([&] {
settings["Video/Synchronize"].setValue(synchronizeVideo.checked());
@ -127,6 +128,9 @@ Presentation::Presentation() {
manifestViewer.setText("Manifest Viewer").onActivate([&] { toolsManager->show(2); });
helpMenu.setText("Help");
documentation.setText("Documentation ...").onActivate([&] {
invoke("http://doc.byuu.org/higan/");
});
about.setText("About ...").onActivate([&] {
MessageDialog().setParent(*this).setTitle("About higan ...").setText({
Emulator::Name, " v", Emulator::Version, " (", Emulator::Profile, ")\n\n",
@ -149,11 +153,14 @@ Presentation::Presentation() {
resizeViewport();
setCentered();
#if defined(PLATFORM_WINDOWS)
Application::Windows::onModalChange([](bool modal) { if(modal && audio) audio->clear(); });
#endif
#if defined(PLATFORM_MACOSX)
showConfigurationSeparator.setVisible(false);
showConfiguration.setVisible(false);
helpMenu.setVisible(false);
about.setVisible(false);
Application::Cocoa::onAbout([&] { about.doActivate(); });
Application::Cocoa::onActivate([&] { setFocused(); });
Application::Cocoa::onPreferences([&] { showConfiguration.doActivate(); });
@ -271,31 +278,23 @@ auto Presentation::drawSplashScreen() -> void {
auto Presentation::loadShaders() -> void {
if(settings["Video/Driver"].text() != "OpenGL") {
videoShaderMenu.setVisible(false);
return;
}
auto pathname = locate({localpath(), "higan/"}, "Video Shaders/");
for(auto shader : directory::folders(pathname, "*.shader")) {
if(videoShaders.objectCount() == 2) videoShaderMenu.append(MenuSeparator());
MenuRadioItem item{&videoShaderMenu};
item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] {
settings["Video/Shader"].setValue({pathname, shader});
program->updateVideoFilter();
program->updateVideoShader();
});
videoShaders.append(item);
}
videoShaderMenu.setText("Video Shaders");
videoShaderNone.setChecked().setText("None").onActivate([=] {
settings["Video/Shader"].setValue("None");
program->updateVideoFilter();
});
for(auto object : videoShaders.objects()) {
if(auto radioItem = dynamic_cast<mMenuRadioItem*>(object.data())) {
if(settings["Video/Shader"].text() == string{pathname, radioItem->text(), ".shader/"}) {
radioItem->setChecked();
}
for(auto radioItem : videoShaders.objects<MenuRadioItem>()) {
if(settings["Video/Shader"].text() == string{pathname, radioItem.text(), ".shader/"}) {
radioItem.setChecked();
}
}
}

View File

@ -27,18 +27,15 @@ struct Presentation : Window {
Group videoScales{&videoScaleSmall, &videoScaleMedium, &videoScaleLarge};
MenuSeparator videoScaleSeparator{&videoScaleMenu};
MenuCheckItem aspectCorrection{&videoScaleMenu};
Menu videoFilterMenu{&settingsMenu};
MenuRadioItem videoFilterNone{&videoFilterMenu};
MenuRadioItem videoFilterBlur{&videoFilterMenu};
Group videoFilters{&videoFilterNone, &videoFilterBlur};
MenuSeparator videoFilterSeparator{&videoFilterMenu};
MenuCheckItem blurEmulation{&videoFilterMenu};
MenuCheckItem colorEmulation{&videoFilterMenu};
MenuCheckItem scanlineEmulation{&videoFilterMenu};
MenuCheckItem maskOverscan{&videoFilterMenu};
Menu videoEmulationMenu{&settingsMenu};
MenuCheckItem blurEmulation{&videoEmulationMenu};
MenuCheckItem colorEmulation{&videoEmulationMenu};
MenuCheckItem scanlineEmulation{&videoEmulationMenu};
MenuCheckItem maskOverscan{&videoEmulationMenu};
Menu videoShaderMenu{&settingsMenu};
MenuRadioItem videoShaderNone{&videoShaderMenu};
Group videoShaders{&videoShaderNone};
MenuRadioItem videoShaderBlur{&videoShaderMenu};
Group videoShaders{&videoShaderNone, &videoShaderBlur};
MenuSeparator videoSettingsSeparator{&settingsMenu};
MenuCheckItem synchronizeVideo{&settingsMenu};
MenuCheckItem synchronizeAudio{&settingsMenu};
@ -64,6 +61,7 @@ struct Presentation : Window {
MenuItem stateManager{&toolsMenu};
MenuItem manifestViewer{&toolsMenu};
Menu helpMenu{&menuBar};
MenuItem documentation{&helpMenu};
MenuItem about{&helpMenu};
FixedLayout layout{this};

View File

@ -53,11 +53,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
pitch >>= 2, length >>= 2;
for(auto y : range(height)) {
const uint32* sp = data + y * pitch;
uint32* dp = output + y * length;
for(auto x : range(width)) {
*dp++ = *sp++;
}
memory::copy(output + y * length, data + y * pitch, width * sizeof(uint32));
}
if(emulator->information.overscan && settings["Video/Overscan/Mask"].boolean()) {
@ -101,7 +97,7 @@ auto Program::audioSample(int16 lsample, int16 rsample) -> void {
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
if(presentation->focused()) {
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean()) {
auto guid = emulator->port[port].device[device].input[input].guid;
auto mapping = (InputMapping*)guid;
if(mapping) return mapping->poll();
@ -110,7 +106,7 @@ auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
}
auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void {
if(presentation->focused() || !enable) {
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean() || !enable) {
auto guid = emulator->port[port].device[device].input[input].guid;
auto mapping = (InputMapping*)guid;
if(mapping) return mapping->rumble(enable);

View File

@ -11,9 +11,8 @@ Program* program = nullptr;
Program::Program(lstring args) {
program = this;
directory::create({localpath(), "tomoko/"});
directory::create({localpath(), "higan/"});
Application::onMain({&Program::main, this});
Application::Windows::onModalChange([](bool modal) { if(modal && audio) audio->clear(); });
emulators.append(new Famicom::Interface);
emulators.append(new SuperFamicom::Interface);
@ -64,7 +63,7 @@ Program::Program(lstring args) {
presentation->drawSplashScreen();
updateVideoFilter();
updateVideoShader();
updateAudioVolume();
args.takeFirst(); //ignore program location in argument parsing
@ -101,7 +100,7 @@ auto Program::main() -> void {
updateStatusText();
inputManager->poll();
if(!emulator || !emulator->loaded() || pause) {
if(!emulator || !emulator->loaded() || pause || (!presentation->focused() && settings["Input/FocusLoss/Pause"].boolean())) {
audio->clear();
usleep(20 * 1000);
return;

View File

@ -32,7 +32,7 @@ struct Program : Emulator::Interface::Bind {
auto softReset() -> void;
auto showMessage(const string& text) -> void;
auto updateStatusText() -> void;
auto updateVideoFilter() -> void;
auto updateVideoShader() -> void;
auto updateAudio() -> void;
auto updateAudioVolume() -> void;
auto updateDSP() -> void;

View File

@ -24,7 +24,7 @@ auto Program::updateStatusText() -> void {
text = statusMessage;
} else if(!emulator || emulator->loaded() == false) {
text = "No cartridge loaded";
} else if(pause) {
} else if(pause || (!presentation->focused() && settings["Input/FocusLoss/Pause"].boolean())) {
text = "Paused";
} else {
text = statusText;
@ -35,15 +35,16 @@ auto Program::updateStatusText() -> void {
}
}
auto Program::updateVideoFilter() -> void {
auto Program::updateVideoShader() -> void {
if(settings["Video/Driver"].text() == "OpenGL"
&& settings["Video/Shader"].text() != "None"
&& settings["Video/Shader"].text() != "Blur"
&& directory::exists(settings["Video/Shader"].text())
) {
video->set(Video::Filter, Video::FilterNearest);
video->set(Video::Shader, settings["Video/Shader"].text());
} else {
video->set(Video::Filter, settings["Video/Filter"].text() == "Blur" ? Video::FilterLinear : Video::FilterNearest);
video->set(Video::Filter, settings["Video/Shader"].text() == "Blur" ? Video::FilterLinear : Video::FilterNearest);
video->set(Video::Shader, (string)"");
}
}

View File

@ -29,7 +29,7 @@ auto HotkeySettings::reloadMappings() -> void {
mappingList.append(ListViewHeader().setVisible()
.append(ListViewColumn().setText("Name"))
.append(ListViewColumn().setText("Mapping").setExpandable())
.append(ListViewColumn().setText("Device"))
.append(ListViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
);
for(auto& hotkey : inputManager->hotkeys) {
mappingList.append(ListViewItem()

View File

@ -3,6 +3,14 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
setText("Input");
layout.setMargin(5);
focusLabel.setText("When Focus is Lost:");
pauseEmulation.setText("Pause Emulation").setChecked(settings["Input/FocusLoss/Pause"].boolean()).onToggle([&] {
settings["Input/FocusLoss/Pause"].setValue(pauseEmulation.checked());
allowInput.setEnabled(!pauseEmulation.checked());
}).doToggle();
allowInput.setText("Allow Input").setChecked(settings["Input/FocusLoss/AllowInput"].boolean()).onToggle([&] {
settings["Input/FocusLoss/AllowInput"].setValue(allowInput.checked());
});
for(auto& emulator : inputManager->emulators) {
emulatorList.append(ComboButtonItem().setText(emulator.name));
}
@ -87,7 +95,7 @@ auto InputSettings::reloadMappings() -> void {
mappingList.append(ListViewHeader().setVisible()
.append(ListViewColumn().setText("Name"))
.append(ListViewColumn().setText("Mapping").setExpandable())
.append(ListViewColumn().setText("Device").setForegroundColor({0, 128, 0}))
.append(ListViewColumn().setText("Device").setAlignment(1.0).setForegroundColor({0, 128, 0}))
);
for(auto& mapping : activeDevice().mappings) {
mappingList.append(ListViewItem()

View File

@ -53,6 +53,10 @@ struct InputSettings : TabFrameItem {
Timer timer;
VerticalLayout layout{this};
HorizontalLayout focusLayout{&layout, Size{~0, 0}};
Label focusLabel{&focusLayout, Size{0, 0}};
CheckLabel pauseEmulation{&focusLayout, Size{0, 0}};
CheckLabel allowInput{&focusLayout, Size{0, 0}};
HorizontalLayout selectionLayout{&layout, Size{~0, 0}};
ComboButton emulatorList{&selectionLayout, Size{~0, 0}};
ComboButton portList{&selectionLayout, Size{~0, 0}};

View File

@ -4,6 +4,10 @@ include ../hiro/GNUmakefile
flags += -I.. -O3
link +=
ifeq ($(platform),windows)
link += -mwindows
endif
objects := obj/hiro.o
objects += obj/icarus.o
objects += $(if $(call streq,$(platform),windows),obj/resource.o)

View File

@ -74,6 +74,22 @@ auto nall::main(lstring args) -> void {
new SettingsDialog;
new ImportDialog;
new ErrorDialog;
#if defined(PLATFORM_MACOSX)
Application::Cocoa::onAbout([&] {
MessageDialog().setTitle("About icarus").setText({
"icarus\n\n"
"Author: byuu\n"
"License: GPLv3\n"
"Website: http://byuu.org/\n"
}).information();
});
Application::Cocoa::onPreferences([&] {
scanDialog->settingsButton.doActivate();
});
Application::Cocoa::onQuit({
Application::quit();
});
#endif
scanDialog->show();
Application::run();
}