Update to v096r07 release.

byuu says:

Changelog:
- configuration files are now stored in localpath() instead of configpath()
- Video gamma/saturation/luminance sliders are gone now, sorry
- added Video Filter->Blur Emulation [1]
- added Video Filter->Scanline Emulation [2]
- improvements to GBA audio emulation (fixes Minish Cap) [Jonas Quinn]

[1] For the Famicom, this does nothing. For the Super Famicom, this
performs horizontal blending for proper pseudo-hires translucency. For
the Game Boy, Game Boy Color, and Game Boy Advance, this performs
interframe blending (each frame is the average of the current and
previous frame), which is important for things like the GBVideoPlayer.

[2] Right now, this only applies to the Super Famicom, but it'll come to
the Famicom in the future. For the Super Famicom, this option doesn't
just add scanlines, it simulates the phosphor decay that's visible in
interlace mode. If you observe an interlaced game like RPM Racing on
a real SNES, you'll notice that even on perfectly still screens, the
image appears to shake. This option emulates that effect.

Note 1: the buffering right now is a little sub-optimal, so there will
be a slight speed hit with this new support. Since the core is now
generating native ARGB8888 colors, it might as well call out to the
interface to lock/unlock/refresh the video, that way it can render
directly to the screen. Although ... that might not be such a hot idea,
since the GBx interframe blending reads from the target buffer, and that
tends to be a catastrophic option for performance.

Note 2: the balanced and performance profiles for the SNES are
completely busted again. This WIP took 6 1/2 hours, and I'm exhausted.
Very much not looking forward to working on those, since those two have
all kinds of fucked up speedup tricks for non-interlaced and/or
non-hires video modes.

Note 3: if you're on Windows and you saved your system folders somewhere
else, now'd be a good time to move them to %localappdata%/higan
This commit is contained in:
Tim Allen 2016-01-15 21:06:51 +11:00
parent 3414c8c8df
commit cec33c1d0f
48 changed files with 574 additions and 443 deletions

View File

@ -35,12 +35,7 @@ ifeq ($(platform),windows)
link += -Wl,-enable-runtime-pseudo-reloc link += -Wl,-enable-runtime-pseudo-reloc
else ifeq ($(platform),macosx) else ifeq ($(platform),macosx)
flags += -march=native flags += -march=native
else ifeq ($(platform),linux) else ifneq ($(filter $(platform),linux bsd),)
flags += -march=native -fopenmp
link += -fopenmp
link += -Wl,-export-dynamic
link += -lX11 -lXext
else ifeq ($(platform),bsd)
flags += -march=native -fopenmp flags += -march=native -fopenmp
link += -fopenmp link += -fopenmp
link += -Wl,-export-dynamic link += -Wl,-export-dynamic

View File

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

View File

@ -49,8 +49,7 @@ struct Interface {
virtual auto loadRequest(uint, string, string, bool) -> void {} virtual auto loadRequest(uint, string, string, bool) -> void {}
virtual auto loadRequest(uint, string, bool) -> void {} virtual auto loadRequest(uint, string, bool) -> void {}
virtual auto saveRequest(uint, string) -> void {} virtual auto saveRequest(uint, string) -> void {}
virtual auto videoColor(uint, uint16, uint16, uint16, uint16) -> uint32 { return 0u; } virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
virtual auto videoRefresh(const uint32*, const uint32*, uint, uint, uint) -> void {}
virtual auto audioSample(int16, int16) -> void {} virtual auto audioSample(int16, int16) -> void {}
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; } virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
virtual auto inputRumble(uint, uint, uint, bool) -> void {} 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 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 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 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* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
auto videoRefresh(const uint32* palette, const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(palette, data, pitch, width, height); }
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); } 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 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); } 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 //cheat functions
virtual auto cheatSet(const lstring& = lstring{}) -> void {} virtual auto cheatSet(const lstring& = lstring{}) -> void {}
//utility functions //settings
enum class PaletteMode : uint { Literal, Channel, Standard, Emulation }; virtual auto cap(const string& name) -> bool { return false; }
virtual auto paletteUpdate(PaletteMode mode) -> void {} virtual auto get(const string& name) -> any { return {}; }
virtual auto set(const string& name, const any& value) -> bool { return false; }
}; };
} }

View File

@ -3,6 +3,7 @@
namespace Famicom { namespace Famicom {
Interface* interface = nullptr; Interface* interface = nullptr;
Settings settings;
Interface::Interface() { Interface::Interface() {
interface = this; interface = this;
@ -166,8 +167,19 @@ auto Interface::cheatSet(const lstring& list) -> void {
} }
} }
auto Interface::paletteUpdate(PaletteMode mode) -> void { auto Interface::cap(const string& name) -> bool {
video.generate_palette(mode); 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<bool>()) return settings.colorEmulation = value.get<bool>(), true;
return false;
} }
} }

