diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 38fc8db9..7d799cc4 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -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/"; diff --git a/higan/fc/interface/interface.cpp b/higan/fc/interface/interface.cpp index ef12013d..0f5fb010 100644 --- a/higan/fc/interface/interface.cpp +++ b/higan/fc/interface/interface.cpp @@ -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()) return settings.colorEmulation = value.get(), true; + if(name == "Scanline Emulation" && value.is()) return settings.scanlineEmulation = value.get(), true; return false; } diff --git a/higan/fc/interface/interface.hpp b/higan/fc/interface/interface.hpp index b161cc0d..580355f2 100644 --- a/higan/fc/interface/interface.hpp +++ b/higan/fc/interface/interface.hpp @@ -58,6 +58,7 @@ private: struct Settings { bool colorEmulation = true; + bool scanlineEmulation = true; }; extern Interface* interface; diff --git a/higan/fc/video/video.cpp b/higan/fc/video/video.cpp index 9f7ce703..98b22d7b 100644 --- a/higan/fc/video/video.cpp +++ b/higan/fc/video/video.cpp @@ -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( diff --git a/higan/gb/apu/apu.cpp b/higan/gb/apu/apu.cpp index 2c8df0e6..a983205e 100644 --- a/higan/gb/apu/apu.cpp +++ b/higan/gb/apu/apu.cpp @@ -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); diff --git a/higan/gb/apu/master/master.cpp b/higan/gb/apu/master/master.cpp index 9157e86f..3feb4ec6 100644 --- a/higan/gb/apu/master/master.cpp +++ b/higan/gb/apu/master/master.cpp @@ -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; + } } } } diff --git a/higan/gb/apu/noise/noise.cpp b/higan/gb/apu/noise/noise.cpp index 3042cbd4..00986e98 100644 --- a/higan/gb/apu/noise/noise.cpp +++ b/higan/gb/apu/noise/noise.cpp @@ -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) { diff --git a/higan/gb/apu/square1/square1.cpp b/higan/gb/apu/square1/square1.cpp index adaacdc7..6442a7f0 100644 --- a/higan/gb/apu/square1/square1.cpp +++ b/higan/gb/apu/square1/square1.cpp @@ -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) { diff --git a/higan/gb/apu/square2/square2.cpp b/higan/gb/apu/square2/square2.cpp index f07d5203..d993eec0 100644 --- a/higan/gb/apu/square2/square2.cpp +++ b/higan/gb/apu/square2/square2.cpp @@ -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) { diff --git a/higan/gb/apu/wave/wave.cpp b/higan/gb/apu/wave/wave.cpp index b0032748..20f623f4 100644 --- a/higan/gb/apu/wave/wave.cpp +++ b/higan/gb/apu/wave/wave.cpp @@ -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; } diff --git a/higan/sfc/alt/cpu/timing.cpp b/higan/sfc/alt/cpu/timing.cpp index 5ac063d4..741e7ab1 100644 --- a/higan/sfc/alt/cpu/timing.cpp +++ b/higan/sfc/alt/cpu/timing.cpp @@ -60,7 +60,6 @@ auto CPU::scanline() -> void { synchronizeSMP(); synchronizePPU(); synchronizeCoprocessors(); - system.scanline(); if(vcounter() == 0) hdma_init(); diff --git a/higan/sfc/alt/ppu-balanced/ppu.cpp b/higan/sfc/alt/ppu-balanced/ppu.cpp index 8b454b24..43ed24e8 100644 --- a/higan/sfc/alt/ppu-balanced/ppu.cpp +++ b/higan/sfc/alt/ppu-balanced/ppu.cpp @@ -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(); diff --git a/higan/sfc/alt/ppu-balanced/ppu.hpp b/higan/sfc/alt/ppu-balanced/ppu.hpp index 74a80198..9f2305f5 100644 --- a/higan/sfc/alt/ppu-balanced/ppu.hpp +++ b/higan/sfc/alt/ppu-balanced/ppu.hpp @@ -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; diff --git a/higan/sfc/alt/ppu-balanced/render/line.cpp b/higan/sfc/alt/ppu-balanced/render/line.cpp index 430a8cd9..49b75e85 100644 --- a/higan/sfc/alt/ppu-balanced/render/line.cpp +++ b/higan/sfc/alt/ppu-balanced/render/line.cpp @@ -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)); } diff --git a/higan/sfc/alt/ppu-performance/mmio/mmio.cpp b/higan/sfc/alt/ppu-performance/mmio/mmio.cpp index 2080a43c..20864eb8 100644 --- a/higan/sfc/alt/ppu-performance/mmio/mmio.cpp +++ b/higan/sfc/alt/ppu-performance/mmio/mmio.cpp @@ -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; diff --git a/higan/sfc/alt/ppu-performance/ppu.cpp b/higan/sfc/alt/ppu-performance/ppu.cpp index 295a6bac..a4f9e406 100644 --- a/higan/sfc/alt/ppu-performance/ppu.cpp +++ b/higan/sfc/alt/ppu-performance/ppu.cpp @@ -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; diff --git a/higan/sfc/alt/ppu-performance/ppu.hpp b/higan/sfc/alt/ppu-performance/ppu.hpp index fb001cf5..6bb622f6 100644 --- a/higan/sfc/alt/ppu-performance/ppu.hpp +++ b/higan/sfc/alt/ppu-performance/ppu.hpp @@ -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" diff --git a/higan/sfc/alt/ppu-performance/screen/screen.cpp b/higan/sfc/alt/ppu-performance/screen/screen.cpp index 0f785802..d941c46c 100644 --- a/higan/sfc/alt/ppu-performance/screen/screen.cpp +++ b/higan/sfc/alt/ppu-performance/screen/screen.cpp @@ -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++) { diff --git a/higan/sfc/cpu/timing/timing.cpp b/higan/sfc/cpu/timing/timing.cpp index 1706e766..801fd679 100644 --- a/higan/sfc/cpu/timing/timing.cpp +++ b/higan/sfc/cpu/timing/timing.cpp @@ -42,7 +42,6 @@ auto CPU::scanline() -> void { synchronizeSMP(); synchronizePPU(); synchronizeCoprocessors(); - system.scanline(); if(vcounter() == 0) { //HDMA init triggers once every frame diff --git a/higan/sfc/ppu/mmio/mmio.cpp b/higan/sfc/ppu/mmio/mmio.cpp index 5992d089..d42ecf8f 100644 --- a/higan/sfc/ppu/mmio/mmio.cpp +++ b/higan/sfc/ppu/mmio/mmio.cpp @@ -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(); diff --git a/higan/sfc/ppu/ppu.cpp b/higan/sfc/ppu/ppu.cpp index 5371853b..f3ff6af8 100644 --- a/higan/sfc/ppu/ppu.cpp +++ b/higan/sfc/ppu/ppu.cpp @@ -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; diff --git a/higan/sfc/ppu/ppu.hpp b/higan/sfc/ppu/ppu.hpp index 4e688a0f..ff1a3d3f 100644 --- a/higan/sfc/ppu/ppu.hpp +++ b/higan/sfc/ppu/ppu.hpp @@ -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; diff --git a/higan/sfc/system/audio.cpp b/higan/sfc/system/audio.cpp index 81cf0dad..556e80fe 100644 --- a/higan/sfc/system/audio.cpp +++ b/higan/sfc/system/audio.cpp @@ -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]; diff --git a/higan/sfc/system/audio.hpp b/higan/sfc/system/audio.hpp index 5c5295f0..ac438b1d 100644 --- a/higan/sfc/system/audio.hpp +++ b/higan/sfc/system/audio.hpp @@ -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; diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index ba0d4fc0..29a40c9b 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -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 { -} - } diff --git a/higan/sfc/system/system.hpp b/higan/sfc/system/system.hpp index 9682867a..582cbba4 100644 --- a/higan/sfc/system/system.hpp +++ b/higan/sfc/system/system.hpp @@ -19,9 +19,6 @@ struct System : property { auto power() -> void; auto reset() -> void; - auto frame() -> void; - auto scanline() -> void; - //return *active* system information (settings are cached upon power-on) readonly region; readonly expansionPort; diff --git a/higan/sfc/system/video.cpp b/higan/sfc/system/video.cpp index dea13c66..e1d568d6 100644 --- a/higan/sfc/system/video.cpp +++ b/higan/sfc/system/video.cpp @@ -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, diff --git a/higan/sfc/system/video.hpp b/higan/sfc/system/video.hpp index 4ed63209..840f29f6 100644 --- a/higan/sfc/system/video.hpp +++ b/higan/sfc/system/video.hpp @@ -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; diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index 7da17ed6..13c1466d 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -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); diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 53906d1b..e7a51123 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -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(object.data())) { - if(settings["Video/Shader"].text() == string{pathname, radioItem->text(), ".shader/"}) { - radioItem->setChecked(); - } + for(auto radioItem : videoShaders.objects()) { + if(settings["Video/Shader"].text() == string{pathname, radioItem.text(), ".shader/"}) { + radioItem.setChecked(); } } } diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index d40b69ad..05f21d3b 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -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}; diff --git a/higan/target-tomoko/program/interface.cpp b/higan/target-tomoko/program/interface.cpp index be612b95..7918574d 100644 --- a/higan/target-tomoko/program/interface.cpp +++ b/higan/target-tomoko/program/interface.cpp @@ -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); diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index cce4e59b..aa716c61 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -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; diff --git a/higan/target-tomoko/program/program.hpp b/higan/target-tomoko/program/program.hpp index 9033c311..0f8832c9 100644 --- a/higan/target-tomoko/program/program.hpp +++ b/higan/target-tomoko/program/program.hpp @@ -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; diff --git a/higan/target-tomoko/program/utility.cpp b/higan/target-tomoko/program/utility.cpp index 03ba5095..62687781 100644 --- a/higan/target-tomoko/program/utility.cpp +++ b/higan/target-tomoko/program/utility.cpp @@ -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)""); } } diff --git a/higan/target-tomoko/settings/hotkeys.cpp b/higan/target-tomoko/settings/hotkeys.cpp index 030e5e55..de30aca0 100644 --- a/higan/target-tomoko/settings/hotkeys.cpp +++ b/higan/target-tomoko/settings/hotkeys.cpp @@ -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() diff --git a/higan/target-tomoko/settings/input.cpp b/higan/target-tomoko/settings/input.cpp index ebafdb90..7cd6d965 100644 --- a/higan/target-tomoko/settings/input.cpp +++ b/higan/target-tomoko/settings/input.cpp @@ -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() diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 0fc6380a..9fc00951 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -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}}; diff --git a/icarus/GNUmakefile b/icarus/GNUmakefile index 138dd0ac..677f881c 100644 --- a/icarus/GNUmakefile +++ b/icarus/GNUmakefile @@ -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) diff --git a/icarus/icarus.cpp b/icarus/icarus.cpp index fbb5ae08..2da0855f 100644 --- a/icarus/icarus.cpp +++ b/icarus/icarus.cpp @@ -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(); }