diff --git a/higan/GNUmakefile b/higan/GNUmakefile index 1e8fce2e..9e194d41 100644 --- a/higan/GNUmakefile +++ b/higan/GNUmakefile @@ -35,12 +35,7 @@ ifeq ($(platform),windows) link += -Wl,-enable-runtime-pseudo-reloc else ifeq ($(platform),macosx) flags += -march=native -else ifeq ($(platform),linux) - flags += -march=native -fopenmp - link += -fopenmp - link += -Wl,-export-dynamic - link += -lX11 -lXext -else ifeq ($(platform),bsd) +else ifneq ($(filter $(platform),linux bsd),) flags += -march=native -fopenmp link += -fopenmp link += -Wl,-export-dynamic diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index e1a86f6f..38fc8db9 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.06"; + static const string Version = "096.07"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index 0999cbf4..decd72c3 100644 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -49,8 +49,7 @@ struct Interface { virtual auto loadRequest(uint, string, string, bool) -> void {} virtual auto loadRequest(uint, string, bool) -> void {} virtual auto saveRequest(uint, string) -> void {} - virtual auto videoColor(uint, uint16, uint16, uint16, uint16) -> uint32 { return 0u; } - virtual auto videoRefresh(const uint32*, const uint32*, uint, uint, uint) -> void {} + virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {} virtual auto audioSample(int16, int16) -> void {} virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; } virtual auto inputRumble(uint, uint, uint, bool) -> void {} @@ -64,8 +63,7 @@ struct Interface { auto loadRequest(uint id, string name, string type, bool required) -> void { return bind->loadRequest(id, name, type, required); } auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); } auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); } - auto videoColor(uint source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 { return bind->videoColor(source, alpha, red, green, blue); } - auto videoRefresh(const uint32* palette, const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(palette, data, pitch, width, height); } + auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); } auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); } auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); } auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); } @@ -106,9 +104,10 @@ struct Interface { //cheat functions virtual auto cheatSet(const lstring& = lstring{}) -> void {} - //utility functions - enum class PaletteMode : uint { Literal, Channel, Standard, Emulation }; - virtual auto paletteUpdate(PaletteMode mode) -> void {} + //settings + virtual auto cap(const string& name) -> bool { return false; } + virtual auto get(const string& name) -> any { return {}; } + virtual auto set(const string& name, const any& value) -> bool { return false; } }; } diff --git a/higan/fc/interface/interface.cpp b/higan/fc/interface/interface.cpp index d43143f8..ef12013d 100644 --- a/higan/fc/interface/interface.cpp +++ b/higan/fc/interface/interface.cpp @@ -3,6 +3,7 @@ namespace Famicom { Interface* interface = nullptr; +Settings settings; Interface::Interface() { interface = this; @@ -166,8 +167,19 @@ auto Interface::cheatSet(const lstring& list) -> void { } } -auto Interface::paletteUpdate(PaletteMode mode) -> void { - video.generate_palette(mode); +auto Interface::cap(const string& name) -> bool { + if(name == "Color Emulation") return true; + return false; +} + +auto Interface::get(const string& name) -> any { + if(name == "Color Emulation") return settings.colorEmulation; + return {}; +} + +auto Interface::set(const string& name, const any& value) -> bool { + if(name == "Color Emulation" && value.is()) return settings.colorEmulation = value.get(), true; + return false; } } diff --git a/higan/fc/interface/interface.hpp b/higan/fc/interface/interface.hpp index f79fe94f..b161cc0d 100644 --- a/higan/fc/interface/interface.hpp +++ b/higan/fc/interface/interface.hpp @@ -48,12 +48,19 @@ struct Interface : Emulator::Interface { auto cheatSet(const lstring&) -> void; - auto paletteUpdate(PaletteMode mode) -> void; + auto cap(const string& name) -> bool override; + auto get(const string& name) -> any override; + auto set(const string& name, const any& value) -> bool override; private: vector device; }; +struct Settings { + bool colorEmulation = true; +}; + extern Interface* interface; +extern Settings settings; } diff --git a/higan/fc/system/system.cpp b/higan/fc/system/system.cpp index 667447d5..a54d7b51 100644 --- a/higan/fc/system/system.cpp +++ b/higan/fc/system/system.cpp @@ -8,7 +8,7 @@ System system; auto System::run() -> void { scheduler.enter(); if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { - interface->videoRefresh(video.palette, ppu.buffer, 4 * 256, 256, 240); + video.refresh(); } } @@ -36,7 +36,7 @@ auto System::runthreadtosave() -> void { scheduler.enter(); if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { - interface->videoRefresh(video.palette, ppu.buffer, 4 * 256, 256, 240); + video.refresh(); } } } @@ -65,6 +65,7 @@ auto System::reset() -> void { ppu.reset(); input.reset(); scheduler.reset(); + video.reset(); } auto System::init() -> void { diff --git a/higan/fc/video/video.cpp b/higan/fc/video/video.cpp index 36ce5846..9f7ce703 100644 --- a/higan/fc/video/video.cpp +++ b/higan/fc/video/video.cpp @@ -7,34 +7,41 @@ namespace Famicom { Video video; Video::Video() { - palette = new uint32_t[1 << 9](); + output = new uint32[256 * 240]; + paletteStandard = new uint32[1 << 9]; + paletteEmulation = new uint32[1 << 9]; } Video::~Video() { - delete[] palette; + delete[] output; + delete[] paletteStandard; + delete[] paletteEmulation; } -auto Video::generate_palette(Emulator::Interface::PaletteMode mode) -> void { +auto Video::reset() -> void { + memory::fill(output, 256 * 240); + for(auto color : range(1 << 9)) { - if(mode == Emulator::Interface::PaletteMode::Literal) { - palette[color] = color; - } else if(mode == Emulator::Interface::PaletteMode::Channel) { - uint emphasis = (color >> 6) & 7; - uint luma = (color >> 4) & 3; - uint chroma = (color >> 0) & 15; - emphasis = image::normalize(emphasis, 3, 16); - luma = image::normalize(luma, 2, 16); - chroma = image::normalize(chroma, 4, 16); - palette[color] = interface->videoColor(color, 0, emphasis, luma, chroma); - } else if(mode == Emulator::Interface::PaletteMode::Standard) { - palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 2.2); - } else if(mode == Emulator::Interface::PaletteMode::Emulation) { - palette[color] = generate_color(color, 2.0, 0.0, 1.0, 1.0, 1.8); - } + paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2); + paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8); } } -auto Video::generate_color( +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++]; + } + } + + interface->videoRefresh(output, 4 * 256, 256, 240); +} + +auto Video::generateColor( uint n, double saturation, double hue, double contrast, double brightness, double gamma ) -> uint32 { @@ -75,11 +82,11 @@ auto Video::generate_color( q *= saturation; auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); }; - uint r = 65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q); - uint g = 65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q); - uint b = 65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q); + uint r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q)); + uint g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q)); + uint b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q)); - return interface->videoColor(n, 0, uclamp<16>(r), uclamp<16>(g), uclamp<16>(b)); + return (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0); } } diff --git a/higan/fc/video/video.hpp b/higan/fc/video/video.hpp index 7a2ebc8d..e0a73a23 100644 --- a/higan/fc/video/video.hpp +++ b/higan/fc/video/video.hpp @@ -2,12 +2,15 @@ struct Video { Video(); ~Video(); - auto generate_palette(Emulator::Interface::PaletteMode mode) -> void; + auto reset() -> void; + auto refresh() -> void; - uint32* palette = nullptr; + uint32* output = nullptr; + uint32* paletteStandard = nullptr; + uint32* paletteEmulation = nullptr; private: - auto generate_color(uint, double, double, double, double, double) -> uint32; + auto generateColor(uint, double, double, double, double, double) -> uint32; }; extern Video video; diff --git a/higan/gb/interface/interface.cpp b/higan/gb/interface/interface.cpp index cd46c486..aa740750 100644 --- a/higan/gb/interface/interface.cpp +++ b/higan/gb/interface/interface.cpp @@ -3,6 +3,7 @@ namespace GameBoy { Interface* interface = nullptr; +Settings settings; Interface::Interface() { interface = this; @@ -167,10 +168,6 @@ auto Interface::cheatSet(const lstring& list) -> void { } } -auto Interface::paletteUpdate(PaletteMode mode) -> void { - video.generate_palette(mode); -} - auto Interface::lcdScanline() -> void { if(hook) hook->lcdScanline(); } @@ -183,4 +180,22 @@ auto Interface::joypWrite(bool p15, bool p14) -> void { if(hook) hook->joypWrite(p15, p14); } +auto Interface::cap(const string& name) -> bool { + if(name == "Blur Emulation") return true; + if(name == "Color Emulation") return true; + return false; +} + +auto Interface::get(const string& name) -> any { + if(name == "Blur Emulation") return settings.blurEmulation; + if(name == "Color Emulation") return settings.colorEmulation; + return {}; +} + +auto Interface::set(const string& name, const any& value) -> bool { + if(name == "Blur Emulation" && value.is()) return settings.blurEmulation = value.get(), true; + if(name == "Color Emulation" && value.is()) return settings.colorEmulation = value.get(), true; + return false; +} + } diff --git a/higan/gb/interface/interface.hpp b/higan/gb/interface/interface.hpp index 6436b656..55723826 100644 --- a/higan/gb/interface/interface.hpp +++ b/higan/gb/interface/interface.hpp @@ -50,7 +50,9 @@ struct Interface : Emulator::Interface { auto cheatSet(const lstring&) -> void; - auto paletteUpdate(PaletteMode mode) -> void; + auto cap(const string& name) -> bool override; + auto get(const string& name) -> any override; + auto set(const string& name, const any& value) -> bool override; //Super Game Boy bindings struct Hook { @@ -68,6 +70,12 @@ private: vector device; }; +struct Settings { + bool blurEmulation = true; + bool colorEmulation = true; +}; + extern Interface* interface; +extern Settings settings; } diff --git a/higan/gb/ppu/ppu.cpp b/higan/gb/ppu/ppu.cpp index 80a67406..a097c754 100644 --- a/higan/gb/ppu/ppu.cpp +++ b/higan/gb/ppu/ppu.cpp @@ -160,7 +160,7 @@ auto PPU::power() -> void { status.obpi_increment = 0; status.obpi = 0; - for(auto& n : screen) n = 0x0000; + for(auto& n : screen) n = 0; bg.color = 0; bg.palette = 0; diff --git a/higan/gb/system/system.cpp b/higan/gb/system/system.cpp index 858b9dd0..1bb6d80b 100644 --- a/higan/gb/system/system.cpp +++ b/higan/gb/system/system.cpp @@ -16,7 +16,7 @@ auto System::run() -> void { scheduler.enter(); if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { - interface->videoRefresh(video.palette, ppu.screen, 4 * 160, 160, 144); + video.refresh(); } } @@ -40,7 +40,7 @@ auto System::runthreadtosave() -> void { scheduler.enter(); if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { - interface->videoRefresh(video.palette, ppu.screen, 4 * 160, 160, 144); + video.refresh(); } } } @@ -71,6 +71,7 @@ auto System::power() -> void { cpu.power(); ppu.power(); apu.power(); + video.power(); scheduler.init(); clocks_executed = 0; diff --git a/higan/gb/video/video.cpp b/higan/gb/video/video.cpp index 30564e6e..a19fa351 100644 --- a/higan/gb/video/video.cpp +++ b/higan/gb/video/video.cpp @@ -5,89 +5,84 @@ namespace GameBoy { Video video; Video::Video() { - palette = new uint32_t[1 << 15](); + output = new uint32[160 * 144]; + paletteStandard = new uint32[1 << 15]; + paletteEmulation = new uint32[1 << 15]; } Video::~Video() { - delete[] palette; + delete[] output; + delete[] paletteStandard; + delete[] paletteEmulation; } -auto Video::generate_palette(Emulator::Interface::PaletteMode mode) -> void { - this->mode = mode; - if(system.dmg()) for(auto n : range(4)) palette[n] = paletteDMG(n); - if(system.sgb()) for(auto n : range(4)) palette[n] = paletteSGB(n); - if(system.cgb()) for(auto n : range(1 << 15)) palette[n] = paletteCGB(n); +auto Video::power() -> void { + memory::fill(output, 160 * 144 * sizeof(uint32)); + + if(system.dmg()) { + for(auto color : range(1 << 2)) { + uint L = image::normalize(3 - color, 2, 8); + uint R = monochrome[color][0] >> 8; + uint G = monochrome[color][1] >> 8; + uint B = monochrome[color][2] >> 8; + paletteStandard[color] = (255 << 24) | (L << 16) | (L << 8) | (L << 0); + paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0); + } + } + + if(system.sgb()) { + for(auto color : range(1 << 2)) { + paletteStandard[color] = color; + paletteEmulation[color] = color; + } + } + + if(system.cgb()) { + for(auto color : range(1 << 15)) { + uint r = (uint5)(color >> 0); + uint g = (uint5)(color >> 5); + uint b = (uint5)(color >> 10); + + { uint R = image::normalize(r, 5, 8); + uint G = image::normalize(g, 5, 8); + uint B = image::normalize(b, 5, 8); + paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0); + } + + { uint R = (r * 26 + g * 4 + b * 2); + uint G = ( g * 24 + b * 8); + uint B = (r * 6 + g * 4 + b * 22); + R = min(960, R) >> 2; + G = min(960, G) >> 2; + B = min(960, B) >> 2; + paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0); + } + } + } } -auto Video::paletteDMG(uint color) const -> uint { - if(mode == Emulator::Interface::PaletteMode::Literal) { - return color; +auto Video::refresh() -> void { + auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard; + + for(uint y = 0; y < 144; y++) { + auto source = ppu.screen + y * 160; + auto target = output + y * 160; + + if(settings.blurEmulation) { + for(uint x = 0; x < 160; x++) { + auto a = palette[*source++]; + auto b = *target; + *target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1; + } + } else { + for(uint x = 0; x < 160; x++) { + auto color = palette[*source++]; + *target++ = color; + } + } } - if(mode == Emulator::Interface::PaletteMode::Channel) { - uint L = image::normalize(color, 2, 16); - return interface->videoColor(color, 0, 0, 0, L); - } - - if(mode == Emulator::Interface::PaletteMode::Standard) { - uint L = image::normalize(3 - color, 2, 16); - return interface->videoColor(color, 0, L, L, L); - } - - if(mode == Emulator::Interface::PaletteMode::Emulation) { - uint R = monochrome[color][0]; - uint G = monochrome[color][1]; - uint B = monochrome[color][2]; - return interface->videoColor(color, 0, R, G, B); - } - - return 0; -} - -auto Video::paletteSGB(uint color) const -> uint { - return color; -} - -auto Video::paletteCGB(uint color) const -> uint { - if(mode == Emulator::Interface::PaletteMode::Literal) { - return color; - } - - uint r = (color >> 0) & 31; - uint g = (color >> 5) & 31; - uint b = (color >> 10) & 31; - - if(mode == Emulator::Interface::PaletteMode::Channel) { - r = image::normalize(r, 5, 16); - g = image::normalize(g, 5, 16); - b = image::normalize(b, 5, 16); - return interface->videoColor(color, 0, r, g, b); - } - - if(mode == Emulator::Interface::PaletteMode::Standard) { - r = image::normalize(r, 5, 16); - g = image::normalize(g, 5, 16); - b = image::normalize(b, 5, 16); - return interface->videoColor(color, 0, r, g, b); - } - - if(mode == Emulator::Interface::PaletteMode::Emulation) { - uint R = (r * 26 + g * 4 + b * 2); - uint G = ( g * 24 + b * 8); - uint B = (r * 6 + g * 4 + b * 22); - - R = min(960, R); - G = min(960, G); - B = min(960, B); - - R = R << 6 | R >> 4; - G = G << 6 | G >> 4; - B = B << 6 | B >> 4; - - return interface->videoColor(color, 0, R, G, B); - } - - return 0; + interface->videoRefresh(output, 4 * 160, 160, 144); } #define DMG_PALETTE_GREEN diff --git a/higan/gb/video/video.hpp b/higan/gb/video/video.hpp index 17ef9f97..a058f7d7 100644 --- a/higan/gb/video/video.hpp +++ b/higan/gb/video/video.hpp @@ -2,16 +2,15 @@ struct Video { Video(); ~Video(); - auto generate_palette(Emulator::Interface::PaletteMode mode) -> void; + auto power() -> void; + auto refresh() -> void; - uint32* palette = nullptr; + uint32* output = nullptr; + uint32* paletteStandard = nullptr; + uint32* paletteEmulation = nullptr; private: - Emulator::Interface::PaletteMode mode; static const uint16 monochrome[4][3]; - auto paletteDMG(uint color) const -> uint; - auto paletteSGB(uint color) const -> uint; - auto paletteCGB(uint color) const -> uint; }; extern Video video; diff --git a/higan/gba/apu/registers.hpp b/higan/gba/apu/registers.hpp index 99fcff37..fbf99219 100644 --- a/higan/gba/apu/registers.hpp +++ b/higan/gba/apu/registers.hpp @@ -125,7 +125,6 @@ struct Sequencer { uint3 rvolume; uint1 lenable[4]; uint1 renable[4]; - uint1 enable[4]; uint1 masterenable; uint12 base; diff --git a/higan/gba/apu/sequencer.cpp b/higan/gba/apu/sequencer.cpp index 6bc48f31..71eef05f 100644 --- a/higan/gba/apu/sequencer.cpp +++ b/higan/gba/apu/sequencer.cpp @@ -20,10 +20,10 @@ auto APU::runsequencer() -> void { } r.base++; - if(r.enable[0]) square1.run(); - if(r.enable[1]) square2.run(); - if(r.enable[2]) wave.run(); - if(r.enable[3]) noise.run(); + if(square1.enable) square1.run(); + if(square2.enable) square2.run(); + if(wave.enable) wave.run(); + if(noise.enable) noise.run(); } auto APU::Sequencer::read(uint addr) const -> uint8 { @@ -39,7 +39,13 @@ auto APU::Sequencer::read(uint addr) const -> uint8 { | (lenable[2] << 6) | (lenable[3] << 7) ); - case 2: return (masterenable << 7); + case 2: return ( + (apu.square1.enable << 0) + | (apu.square2.enable << 1) + | (apu.wave.enable << 2) + | (apu.noise.enable << 3) + | (masterenable << 7) + ); } } @@ -62,10 +68,6 @@ auto APU::Sequencer::write(uint addr, uint8 byte) -> void { break; case 2: //NR52 - enable[0] = byte >> 0; - enable[1] = byte >> 1; - enable[2] = byte >> 2; - enable[3] = byte >> 3; masterenable = byte >> 7; break; } @@ -76,7 +78,6 @@ auto APU::Sequencer::power() -> void { rvolume = 0; for(auto& n : lenable) n = 0; for(auto& n : renable) n = 0; - for(auto& n : enable) n = 0; masterenable = 0; base = 0; step = 0; diff --git a/higan/gba/apu/serialization.cpp b/higan/gba/apu/serialization.cpp index ba73ec12..0918cf05 100644 --- a/higan/gba/apu/serialization.cpp +++ b/higan/gba/apu/serialization.cpp @@ -86,7 +86,6 @@ auto APU::serialize(serializer& s) -> void { s.integer(sequencer.rvolume); for(auto& flag : sequencer.lenable) s.integer(flag); for(auto& flag : sequencer.renable) s.integer(flag); - for(auto& flag : sequencer.enable) s.integer(flag); s.integer(sequencer.masterenable); s.integer(sequencer.base); s.integer(sequencer.step); diff --git a/higan/gba/interface/interface.cpp b/higan/gba/interface/interface.cpp index 41475e4e..7ae297be 100644 --- a/higan/gba/interface/interface.cpp +++ b/higan/gba/interface/interface.cpp @@ -3,6 +3,7 @@ namespace GameBoyAdvance { Interface* interface = nullptr; +Settings settings; Interface::Interface() { interface = this; @@ -153,8 +154,22 @@ auto Interface::unserialize(serializer& s) -> bool { return system.unserialize(s); } -auto Interface::paletteUpdate(PaletteMode mode) -> void { - video.generatePalette(mode); +auto Interface::cap(const string& name) -> bool { + if(name == "Blur Emulation") return true; + if(name == "Color Emulation") return true; + return false; +} + +auto Interface::get(const string& name) -> any { + if(name == "Blur Emulation") return settings.blurEmulation; + if(name == "Color Emulation") return settings.colorEmulation; + return {}; +} + +auto Interface::set(const string& name, const any& value) -> bool { + if(name == "Blur Emulation" && value.is()) return settings.blurEmulation = value.get(), true; + if(name == "Color Emulation" && value.is()) return settings.colorEmulation = value.get(), true; + return false; } } diff --git a/higan/gba/interface/interface.hpp b/higan/gba/interface/interface.hpp index 675f72d6..c677b3c4 100644 --- a/higan/gba/interface/interface.hpp +++ b/higan/gba/interface/interface.hpp @@ -45,12 +45,20 @@ struct Interface : Emulator::Interface { auto serialize() -> serializer; auto unserialize(serializer&) -> bool; - auto paletteUpdate(PaletteMode mode) -> void; + auto cap(const string& name) -> bool override; + auto get(const string& name) -> any override; + auto set(const string& name, const any& value) -> bool override; private: vector device; }; +struct Settings { + bool blurEmulation = true; + bool colorEmulation = true; +}; + extern Interface* interface; +extern Settings settings; } diff --git a/higan/gba/system/system.cpp b/higan/gba/system/system.cpp index 464d3064..d48a672d 100644 --- a/higan/gba/system/system.cpp +++ b/higan/gba/system/system.cpp @@ -20,6 +20,7 @@ auto System::power() -> void { ppu.power(); apu.power(); cartridge.power(); + video.power(); scheduler.power(); } @@ -39,7 +40,7 @@ auto System::run() -> void { scheduler.enter(); if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break; } - interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160); + video.refresh(); } auto System::runtosave() -> void { @@ -62,7 +63,7 @@ auto System::runthreadtosave() -> void { scheduler.enter(); if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { - interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160); + video.refresh(); } } } diff --git a/higan/gba/video/video.cpp b/higan/gba/video/video.cpp index 6f8e045c..bca222a2 100644 --- a/higan/gba/video/video.cpp +++ b/higan/gba/video/video.cpp @@ -5,62 +5,65 @@ namespace GameBoyAdvance { Video video; Video::Video() { - palette = new uint32[1 << 15](); + output = new uint32[240 * 160]; + paletteStandard = new uint32[1 << 15]; + paletteEmulation = new uint32[1 << 15]; } Video::~Video() { - delete[] palette; + delete[] output; + delete[] paletteStandard; + delete[] paletteEmulation; } -auto Video::generatePalette(Emulator::Interface::PaletteMode mode) -> void { +auto Video::power() -> void { + memory::fill(output, 240 * 160 * sizeof(uint32)); + for(auto color : range(1 << 15)) { - if(mode == Emulator::Interface::PaletteMode::Literal) { - palette[color] = color; - continue; + uint B = (uint5)(color >> 10); + uint G = (uint5)(color >> 5); + uint R = (uint5)(color >> 0); + + { uint b = image::normalize(B, 5, 8); + uint g = image::normalize(G, 5, 8); + uint r = image::normalize(R, 5, 8); + paletteStandard[color] = (255 << 24) | (r << 16) | (g << 8) | (b << 0); } - uint B = (color >> 10) & 31; - uint G = (color >> 5) & 31; - uint R = (color >> 0) & 31; - - if(mode == Emulator::Interface::PaletteMode::Channel) { - R = image::normalize(R, 5, 16); - G = image::normalize(G, 5, 16); - B = image::normalize(B, 5, 16); - palette[color] = interface->videoColor(color, 0, R, G, B); - continue; - } - - if(mode == Emulator::Interface::PaletteMode::Standard) { - R = image::normalize(R, 5, 16); - G = image::normalize(G, 5, 16); - B = image::normalize(B, 5, 16); - palette[color] = interface->videoColor(color, 0, R, G, B); - continue; - } - - if(mode == Emulator::Interface::PaletteMode::Emulation) { - double lcdGamma = 4.0, outGamma = 2.2; + { double lcdGamma = 4.0, outGamma = 2.2; double lb = pow(B / 31.0, lcdGamma); double lg = pow(G / 31.0, lcdGamma); double lr = pow(R / 31.0, lcdGamma); - B = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); - G = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); - R = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); - - palette[color] = interface->videoColor(color, 0, R, G, B); - continue; + uint b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); + uint g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); + uint r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); + paletteEmulation[color] = (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0); } - - palette[color] = 0; } } -const uint8 Video::curve[32] = { - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x10, 0x12, - 0x14, 0x16, 0x18, 0x1c, 0x20, 0x28, 0x38, 0x38, - 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x80, - 0x88, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, -}; +auto Video::refresh() -> void { + auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard; + + for(uint y = 0; y < 160; y++) { + auto source = ppu.output + y * 240; + auto target = output + y * 240; + + if(settings.blurEmulation) { + for(uint x = 0; x < 240; x++) { + auto a = palette[*source++]; + auto b = *target; + *target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1; + } + } else { + for(uint x = 0; x < 240; x++) { + auto color = palette[*source++]; + *target++ = color; + } + } + } + + interface->videoRefresh(output, 240 * sizeof(uint32), 240, 160); +} } diff --git a/higan/gba/video/video.hpp b/higan/gba/video/video.hpp index 8f235443..fda1ea71 100644 --- a/higan/gba/video/video.hpp +++ b/higan/gba/video/video.hpp @@ -2,12 +2,12 @@ struct Video { Video(); ~Video(); - auto generatePalette(Emulator::Interface::PaletteMode mode) -> void; + auto power() -> void; + auto refresh() -> void; - uint32* palette = nullptr; - -private: - static const uint8 curve[32]; + uint32* output = nullptr; + uint32* paletteStandard = nullptr; + uint32* paletteEmulation = nullptr; }; extern Video video; diff --git a/higan/sfc/coprocessor/icd2/icd2.cpp b/higan/sfc/coprocessor/icd2/icd2.cpp index 77fd95e0..c816a7ed 100644 --- a/higan/sfc/coprocessor/icd2/icd2.cpp +++ b/higan/sfc/coprocessor/icd2/icd2.cpp @@ -71,7 +71,6 @@ auto ICD2::reset() -> void { joyp14lock = 0; pulselock = true; - GameBoy::video.generate_palette(Emulator::Interface::PaletteMode::Literal); GameBoy::system.init(); GameBoy::system.power(); } diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index c243acd1..ad4d2256 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -3,6 +3,7 @@ namespace SuperFamicom { Interface* interface = nullptr; +Settings settings; Interface::Interface() { interface = this; @@ -469,8 +470,25 @@ auto Interface::cheatSet(const lstring& list) -> void { } } -auto Interface::paletteUpdate(PaletteMode mode) -> void { - video.generate_palette(mode); +auto Interface::cap(const string& name) -> bool { + if(name == "Blur Emulation") return true; + if(name == "Color Emulation") return true; + if(name == "Scanline Emulation") return true; + return false; +} + +auto Interface::get(const string& name) -> any { + if(name == "Blur Emulation") return settings.blurEmulation; + 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 == "Blur Emulation" && value.is()) return settings.blurEmulation = value.get(), true; + 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/sfc/interface/interface.hpp b/higan/sfc/interface/interface.hpp index cd4ffbcb..c804101d 100644 --- a/higan/sfc/interface/interface.hpp +++ b/higan/sfc/interface/interface.hpp @@ -117,11 +117,20 @@ struct Interface : Emulator::Interface { auto cheatSet(const lstring&) -> void; - auto paletteUpdate(PaletteMode mode) -> void; + auto cap(const string& name) -> bool override; + auto get(const string& name) -> any override; + auto set(const string& name, const any& value) -> bool override; vector device; }; +struct Settings { + bool blurEmulation = true; + bool colorEmulation = true; + bool scanlineEmulation = true; +}; + extern Interface* interface; +extern Settings settings; } diff --git a/higan/sfc/ppu/ppu.cpp b/higan/sfc/ppu/ppu.cpp index e0f98dea..5371853b 100644 --- a/higan/sfc/ppu/ppu.cpp +++ b/higan/sfc/ppu/ppu.cpp @@ -19,12 +19,11 @@ bg4(*this, Background::ID::BG4), sprite(*this), window(*this), screen(*this) { - surface = new uint32[512 * 512]; - output = surface + 16 * 512; + output = new uint32[512 * 512]; } PPU::~PPU() { - delete[] surface; + delete[] output; } auto PPU::step(uint clocks) -> void { @@ -110,7 +109,7 @@ auto PPU::power() -> void { auto PPU::reset() -> void { create(Enter, system.cpuFrequency()); PPUcounter::reset(); - memory::fill(surface, 512 * 512 * sizeof(uint32)); + memory::fill(output, 512 * 480 * sizeof(uint32)); mmio_reset(); bg1.reset(); diff --git a/higan/sfc/ppu/ppu.hpp b/higan/sfc/ppu/ppu.hpp index 757548d9..4e688a0f 100644 --- a/higan/sfc/ppu/ppu.hpp +++ b/higan/sfc/ppu/ppu.hpp @@ -27,7 +27,6 @@ privileged: uint ppu1_version = 1; //allowed: 1 uint ppu2_version = 3; //allowed: 1, 2, 3 - uint32* surface = nullptr; uint32* output = nullptr; struct { diff --git a/higan/sfc/ppu/screen/screen.cpp b/higan/sfc/ppu/screen/screen.cpp index d6c66a2e..dd54a370 100644 --- a/higan/sfc/ppu/screen/screen.cpp +++ b/higan/sfc/ppu/screen/screen.cpp @@ -2,8 +2,8 @@ PPU::Screen::Screen(PPU& self) : self(self) { } auto PPU::Screen::scanline() -> void { - output = self.output + self.vcounter() * 1024; - if(self.display.interlace && self.field()) output += 512; + line = self.output + self.vcounter() * 1024; + if(self.display.interlace && self.field()) line += 512; //the first hires pixel of each scanline is transparent //note: exact value initializations are not confirmed on hardware @@ -19,14 +19,14 @@ auto PPU::Screen::scanline() -> void { } auto PPU::Screen::run() -> void { - if(ppu.vcounter() == 0) return; + if(self.vcounter() == 0) return; bool hires = self.regs.pseudo_hires || self.regs.bgmode == 5 || self.regs.bgmode == 6; auto sscolor = get_pixel_sub(hires); auto mscolor = get_pixel_main(); - *output++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor); - *output++ = (self.regs.display_brightness << 15) | (mscolor); + *line++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor); + *line++ = (self.regs.display_brightness << 15) | (mscolor); } auto PPU::Screen::get_pixel_sub(bool hires) -> uint16 { diff --git a/higan/sfc/ppu/screen/screen.hpp b/higan/sfc/ppu/screen/screen.hpp index 821289e5..fb86e56f 100644 --- a/higan/sfc/ppu/screen/screen.hpp +++ b/higan/sfc/ppu/screen/screen.hpp @@ -1,5 +1,5 @@ struct Screen { - uint32* output; + uint32* line; struct Regs { bool addsub_mode; diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index 89f911ce..ba0d4fc0 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -23,7 +23,7 @@ auto System::run() -> void { scheduler.enter(); if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { - video.update(); + video.refresh(); } } @@ -60,7 +60,7 @@ auto System::runThreadToSave() -> void { scheduler.enter(); if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { - video.update(); + video.refresh(); } } } @@ -243,6 +243,7 @@ auto System::reset() -> void { if(cartridge.hasSPC7110()) cpu.coprocessors.append(&spc7110); if(cartridge.hasMSU1()) cpu.coprocessors.append(&msu1); + video.reset(); scheduler.init(); device.connect(0, configuration.controllerPort1); device.connect(1, configuration.controllerPort2); diff --git a/higan/sfc/system/video.cpp b/higan/sfc/system/video.cpp index 92d4f823..dea13c66 100644 --- a/higan/sfc/system/video.cpp +++ b/higan/sfc/system/video.cpp @@ -1,56 +1,211 @@ Video video; Video::Video() { - palette = new uint32[1 << 19](); + output = new uint32[512 * 512](); + paletteStandard = new uint32[1 << 19]; + paletteEmulation = new uint32[1 << 19]; + + output += 16 * 512; //overscan padding } Video::~Video() { - delete[] palette; + output -= 16 * 512; + delete[] output; + delete[] paletteStandard; + delete[] paletteEmulation; } -auto Video::generate_palette(Emulator::Interface::PaletteMode mode) -> void { +auto Video::reset() -> void { + memory::fill(output, 512 * 480 * sizeof(uint32)); //padding area already cleared + for(auto color : range(1 << 19)) { - if(mode == Emulator::Interface::PaletteMode::Literal) { - palette[color] = color; - continue; + uint l = (uint4)(color >> 15); + uint b = (uint5)(color >> 10); + uint g = (uint5)(color >> 5); + uint r = (uint5)(color >> 0); + + double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.5); + + { uint R = L * image::normalize(r, 5, 8); + uint G = L * image::normalize(g, 5, 8); + uint B = L * image::normalize(b, 5, 8); + paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0); } - uint l = (color >> 15) & 15; - uint b = (color >> 10) & 31; - uint g = (color >> 5) & 31; - uint r = (color >> 0) & 31; - - if(mode == Emulator::Interface::PaletteMode::Channel) { - l = image::normalize(l, 4, 16); - r = image::normalize(r, 5, 16); - g = image::normalize(g, 5, 16); - b = image::normalize(b, 5, 16); - palette[color] = interface->videoColor(color, l, r, g, b); - continue; + { uint R = L * gamma_ramp[r]; + uint G = L * gamma_ramp[g]; + uint B = L * gamma_ramp[b]; + paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0); } + } - if(mode == Emulator::Interface::PaletteMode::Emulation) { - r = gamma_ramp[r]; - g = gamma_ramp[g]; - b = gamma_ramp[b]; - } else { - r = image::normalize(r, 5, 8); - g = image::normalize(g, 5, 8); - b = image::normalize(b, 5, 8); - } - - double L = (1.0 + l) / 16.0; - if(l == 0) L *= 0.5; - uint R = L * image::normalize(r, 8, 16); - uint G = L * image::normalize(g, 8, 16); - uint B = L * image::normalize(b, 8, 16); - - palette[color] = interface->videoColor(color, 0, R, G, B); + for(auto color : range(1 << 19)) { + uint l = (uint4)(color >> 15); + uint b = (uint5)(color >> 10); + uint g = (uint5)(color >> 5); + uint r = (uint5)(color >> 0); } } +auto Video::refresh() -> void { + auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard; + + if(settings.scanlineEmulation) { + for(uint y = 0; y < 240; y++) { + auto sourceLo = ppu.output + y * 1024; + auto sourceHi = ppu.output + y * 1024 + 512; + auto targetLo = output + y * 1024; + auto targetHi = output + y * 1024 + 512; + if(!ppu.interlace()) { + for(uint x = 0; x < 512; x++) { + auto color = palette[*sourceLo++]; + *targetLo++ = color; + *targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1); + } + } else if(!ppu.field()) { + for(uint x = 0; x < 512; x++) { + auto color = palette[*sourceHi++]; + *targetLo++ = palette[*sourceLo++]; + *targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1); + } + } else { + for(uint x = 0; x < 512; x++) { + auto color = palette[*sourceLo++]; + *targetLo++ = (255 << 24) | ((color & 0xfefefe) >> 1); + *targetHi++ = palette[*sourceHi++]; + } + } + } + } else { + for(uint y = 0; y < 240; y++) { + auto sourceLo = ppu.output + y * 1024; + auto sourceHi = ppu.output + y * 1024 + 512; + auto targetLo = output + y * 1024; + auto targetHi = output + y * 1024 + 512; + if(!ppu.interlace()) { + for(uint x = 0; x < 512; x++) { + auto color = palette[*sourceLo++]; + *targetLo++ = color; + *targetHi++ = color; + } + } else { + for(uint x = 0; x < 512; x++) { + *targetLo++ = palette[*sourceLo++]; + *targetHi++ = palette[*sourceHi++]; + } + } + } + } + + if(settings.blurEmulation) { + for(uint y = 0; y < 480; y++) { + auto target = output + y * 512; + for(uint x = 0; x < 512; x++) { + auto a = target[x]; + auto b = target[x + (x != 511)]; + target[x] = (a + b - ((a ^ b) & 0x01010101)) >> 1; + } + } + } + + 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 +auto Video::drawCursor(uint32 color, int x, int y) -> void { + auto data = (uint32*)output; + if(ppu.interlace() && ppu.field()) data += 512; + + for(int cy = 0; cy < 15; cy++) { + 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 + uint8 pixel = cursor[cy * 15 + cx]; + 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; + } + } + } +} + +auto Video::drawCursors() -> void { + switch(configuration.controllerPort2) { + case Device::ID::SuperScope: + if(dynamic_cast(device.controllerPort2)) { + auto& controller = (SuperScope&)*device.controllerPort2; + drawCursor(0xff0000ff, controller.x, controller.y); + } + break; + case Device::ID::Justifier: + case Device::ID::Justifiers: + if(dynamic_cast(device.controllerPort2)) { + auto& controller = (Justifier&)*device.controllerPort2; + drawCursor(0xffff0000, controller.player1.x, controller.player1.y); + if(!controller.chained) break; + drawCursor(0xff00bf00, controller.player2.x, controller.player2.y); + } + break; + } +} + +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] = { 0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c, 0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78, @@ -75,89 +230,3 @@ const uint8 Video::cursor[15 * 15] = { 0,0,0,0,1,1,2,2,2,1,1,0,0,0,0, 0,0,0,0,0,0,1,1,1,0,0,0,0,0,0, }; - -auto Video::draw_cursor(uint16 color, int x, int y) -> void { - uint32* data = (uint32*)ppu.output; - if(ppu.interlace() && ppu.field()) data += 512; - - for(int cy = 0; cy < 15; cy++) { - 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 - uint8 pixel = cursor[cy * 15 + cx]; - if(pixel == 0) continue; - uint32 pixelcolor = (15 << 15) | ((pixel == 1) ? 0 : color); - - if(hires == false) { - *((uint32*)data + vy * 1024 + vx) = pixelcolor; - } else { - *((uint32*)data + vy * 1024 + vx * 2 + 0) = pixelcolor; - *((uint32*)data + vy * 1024 + vx * 2 + 1) = pixelcolor; - } - } - } -} - -auto Video::update() -> void { - switch(configuration.controllerPort2) { - case Device::ID::SuperScope: - if(dynamic_cast(device.controllerPort2)) { - auto& controller = (SuperScope&)*device.controllerPort2; - draw_cursor(0x7c00, controller.x, controller.y); - } - break; - case Device::ID::Justifier: - case Device::ID::Justifiers: - if(dynamic_cast(device.controllerPort2)) { - auto& controller = (Justifier&)*device.controllerPort2; - draw_cursor(0x001f, controller.player1.x, controller.player1.y); - if(!controller.chained) break; - draw_cursor(0x02e0, controller.player2.x, controller.player2.y); - } - break; - } - - auto data = (uint32*)ppu.output; - if(ppu.interlace() && ppu.field()) data += 512; - - if(hires) { - //normalize line widths - for(unsigned y = 0; y < 240; y++) { - if(line_width[y] == 512) continue; - uint32* buffer = data + y * 1024; - for(signed 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( - video.palette, - ppu.output - (ppu.overscan() ? 0 : 7 * 1024), - 4 * (1024 >> ppu.interlace()), - 256 << hires, - 240 << ppu.interlace() - ); - - hires = false; -} - -auto Video::scanline() -> void { - uint y = cpu.vcounter(); - if(y >= 240) return; - - hires |= ppu.hires(); - uint width = (ppu.hires() == false ? 256 : 512); - line_width[y] = width; -} - -auto Video::init() -> void { - hires = false; - for(auto& n : line_width) n = 256; -} diff --git a/higan/sfc/system/video.hpp b/higan/sfc/system/video.hpp index 76e8074b..4ed63209 100644 --- a/higan/sfc/system/video.hpp +++ b/higan/sfc/system/video.hpp @@ -1,9 +1,13 @@ struct Video { Video(); ~Video(); - auto generate_palette(Emulator::Interface::PaletteMode mode) -> void; - uint32_t* palette = nullptr; + auto reset() -> void; + auto refresh() -> void; + + uint32* output = nullptr; + uint32* paletteStandard = nullptr; + uint32* paletteEmulation = nullptr; private: bool hires; @@ -13,9 +17,11 @@ private: 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 cursor[15 * 15]; - auto draw_cursor(uint16 color, int x, int y) -> void; friend class System; }; diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index 05798755..d6c9ea77 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -1,4 +1,4 @@ -name := tomoko +name := higan processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050 include processor/GNUmakefile @@ -68,12 +68,12 @@ obj/ui-resource.o: build: $(objects) $(strip $(compiler) -o out/$(name) $(objects) $(link)) ifeq ($(platform),macosx) - @if [ -d out/higan.app ]; then rm -r out/higan.app; fi - mkdir -p out/higan.app/Contents/MacOS/ - mkdir -p out/higan.app/Contents/Resources/ - mv out/$(name) out/higan.app/Contents/MacOS/higan - cp data/higan.plist out/higan.app/Contents/Info.plist - sips -s format icns data/higan.png --out out/higan.app/Contents/Resources/higan.icns + @if [ -d out/$(name).app ]; then rm -r out/$(name).app; fi + mkdir -p out/$(name).app/Contents/MacOS/ + mkdir -p out/$(name).app/Contents/Resources/ + mv out/$(name) out/$(name).app/Contents/MacOS/$(name) + cp data/$(name).plist out/$(name).app/Contents/Info.plist + sips -s format icns data/$(name).png --out out/$(name).app/Contents/Resources/$(name).icns endif install: @@ -83,18 +83,18 @@ else ifeq ($(platform),windows) else ifeq ($(platform),macosx) mkdir -p ~/Library/Application\ Support/$(name)/ mkdir -p ~/Emulation/System/ - cp -R out/higan.app /Applications/higan.app + cp -R out/$(name).app /Applications/$(name).app cp data/cheats.bml ~/Library/Application\ Support/$(name)/ - cp -R profile/* ~/Emulation/System/ -else + cp -R profile/* ~/Library/Application\ Support/$(name)/ +else ifneq ($(filter $(platform),linux bsd),) mkdir -p $(prefix)/bin/ mkdir -p $(prefix)/share/icons/ mkdir -p $(prefix)/$(name)/ mkdir -p ~/Emulation/System/ cp out/$(name) $(prefix)/bin/$(name) - cp data/higan.png $(prefix)/share/icons/$(name).png + cp data/$(name).png $(prefix)/share/icons/$(name).png cp data/cheats.bml $(prefix)/$(name)/cheats.bml - cp -R profile/* ~/Emulation/System/ + cp -R profile/* $(prefix)/$(name)/ endif uninstall: @@ -102,8 +102,8 @@ ifeq ($(shell id -un),root) $(error "make uninstall should not be run as root") else ifeq ($(platform),windows) else ifeq ($(platform),macosx) - if [ -d /Applications/higan.app ]; then rm -r /Applications/higan.app; fi -else + if [ -d /Applications/$(name).app ]; then rm -r /Applications/$(name).app; fi +else ifneq ($(filter $(platform),linux bsd),) if [ -f $(prefix)/bin/$(name) ]; then rm $(prefix)/bin/$(name); fi if [ -f $(prefix)/share/icons/$(name).png ]; then rm $(prefix)/share/icons/$(name).png; fi endif diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index 451a8b5a..7da17ed6 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -2,7 +2,7 @@ Settings settings; Settings::Settings() { - Markup::Node::operator=(BML::unserialize(string::read(locate({configpath(), "tomoko/"}, "settings.bml")))); + Markup::Node::operator=(BML::unserialize(string::read(locate({localpath(), "higan/"}, "settings.bml")))); auto set = [&](const string& name, const string& value) { //create node and set to default value only if it does not already exist @@ -20,7 +20,9 @@ Settings::Settings() { set("Video/AspectCorrection", true); set("Video/Filter", "Blur"); set("Video/Shader", "None"); + set("Video/BlurEmulation", true); set("Video/ColorEmulation", true); + set("Video/ScanlineEmulation", true); set("Video/Saturation", 100); set("Video/Gamma", 100); set("Video/Luminance", 100); @@ -45,5 +47,5 @@ Settings::Settings() { } auto Settings::quit() -> void { - file::write(locate({configpath(), "tomoko/"}, "settings.bml"), BML::serialize(*this)); + file::write(locate({localpath(), "higan/"}, "settings.bml"), BML::serialize(*this)); } diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 3f675f57..53906d1b 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -74,9 +74,17 @@ Presentation::Presentation() { settings["Video/Filter"].setValue("Blur"); program->updateVideoFilter(); }); + blurEmulation.setText("Blur Emulation").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([&] { settings["Video/ColorEmulation"].setValue(colorEmulation.checked()); - program->updateVideoPalette(); + if(emulator) emulator->set("Color Emulation", colorEmulation.checked()); + }); + scanlineEmulation.setText("Scanline Emulation").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()); @@ -131,6 +139,8 @@ Presentation::Presentation() { statusBar.setFont(Font().setBold()); statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); + viewport.setDroppable().onDrop([&](auto locations) { program->load(locations(0)); }); + onClose([&] { program->quit(); }); setTitle({"higan v", Emulator::Version}); @@ -187,6 +197,10 @@ auto Presentation::updateEmulator() -> void { } systemMenuSeparatorPorts.setVisible(inputPort1.visible() || inputPort2.visible() || inputPort3.visible()); + + emulator->set("Blur Emulation", blurEmulation.checked()); + emulator->set("Color Emulation", colorEmulation.checked()); + emulator->set("Scanline Emulation", scanlineEmulation.checked()); } auto Presentation::resizeViewport() -> void { @@ -261,7 +275,7 @@ auto Presentation::loadShaders() -> void { return; } - auto pathname = locate({localpath(), "tomoko/"}, "Video Shaders/"); + auto pathname = locate({localpath(), "higan/"}, "Video Shaders/"); for(auto shader : directory::folders(pathname, "*.shader")) { MenuRadioItem item{&videoShaderMenu}; item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] { diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index b8c9ba30..d40b69ad 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -32,7 +32,9 @@ struct Presentation : Window { MenuRadioItem videoFilterBlur{&videoFilterMenu}; Group videoFilters{&videoFilterNone, &videoFilterBlur}; MenuSeparator videoFilterSeparator{&videoFilterMenu}; + MenuCheckItem blurEmulation{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu}; + MenuCheckItem scanlineEmulation{&videoFilterMenu}; MenuCheckItem maskOverscan{&videoFilterMenu}; Menu videoShaderMenu{&settingsMenu}; MenuRadioItem videoShaderNone{&videoShaderMenu}; diff --git a/higan/target-tomoko/program/interface.cpp b/higan/target-tomoko/program/interface.cpp index 6f8773ed..be612b95 100644 --- a/higan/target-tomoko/program/interface.cpp +++ b/higan/target-tomoko/program/interface.cpp @@ -45,39 +45,7 @@ auto Program::saveRequest(uint id, string filename) -> void { return emulator->save(id, stream); } -auto Program::videoColor(uint source, uint16 a, uint16 r, uint16 g, uint16 b) -> uint32 { - if(settings["Video/Saturation"].natural() != 100) { - uint16 grayscale = uclamp<16>((r + g + b) / 3); - double saturation = settings["Video/Saturation"].natural() * 0.01; - double inverse = max(0.0, 1.0 - saturation); - r = uclamp<16>(r * saturation + grayscale * inverse); - g = uclamp<16>(g * saturation + grayscale * inverse); - b = uclamp<16>(b * saturation + grayscale * inverse); - } - - if(settings["Video/Gamma"].natural() != 100) { - double exponent = settings["Video/Gamma"].natural() * 0.01; - double reciprocal = 1.0 / 32767.0; - r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent); - g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent); - b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent); - } - - if(settings["Video/Luminance"].natural() != 100) { - double luminance = settings["Video/Luminance"].natural() * 0.01; - r = r * luminance; - g = g * luminance; - b = b * luminance; - } - - a >>= 8; - r >>= 8; - g >>= 8; - b >>= 8; - return a << 24 | r << 16 | g << 8 | b << 0; -} - -auto Program::videoRefresh(const uint32* palette, const uint32* data, uint pitch, uint width, uint height) -> void { +auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { uint32* output; uint length; @@ -88,7 +56,7 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, uint pitch const uint32* sp = data + y * pitch; uint32* dp = output + y * length; for(auto x : range(width)) { - *dp++ = palette[*sp++]; + *dp++ = *sp++; } } diff --git a/higan/target-tomoko/program/media.cpp b/higan/target-tomoko/program/media.cpp index 8442535e..a3d3276e 100644 --- a/higan/target-tomoko/program/media.cpp +++ b/higan/target-tomoko/program/media.cpp @@ -16,13 +16,12 @@ auto Program::loadMedia(string location) -> void { auto Program::loadMedia(Emulator::Interface& emulator_, Emulator::Interface::Media& media, const string& location) -> void { unloadMedia(); - mediaPaths(0) = locate({settings["Library/Location"].text(), "System/"}, {media.name, ".sys/"}); + mediaPaths(0) = locate({localpath(), "higan/"}, {media.name, ".sys/"}); mediaPaths(media.id) = location; folderPaths.append(location); emulator = &emulator_; emulator->load(media.id); - updateVideoPalette(); dsp.setFrequency(emulator->audioFrequency()); emulator->power(); diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 87422f6d..cce4e59b 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -11,7 +11,7 @@ Program* program = nullptr; Program::Program(lstring args) { program = this; - directory::create({configpath(), "tomoko/"}); + directory::create({localpath(), "tomoko/"}); Application::onMain({&Program::main, this}); Application::Windows::onModalChange([](bool modal) { if(modal && audio) audio->clear(); }); @@ -72,14 +72,27 @@ Program::Program(lstring args) { if(argument == "--fullscreen") { presentation->toggleFullScreen(); } else { - auto location = argument; - if(directory::exists(location)) { - loadMedia(location); - } else if(file::exists(location)) { - if(auto result = execute("icarus", "--import", location)) { - loadMedia(result.strip()); - } + load(argument); + } + } +} + +auto Program::load(string location) -> void { + if(directory::exists(location)) { + loadMedia(location); + } else if(file::exists(location)) { + //special handling to allow importing the Game Boy Advance BIOS + if(file::size(location) == 16384 && file::sha256(location).beginsWith("fd2547724b505f48")) { + auto target = locate({localpath(), "higan/"}, "Game Boy Advance.sys/"); + if(file::copy(location, {target, "bios.rom"})) { + MessageDialog().setTitle(Emulator::Name).setText("Game Boy Advance BIOS imported successfully!").information(); } + return; + } + + //ask icarus to import the game; and play it upon success + if(auto result = execute("icarus", "--import", location)) { + loadMedia(result.strip()); } } } diff --git a/higan/target-tomoko/program/program.hpp b/higan/target-tomoko/program/program.hpp index 7a630f16..9033c311 100644 --- a/higan/target-tomoko/program/program.hpp +++ b/higan/target-tomoko/program/program.hpp @@ -1,6 +1,7 @@ struct Program : Emulator::Interface::Bind { //program.cpp Program(lstring args); + auto load(string) -> void; auto main() -> void; auto quit() -> void; @@ -8,8 +9,7 @@ struct Program : Emulator::Interface::Bind { auto loadRequest(uint id, string name, string type, bool required) -> void override; auto loadRequest(uint id, string path, bool required) -> void override; auto saveRequest(uint id, string path) -> void override; - auto videoColor(uint source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 override; - auto videoRefresh(const uint32* palette, const uint32* data, uint pitch, uint width, uint height) -> void override; + auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override; auto audioSample(int16 lsample, int16 rsample) -> void override; auto inputPoll(uint port, uint device, uint input) -> int16 override; auto inputRumble(uint port, uint device, uint input, bool enable) -> void override; @@ -33,7 +33,6 @@ struct Program : Emulator::Interface::Bind { auto showMessage(const string& text) -> void; auto updateStatusText() -> void; auto updateVideoFilter() -> void; - auto updateVideoPalette() -> 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 331eefc0..03ba5095 100644 --- a/higan/target-tomoko/program/utility.cpp +++ b/higan/target-tomoko/program/utility.cpp @@ -48,14 +48,6 @@ auto Program::updateVideoFilter() -> void { } } -auto Program::updateVideoPalette() -> void { - if(!emulator) return; - emulator->paletteUpdate(settings["Video/ColorEmulation"].boolean() - ? Emulator::Interface::PaletteMode::Emulation - : Emulator::Interface::PaletteMode::Standard - ); -} - auto Program::updateAudio() -> void { if(!audio) return; audio->clear(); diff --git a/higan/target-tomoko/settings/hotkeys.cpp b/higan/target-tomoko/settings/hotkeys.cpp index da2bfff4..030e5e55 100644 --- a/higan/target-tomoko/settings/hotkeys.cpp +++ b/higan/target-tomoko/settings/hotkeys.cpp @@ -73,6 +73,6 @@ auto HotkeySettings::inputEvent(shared_pointer device, uint group, timer.setEnabled(false); settingsManager->statusBar.setText(); settingsManager->layout.setEnabled(); - }).setInterval(1000).setEnabled(); + }).setInterval(200).setEnabled(); } } diff --git a/higan/target-tomoko/settings/input.cpp b/higan/target-tomoko/settings/input.cpp index 1931668f..ebafdb90 100644 --- a/higan/target-tomoko/settings/input.cpp +++ b/higan/target-tomoko/settings/input.cpp @@ -145,6 +145,6 @@ auto InputSettings::inputEvent(shared_pointer device, uint group, u timer.setEnabled(false); settingsManager->statusBar.setText(); settingsManager->layout.setEnabled(); - }).setInterval(1000).setEnabled(); + }).setInterval(200).setEnabled(); } } diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 21efb622..0fc6380a 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -2,19 +2,6 @@ struct VideoSettings : TabFrameItem { VideoSettings(TabFrame*); VerticalLayout layout{this}; - Label colorAdjustmentLabel{&layout, Size{~0, 0}}; - HorizontalLayout saturationLayout{&layout, Size{~0, 0}}; - Label saturationLabel{&saturationLayout, Size{80, 0}}; - Label saturationValue{&saturationLayout, Size{80, 0}}; - HorizontalSlider saturationSlider{&saturationLayout, Size{~0, 0}}; - HorizontalLayout gammaLayout{&layout, Size{~0, 0}}; - Label gammaLabel{&gammaLayout, Size{80, 0}}; - Label gammaValue{&gammaLayout, Size{80, 0}}; - HorizontalSlider gammaSlider{&gammaLayout, Size{~0, 0}}; - HorizontalLayout luminanceLayout{&layout, Size{~0, 0}}; - Label luminanceLabel{&luminanceLayout, Size{80, 0}}; - Label luminanceValue{&luminanceLayout, Size{80, 0}}; - HorizontalSlider luminanceSlider{&luminanceLayout, Size{~0, 0}}; Label overscanMaskLabel{&layout, Size{~0, 0}}; HorizontalLayout horizontalMaskLayout{&layout, Size{~0, 0}}; Label horizontalMaskLabel{&horizontalMaskLayout, Size{80, 0}}; diff --git a/higan/target-tomoko/settings/video.cpp b/higan/target-tomoko/settings/video.cpp index 5ca392fa..fc3ce544 100644 --- a/higan/target-tomoko/settings/video.cpp +++ b/higan/target-tomoko/settings/video.cpp @@ -4,14 +4,6 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { layout.setMargin(5); - colorAdjustmentLabel.setFont(Font().setBold()).setText("Color Adjustment"); - saturationLabel.setText("Saturation:"); - saturationSlider.setLength(201).setPosition(settings["Video/Saturation"].natural()).onChange([&] { update(); }); - gammaLabel.setText("Gamma:"); - gammaSlider.setLength(101).setPosition(settings["Video/Gamma"].natural() - 100).onChange([&] { update(); }); - luminanceLabel.setText("Luminance:"); - luminanceSlider.setLength(101).setPosition(settings["Video/Luminance"].natural()).onChange([&] { update(); }); - overscanMaskLabel.setFont(Font().setBold()).setText("Overscan Mask"); horizontalMaskLabel.setText("Horizontal:"); horizontalMaskSlider.setLength(17).setPosition(settings["Video/Overscan/Horizontal"].natural()).onChange([&] { update(); }); @@ -22,15 +14,8 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { } auto VideoSettings::update() -> void { - settings["Video/Saturation"].setValue(saturationSlider.position()); - settings["Video/Gamma"].setValue(100 + gammaSlider.position()); - settings["Video/Luminance"].setValue(luminanceSlider.position()); settings["Video/Overscan/Horizontal"].setValue(horizontalMaskSlider.position()); settings["Video/Overscan/Vertical"].setValue(verticalMaskSlider.position()); - saturationValue.setText({saturationSlider.position(), "%"}); - gammaValue.setText({100 + gammaSlider.position(), "%"}); - luminanceValue.setText({luminanceSlider.position(), "%"}); horizontalMaskValue.setText({horizontalMaskSlider.position(), "px"}); verticalMaskValue.setText({verticalMaskSlider.position(), "px"}); - program->updateVideoPalette(); } diff --git a/higan/target-tomoko/tomoko.cpp b/higan/target-tomoko/tomoko.cpp index 88d77c99..ae916f07 100644 --- a/higan/target-tomoko/tomoko.cpp +++ b/higan/target-tomoko/tomoko.cpp @@ -14,7 +14,7 @@ auto locate(string pathname, string filename) -> string { #include auto nall::main(lstring args) -> void { - Application::setName("tomoko"); + Application::setName("higan"); new Program(args); Application::run(); } diff --git a/higan/target-tomoko/tools/cheat-database.cpp b/higan/target-tomoko/tools/cheat-database.cpp index 5c4cad9a..55caeed9 100644 --- a/higan/target-tomoko/tools/cheat-database.cpp +++ b/higan/target-tomoko/tools/cheat-database.cpp @@ -20,7 +20,7 @@ auto CheatDatabase::findCodes() -> void { if(!emulator) return; auto sha256 = emulator->sha256(); - auto contents = string::read(locate({localpath(), "tomoko/"}, "cheats.bml")); + auto contents = string::read(locate({localpath(), "higan/"}, "cheats.bml")); auto document = BML::unserialize(contents); for(auto cartridge : document.find("cartridge")) { diff --git a/nall/file.hpp b/nall/file.hpp index b659a245..6049b247 100644 --- a/nall/file.hpp +++ b/nall/file.hpp @@ -16,6 +16,7 @@ struct file : file_system_object, varint { enum class index : uint { absolute, relative }; static auto copy(const string& sourcename, const string& targetname) -> bool { + if(sourcename == targetname) return true; file rd, wr; if(rd.open(sourcename, mode::read) == false) return false; if(wr.open(targetname, mode::write) == false) return false; @@ -26,6 +27,7 @@ struct file : file_system_object, varint { //attempt to rename file first //this will fail if paths point to different file systems; fall back to copy+remove in this case static auto move(const string& sourcename, const string& targetname) -> bool { + if(sourcename == targetname) return true; if(rename(sourcename, targetname)) return true; if(!writable(sourcename)) return false; if(copy(sourcename, targetname)) {