View File

@ -48,12 +48,19 @@ struct Interface : Emulator::Interface {
auto cheatSet(const lstring&) -> void; 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: private:
vector<Device> device; vector<Device> device;
}; };
struct Settings {
bool colorEmulation = true;
};
extern Interface* interface; extern Interface* interface;
extern Settings settings;
} }

View File

@ -8,7 +8,7 @@ System system;
auto System::run() -> void { auto System::run() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { 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(); scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { 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(); ppu.reset();
input.reset(); input.reset();
scheduler.reset(); scheduler.reset();
video.reset();
} }
auto System::init() -> void { auto System::init() -> void {

View File

@ -7,34 +7,41 @@ namespace Famicom {
Video video; Video video;
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() { 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)) { for(auto color : range(1 << 9)) {
if(mode == Emulator::Interface::PaletteMode::Literal) { paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
palette[color] = color; paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8);
} 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);
}
} }
} }
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, uint n, double saturation, double hue,
double contrast, double brightness, double gamma double contrast, double brightness, double gamma
) -> uint32 { ) -> uint32 {
@ -75,11 +82,11 @@ auto Video::generate_color(
q *= saturation; q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); }; 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 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
uint g = 65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q); uint g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
uint b = 65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * 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);
} }
} }

View File

@ -2,12 +2,15 @@ struct Video {
Video(); 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: private:
auto generate_color(uint, double, double, double, double, double) -> uint32; auto generateColor(uint, double, double, double, double, double) -> uint32;
}; };
extern Video video; extern Video video;

View File

@ -3,6 +3,7 @@
namespace GameBoy { namespace GameBoy {
Interface* interface = nullptr; Interface* interface = nullptr;
Settings settings;
Interface::Interface() { Interface::Interface() {
interface = this; 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 { auto Interface::lcdScanline() -> void {
if(hook) hook->lcdScanline(); if(hook) hook->lcdScanline();
} }
@ -183,4 +180,22 @@ auto Interface::joypWrite(bool p15, bool p14) -> void {
if(hook) hook->joypWrite(p15, p14); 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<bool>()) return settings.blurEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
return false;
}
} }

View File

@ -50,7 +50,9 @@ struct Interface : Emulator::Interface {
auto cheatSet(const lstring&) -> void; 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 //Super Game Boy bindings
struct Hook { struct Hook {
@ -68,6 +70,12 @@ private:
vector<Device> device; vector<Device> device;
}; };
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
};
extern Interface* interface; extern Interface* interface;
extern Settings settings;
} }

View File

@ -160,7 +160,7 @@ auto PPU::power() -> void {
status.obpi_increment = 0; status.obpi_increment = 0;
status.obpi = 0; status.obpi = 0;
for(auto& n : screen) n = 0x0000; for(auto& n : screen) n = 0;
bg.color = 0; bg.color = 0;
bg.palette = 0; bg.palette = 0;

View File

@ -16,7 +16,7 @@ auto System::run() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { 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(); scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { 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(); cpu.power();
ppu.power(); ppu.power();
apu.power(); apu.power();
video.power();
scheduler.init(); scheduler.init();
clocks_executed = 0; clocks_executed = 0;

View File

@ -5,89 +5,84 @@ namespace GameBoy {
Video video; Video video;
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() { Video::~Video() {
delete[] palette; delete[] output;
delete[] paletteStandard;
delete[] paletteEmulation;
} }
auto Video::generate_palette(Emulator::Interface::PaletteMode mode) -> void { auto Video::power() -> void {
this->mode = mode; memory::fill(output, 160 * 144 * sizeof(uint32));
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.dmg()) {
if(system.cgb()) for(auto n : range(1 << 15)) palette[n] = paletteCGB(n); 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 { auto Video::refresh() -> void {
if(mode == Emulator::Interface::PaletteMode::Literal) { auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
return color;
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) { interface->videoRefresh(output, 4 * 160, 160, 144);
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;
} }
#define DMG_PALETTE_GREEN #define DMG_PALETTE_GREEN

View File

@ -2,16 +2,15 @@ struct Video {
Video(); 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: private:
Emulator::Interface::PaletteMode mode;
static const uint16 monochrome[4][3]; 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; extern Video video;

View File

@ -125,7 +125,6 @@ struct Sequencer {
uint3 rvolume; uint3 rvolume;
uint1 lenable[4]; uint1 lenable[4];
uint1 renable[4]; uint1 renable[4];
uint1 enable[4];
uint1 masterenable; uint1 masterenable;
uint12 base; uint12 base;

View File

@ -20,10 +20,10 @@ auto APU::runsequencer() -> void {
} }
r.base++; r.base++;
if(r.enable[0]) square1.run(); if(square1.enable) square1.run();
if(r.enable[1]) square2.run(); if(square2.enable) square2.run();
if(r.enable[2]) wave.run(); if(wave.enable) wave.run();
if(r.enable[3]) noise.run(); if(noise.enable) noise.run();
} }
auto APU::Sequencer::read(uint addr) const -> uint8 { auto APU::Sequencer::read(uint addr) const -> uint8 {
@ -39,7 +39,13 @@ auto APU::Sequencer::read(uint addr) const -> uint8 {
| (lenable[2] << 6) | (lenable[2] << 6)
| (lenable[3] << 7) | (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; break;
case 2: //NR52 case 2: //NR52
enable[0] = byte >> 0;
enable[1] = byte >> 1;
enable[2] = byte >> 2;
enable[3] = byte >> 3;
masterenable = byte >> 7; masterenable = byte >> 7;
break; break;
} }
@ -76,7 +78,6 @@ auto APU::Sequencer::power() -> void {
rvolume = 0; rvolume = 0;
for(auto& n : lenable) n = 0; for(auto& n : lenable) n = 0;
for(auto& n : renable) n = 0; for(auto& n : renable) n = 0;
for(auto& n : enable) n = 0;
masterenable = 0; masterenable = 0;
base = 0; base = 0;
step = 0; step = 0;

View File

@ -86,7 +86,6 @@ auto APU::serialize(serializer& s) -> void {
s.integer(sequencer.rvolume); s.integer(sequencer.rvolume);
for(auto& flag : sequencer.lenable) s.integer(flag); for(auto& flag : sequencer.lenable) s.integer(flag);
for(auto& flag : sequencer.renable) 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.masterenable);
s.integer(sequencer.base); s.integer(sequencer.base);
s.integer(sequencer.step); s.integer(sequencer.step);

View File

@ -3,6 +3,7 @@
namespace GameBoyAdvance { namespace GameBoyAdvance {
Interface* interface = nullptr; Interface* interface = nullptr;
Settings settings;
Interface::Interface() { Interface::Interface() {
interface = this; interface = this;
@ -153,8 +154,22 @@ auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s); return system.unserialize(s);
} }
auto Interface::paletteUpdate(PaletteMode mode) -> void { auto Interface::cap(const string& name) -> bool {
video.generatePalette(mode); 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<bool>()) return settings.blurEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
return false;
} }
} }

View File

@ -45,12 +45,20 @@ struct Interface : Emulator::Interface {
auto serialize() -> serializer; auto serialize() -> serializer;
auto unserialize(serializer&) -> bool; 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: private:
vector<Device> device; vector<Device> device;
}; };
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
};
extern Interface* interface; extern Interface* interface;
extern Settings settings;
} }

View File

@ -20,6 +20,7 @@ auto System::power() -> void {
ppu.power(); ppu.power();
apu.power(); apu.power();
cartridge.power(); cartridge.power();
video.power();
scheduler.power(); scheduler.power();
} }
@ -39,7 +40,7 @@ auto System::run() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break; if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break;
} }
interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160); video.refresh();
} }
auto System::runtosave() -> void { auto System::runtosave() -> void {
@ -62,7 +63,7 @@ auto System::runthreadtosave() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) { if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160); video.refresh();
} }
} }
} }

View File

@ -5,62 +5,65 @@ namespace GameBoyAdvance {
Video video; Video video;
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() { 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)) { for(auto color : range(1 << 15)) {
if(mode == Emulator::Interface::PaletteMode::Literal) { uint B = (uint5)(color >> 10);
palette[color] = color; uint G = (uint5)(color >> 5);
continue; 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; { double lcdGamma = 4.0, outGamma = 2.2;
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 lb = pow(B / 31.0, lcdGamma); double lb = pow(B / 31.0, lcdGamma);
double lg = pow(G / 31.0, lcdGamma); double lg = pow(G / 31.0, lcdGamma);
double lr = pow(R / 31.0, lcdGamma); double lr = pow(R / 31.0, lcdGamma);
B = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280); uint 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); uint 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); 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] = interface->videoColor(color, 0, R, G, B);
continue;
} }
palette[color] = 0;
} }
} }
const uint8 Video::curve[32] = { auto Video::refresh() -> void {
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x10, 0x12, auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
0x14, 0x16, 0x18, 0x1c, 0x20, 0x28, 0x38, 0x38,
0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x80, for(uint y = 0; y < 160; y++) {
0x88, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 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);
}
} }

View File

@ -2,12 +2,12 @@ struct Video {
Video(); Video();
~Video(); ~Video();
auto generatePalette(Emulator::Interface::PaletteMode mode) -> void; auto power() -> void;
auto refresh() -> void;
uint32* palette = nullptr; uint32* output = nullptr;
uint32* paletteStandard = nullptr;
private: uint32* paletteEmulation = nullptr;
static const uint8 curve[32];
}; };
extern Video video; extern Video video;

View File

@ -71,7 +71,6 @@ auto ICD2::reset() -> void {
joyp14lock = 0; joyp14lock = 0;
pulselock = true; pulselock = true;
GameBoy::video.generate_palette(Emulator::Interface::PaletteMode::Literal);
GameBoy::system.init(); GameBoy::system.init();
GameBoy::system.power(); GameBoy::system.power();
} }

View File

@ -3,6 +3,7 @@
namespace SuperFamicom { namespace SuperFamicom {
Interface* interface = nullptr; Interface* interface = nullptr;
Settings settings;
Interface::Interface() { Interface::Interface() {
interface = this; interface = this;
@ -469,8 +470,25 @@ auto Interface::cheatSet(const lstring& list) -> void {
} }
} }
auto Interface::paletteUpdate(PaletteMode mode) -> void { auto Interface::cap(const string& name) -> bool {
video.generate_palette(mode); 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<bool>()) return settings.blurEmulation = value.get<bool>(), true;
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
return false;
} }
} }

View File

@ -117,11 +117,20 @@ struct Interface : Emulator::Interface {
auto cheatSet(const lstring&) -> void; 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> device; vector<Device> device;
}; };
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
bool scanlineEmulation = true;
};
extern Interface* interface; extern Interface* interface;
extern Settings settings;
} }

View File

@ -19,12 +19,11 @@ bg4(*this, Background::ID::BG4),
sprite(*this), sprite(*this),
window(*this), window(*this),
screen(*this) { screen(*this) {
surface = new uint32[512 * 512]; output = new uint32[512 * 512];
output = surface + 16 * 512;
} }
PPU::~PPU() { PPU::~PPU() {
delete[] surface; delete[] output;
} }
auto PPU::step(uint clocks) -> void { auto PPU::step(uint clocks) -> void {
@ -110,7 +109,7 @@ auto PPU::power() -> void {
auto PPU::reset() -> void { auto PPU::reset() -> void {
create(Enter, system.cpuFrequency()); create(Enter, system.cpuFrequency());
PPUcounter::reset(); PPUcounter::reset();
memory::fill(surface, 512 * 512 * sizeof(uint32)); memory::fill(output, 512 * 480 * sizeof(uint32));
mmio_reset(); mmio_reset();
bg1.reset(); bg1.reset();

View File

@ -27,7 +27,6 @@ privileged:
uint ppu1_version = 1; //allowed: 1 uint ppu1_version = 1; //allowed: 1
uint ppu2_version = 3; //allowed: 1, 2, 3 uint ppu2_version = 3; //allowed: 1, 2, 3
uint32* surface = nullptr;
uint32* output = nullptr; uint32* output = nullptr;
struct { struct {

View File

@ -2,8 +2,8 @@ PPU::Screen::Screen(PPU& self) : self(self) {
} }
auto PPU::Screen::scanline() -> void { auto PPU::Screen::scanline() -> void {
output = self.output + self.vcounter() * 1024; line = self.output + self.vcounter() * 1024;
if(self.display.interlace && self.field()) output += 512; if(self.display.interlace && self.field()) line += 512;
//the first hires pixel of each scanline is transparent //the first hires pixel of each scanline is transparent
//note: exact value initializations are not confirmed on hardware //note: exact value initializations are not confirmed on hardware
@ -19,14 +19,14 @@ auto PPU::Screen::scanline() -> void {
} }
auto PPU::Screen::run() -> 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; bool hires = self.regs.pseudo_hires || self.regs.bgmode == 5 || self.regs.bgmode == 6;
auto sscolor = get_pixel_sub(hires); auto sscolor = get_pixel_sub(hires);
auto mscolor = get_pixel_main(); auto mscolor = get_pixel_main();
*output++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor); *line++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor);
*output++ = (self.regs.display_brightness << 15) | (mscolor); *line++ = (self.regs.display_brightness << 15) | (mscolor);
} }
auto PPU::Screen::get_pixel_sub(bool hires) -> uint16 { auto PPU::Screen::get_pixel_sub(bool hires) -> uint16 {

View File

@ -1,5 +1,5 @@
struct Screen { struct Screen {
uint32* output; uint32* line;
struct Regs { struct Regs {
bool addsub_mode; bool addsub_mode;

View File

@ -23,7 +23,7 @@ auto System::run() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
video.update(); video.refresh();
} }
} }
@ -60,7 +60,7 @@ auto System::runThreadToSave() -> void {
scheduler.enter(); scheduler.enter();
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break; if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) { 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.hasSPC7110()) cpu.coprocessors.append(&spc7110);
if(cartridge.hasMSU1()) cpu.coprocessors.append(&msu1); if(cartridge.hasMSU1()) cpu.coprocessors.append(&msu1);
video.reset();
scheduler.init(); scheduler.init();
device.connect(0, configuration.controllerPort1); device.connect(0, configuration.controllerPort1);
device.connect(1, configuration.controllerPort2); device.connect(1, configuration.controllerPort2);

View File

@ -1,56 +1,211 @@
Video video; Video video;
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() { 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)) { for(auto color : range(1 << 19)) {
if(mode == Emulator::Interface::PaletteMode::Literal) { uint l = (uint4)(color >> 15);
palette[color] = color; uint b = (uint5)(color >> 10);
continue; 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 R = L * gamma_ramp[r];
uint b = (color >> 10) & 31; uint G = L * gamma_ramp[g];
uint g = (color >> 5) & 31; uint B = L * gamma_ramp[b];
uint r = (color >> 0) & 31; paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
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;
} }
}
if(mode == Emulator::Interface::PaletteMode::Emulation) { for(auto color : range(1 << 19)) {
r = gamma_ramp[r]; uint l = (uint4)(color >> 15);
g = gamma_ramp[g]; uint b = (uint5)(color >> 10);
b = gamma_ramp[b]; uint g = (uint5)(color >> 5);
} else { uint r = (uint5)(color >> 0);
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);
} }
} }
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 //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<SuperScope*>(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<Justifier*>(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] = { const uint8 Video::gamma_ramp[32] = {
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c, 0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78, 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,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, 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<SuperScope*>(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<Justifier*>(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;
}

View File

@ -1,9 +1,13 @@
struct Video { struct Video {
Video(); 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: private:
bool hires; bool hires;
@ -13,9 +17,11 @@ private:
auto scanline() -> void; auto scanline() -> void;
auto init() -> 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 gamma_ramp[32];
static const uint8 cursor[15 * 15]; static const uint8 cursor[15 * 15];
auto draw_cursor(uint16 color, int x, int y) -> void;
friend class System; friend class System;
}; };

View File

@ -1,4 +1,4 @@
name := tomoko name := higan
processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050 processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050
include processor/GNUmakefile include processor/GNUmakefile
@ -68,12 +68,12 @@ obj/ui-resource.o:
build: $(objects) build: $(objects)
$(strip $(compiler) -o out/$(name) $(objects) $(link)) $(strip $(compiler) -o out/$(name) $(objects) $(link))
ifeq ($(platform),macosx) ifeq ($(platform),macosx)
@if [ -d out/higan.app ]; then rm -r out/higan.app; fi @if [ -d out/$(name).app ]; then rm -r out/$(name).app; fi
mkdir -p out/higan.app/Contents/MacOS/ mkdir -p out/$(name).app/Contents/MacOS/
mkdir -p out/higan.app/Contents/Resources/ mkdir -p out/$(name).app/Contents/Resources/
mv out/$(name) out/higan.app/Contents/MacOS/higan mv out/$(name) out/$(name).app/Contents/MacOS/$(name)
cp data/higan.plist out/higan.app/Contents/Info.plist cp data/$(name).plist out/$(name).app/Contents/Info.plist
sips -s format icns data/higan.png --out out/higan.app/Contents/Resources/higan.icns sips -s format icns data/$(name).png --out out/$(name).app/Contents/Resources/$(name).icns
endif endif
install: install:
@ -83,18 +83,18 @@ else ifeq ($(platform),windows)
else ifeq ($(platform),macosx) else ifeq ($(platform),macosx)
mkdir -p ~/Library/Application\ Support/$(name)/ mkdir -p ~/Library/Application\ Support/$(name)/
mkdir -p ~/Emulation/System/ 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 data/cheats.bml ~/Library/Application\ Support/$(name)/
cp -R profile/* ~/Emulation/System/ cp -R profile/* ~/Library/Application\ Support/$(name)/
else else ifneq ($(filter $(platform),linux bsd),)
mkdir -p $(prefix)/bin/ mkdir -p $(prefix)/bin/
mkdir -p $(prefix)/share/icons/ mkdir -p $(prefix)/share/icons/
mkdir -p $(prefix)/$(name)/ mkdir -p $(prefix)/$(name)/
mkdir -p ~/Emulation/System/ mkdir -p ~/Emulation/System/
cp out/$(name) $(prefix)/bin/$(name) 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 data/cheats.bml $(prefix)/$(name)/cheats.bml
cp -R profile/* ~/Emulation/System/ cp -R profile/* $(prefix)/$(name)/
endif endif
uninstall: uninstall:
@ -102,8 +102,8 @@ ifeq ($(shell id -un),root)
$(error "make uninstall should not be run as root") $(error "make uninstall should not be run as root")
else ifeq ($(platform),windows) else ifeq ($(platform),windows)
else ifeq ($(platform),macosx) else ifeq ($(platform),macosx)
if [ -d /Applications/higan.app ]; then rm -r /Applications/higan.app; fi if [ -d /Applications/$(name).app ]; then rm -r /Applications/$(name).app; fi
else else ifneq ($(filter $(platform),linux bsd),)
if [ -f $(prefix)/bin/$(name) ]; then rm $(prefix)/bin/$(name); fi 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 if [ -f $(prefix)/share/icons/$(name).png ]; then rm $(prefix)/share/icons/$(name).png; fi
endif endif

View File

@ -2,7 +2,7 @@
Settings settings; Settings settings;
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) { auto set = [&](const string& name, const string& value) {
//create node and set to default value only if it does not already exist //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/AspectCorrection", true);
set("Video/Filter", "Blur"); set("Video/Filter", "Blur");
set("Video/Shader", "None"); set("Video/Shader", "None");
set("Video/BlurEmulation", true);
set("Video/ColorEmulation", true); set("Video/ColorEmulation", true);
set("Video/ScanlineEmulation", true);
set("Video/Saturation", 100); set("Video/Saturation", 100);
set("Video/Gamma", 100); set("Video/Gamma", 100);
set("Video/Luminance", 100); set("Video/Luminance", 100);
@ -45,5 +47,5 @@ Settings::Settings() {
} }
auto Settings::quit() -> void { auto Settings::quit() -> void {
file::write(locate({configpath(), "tomoko/"}, "settings.bml"), BML::serialize(*this)); file::write(locate({localpath(), "higan/"}, "settings.bml"), BML::serialize(*this));
} }

View File

@ -74,9 +74,17 @@ Presentation::Presentation() {
settings["Video/Filter"].setValue("Blur"); settings["Video/Filter"].setValue("Blur");
program->updateVideoFilter(); 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([&] { colorEmulation.setText("Color Emulation").setChecked(settings["Video/ColorEmulation"].boolean()).onToggle([&] {
settings["Video/ColorEmulation"].setValue(colorEmulation.checked()); 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([&] { maskOverscan.setText("Mask Overscan").setChecked(settings["Video/Overscan/Mask"].boolean()).onToggle([&] {
settings["Video/Overscan/Mask"].setValue(maskOverscan.checked()); settings["Video/Overscan/Mask"].setValue(maskOverscan.checked());
@ -131,6 +139,8 @@ Presentation::Presentation() {
statusBar.setFont(Font().setBold()); statusBar.setFont(Font().setBold());
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
viewport.setDroppable().onDrop([&](auto locations) { program->load(locations(0)); });
onClose([&] { program->quit(); }); onClose([&] { program->quit(); });
setTitle({"higan v", Emulator::Version}); setTitle({"higan v", Emulator::Version});
@ -187,6 +197,10 @@ auto Presentation::updateEmulator() -> void {
} }
systemMenuSeparatorPorts.setVisible(inputPort1.visible() || inputPort2.visible() || inputPort3.visible()); 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 { auto Presentation::resizeViewport() -> void {
@ -261,7 +275,7 @@ auto Presentation::loadShaders() -> void {
return; return;
} }
auto pathname = locate({localpath(), "tomoko/"}, "Video Shaders/"); auto pathname = locate({localpath(), "higan/"}, "Video Shaders/");
for(auto shader : directory::folders(pathname, "*.shader")) { for(auto shader : directory::folders(pathname, "*.shader")) {
MenuRadioItem item{&videoShaderMenu}; MenuRadioItem item{&videoShaderMenu};
item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] { item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] {

View File

@ -32,7 +32,9 @@ struct Presentation : Window {
MenuRadioItem videoFilterBlur{&videoFilterMenu}; MenuRadioItem videoFilterBlur{&videoFilterMenu};
Group videoFilters{&videoFilterNone, &videoFilterBlur}; Group videoFilters{&videoFilterNone, &videoFilterBlur};
MenuSeparator videoFilterSeparator{&videoFilterMenu}; MenuSeparator videoFilterSeparator{&videoFilterMenu};
MenuCheckItem blurEmulation{&videoFilterMenu};
MenuCheckItem colorEmulation{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu};
MenuCheckItem scanlineEmulation{&videoFilterMenu};
MenuCheckItem maskOverscan{&videoFilterMenu}; MenuCheckItem maskOverscan{&videoFilterMenu};
Menu videoShaderMenu{&settingsMenu}; Menu videoShaderMenu{&settingsMenu};
MenuRadioItem videoShaderNone{&videoShaderMenu}; MenuRadioItem videoShaderNone{&videoShaderMenu};

View File

@ -45,39 +45,7 @@ auto Program::saveRequest(uint id, string filename) -> void {
return emulator->save(id, stream); return emulator->save(id, stream);
} }
auto Program::videoColor(uint source, uint16 a, uint16 r, uint16 g, uint16 b) -> uint32 { auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
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 {
uint32* output; uint32* output;
uint length; uint length;
@ -88,7 +56,7 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, uint pitch
const uint32* sp = data + y * pitch; const uint32* sp = data + y * pitch;
uint32* dp = output + y * length; uint32* dp = output + y * length;
for(auto x : range(width)) { for(auto x : range(width)) {
*dp++ = palette[*sp++]; *dp++ = *sp++;
} }
} }

View File

@ -16,13 +16,12 @@ auto Program::loadMedia(string location) -> void {
auto Program::loadMedia(Emulator::Interface& emulator_, Emulator::Interface::Media& media, const string& location) -> void { auto Program::loadMedia(Emulator::Interface& emulator_, Emulator::Interface::Media& media, const string& location) -> void {
unloadMedia(); unloadMedia();
mediaPaths(0) = locate({settings["Library/Location"].text(), "System/"}, {media.name, ".sys/"}); mediaPaths(0) = locate({localpath(), "higan/"}, {media.name, ".sys/"});
mediaPaths(media.id) = location; mediaPaths(media.id) = location;
folderPaths.append(location); folderPaths.append(location);
emulator = &emulator_; emulator = &emulator_;
emulator->load(media.id); emulator->load(media.id);
updateVideoPalette();
dsp.setFrequency(emulator->audioFrequency()); dsp.setFrequency(emulator->audioFrequency());
emulator->power(); emulator->power();

View File

@ -11,7 +11,7 @@ Program* program = nullptr;
Program::Program(lstring args) { Program::Program(lstring args) {
program = this; program = this;
directory::create({configpath(), "tomoko/"}); directory::create({localpath(), "tomoko/"});
Application::onMain({&Program::main, this}); Application::onMain({&Program::main, this});
Application::Windows::onModalChange([](bool modal) { if(modal && audio) audio->clear(); }); Application::Windows::onModalChange([](bool modal) { if(modal && audio) audio->clear(); });
@ -72,14 +72,27 @@ Program::Program(lstring args) {
if(argument == "--fullscreen") { if(argument == "--fullscreen") {
presentation->toggleFullScreen(); presentation->toggleFullScreen();
} else { } else {
auto location = argument; load(argument);
if(directory::exists(location)) { }
loadMedia(location); }
} else if(file::exists(location)) { }
if(auto result = execute("icarus", "--import", location)) {
loadMedia(result.strip()); 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());
} }
} }
} }

View File

@ -1,6 +1,7 @@
struct Program : Emulator::Interface::Bind { struct Program : Emulator::Interface::Bind {
//program.cpp //program.cpp
Program(lstring args); Program(lstring args);
auto load(string) -> void;
auto main() -> void; auto main() -> void;
auto quit() -> 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 name, string type, bool required) -> void override;
auto loadRequest(uint id, string path, bool required) -> void override; auto loadRequest(uint id, string path, bool required) -> void override;
auto saveRequest(uint id, string path) -> 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* data, uint pitch, uint width, uint height) -> void override;
auto videoRefresh(const uint32* palette, const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(int16 lsample, int16 rsample) -> void override; auto audioSample(int16 lsample, int16 rsample) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override; auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void 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 showMessage(const string& text) -> void;
auto updateStatusText() -> void; auto updateStatusText() -> void;
auto updateVideoFilter() -> void; auto updateVideoFilter() -> void;
auto updateVideoPalette() -> void;
auto updateAudio() -> void; auto updateAudio() -> void;
auto updateAudioVolume() -> void; auto updateAudioVolume() -> void;
auto updateDSP() -> void; auto updateDSP() -> void;

View File

@ -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 { auto Program::updateAudio() -> void {
if(!audio) return; if(!audio) return;
audio->clear(); audio->clear();

View File

@ -73,6 +73,6 @@ auto HotkeySettings::inputEvent(shared_pointer<HID::Device> device, uint group,
timer.setEnabled(false); timer.setEnabled(false);
settingsManager->statusBar.setText(); settingsManager->statusBar.setText();
settingsManager->layout.setEnabled(); settingsManager->layout.setEnabled();
}).setInterval(1000).setEnabled(); }).setInterval(200).setEnabled();
} }
} }

View File

@ -145,6 +145,6 @@ auto InputSettings::inputEvent(shared_pointer<HID::Device> device, uint group, u
timer.setEnabled(false); timer.setEnabled(false);
settingsManager->statusBar.setText(); settingsManager->statusBar.setText();
settingsManager->layout.setEnabled(); settingsManager->layout.setEnabled();
}).setInterval(1000).setEnabled(); }).setInterval(200).setEnabled();
} }
} }

View File

@ -2,19 +2,6 @@ struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*); VideoSettings(TabFrame*);
VerticalLayout layout{this}; 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}}; Label overscanMaskLabel{&layout, Size{~0, 0}};
HorizontalLayout horizontalMaskLayout{&layout, Size{~0, 0}}; HorizontalLayout horizontalMaskLayout{&layout, Size{~0, 0}};
Label horizontalMaskLabel{&horizontalMaskLayout, Size{80, 0}}; Label horizontalMaskLabel{&horizontalMaskLayout, Size{80, 0}};

View File

@ -4,14 +4,6 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
layout.setMargin(5); 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"); overscanMaskLabel.setFont(Font().setBold()).setText("Overscan Mask");
horizontalMaskLabel.setText("Horizontal:"); horizontalMaskLabel.setText("Horizontal:");
horizontalMaskSlider.setLength(17).setPosition(settings["Video/Overscan/Horizontal"].natural()).onChange([&] { update(); }); 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 { 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/Horizontal"].setValue(horizontalMaskSlider.position());
settings["Video/Overscan/Vertical"].setValue(verticalMaskSlider.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"}); horizontalMaskValue.setText({horizontalMaskSlider.position(), "px"});
verticalMaskValue.setText({verticalMaskSlider.position(), "px"}); verticalMaskValue.setText({verticalMaskSlider.position(), "px"});
program->updateVideoPalette();
} }

View File

@ -14,7 +14,7 @@ auto locate(string pathname, string filename) -> string {
#include <nall/main.hpp> #include <nall/main.hpp>
auto nall::main(lstring args) -> void { auto nall::main(lstring args) -> void {
Application::setName("tomoko"); Application::setName("higan");
new Program(args); new Program(args);
Application::run(); Application::run();
} }

View File

@ -20,7 +20,7 @@ auto CheatDatabase::findCodes() -> void {
if(!emulator) return; if(!emulator) return;
auto sha256 = emulator->sha256(); 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); auto document = BML::unserialize(contents);
for(auto cartridge : document.find("cartridge")) { for(auto cartridge : document.find("cartridge")) {

View File

@ -16,6 +16,7 @@ struct file : file_system_object, varint {
enum class index : uint { absolute, relative }; enum class index : uint { absolute, relative };
static auto copy(const string& sourcename, const string& targetname) -> bool { static auto copy(const string& sourcename, const string& targetname) -> bool {
if(sourcename == targetname) return true;
file rd, wr; file rd, wr;
if(rd.open(sourcename, mode::read) == false) return false; if(rd.open(sourcename, mode::read) == false) return false;
if(wr.open(targetname, mode::write) == 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 //attempt to rename file first
//this will fail if paths point to different file systems; fall back to copy+remove in this case //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 { static auto move(const string& sourcename, const string& targetname) -> bool {
if(sourcename == targetname) return true;
if(rename(sourcename, targetname)) return true; if(rename(sourcename, targetname)) return true;
if(!writable(sourcename)) return false; if(!writable(sourcename)) return false;
if(copy(sourcename, targetname)) { if(copy(sourcename, targetname)) {