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
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

View File

@ -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/";

View File

@ -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; }
};
}

View File

@ -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<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 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> device;
};
struct Settings {
bool colorEmulation = true;
};
extern Interface* interface;
extern Settings settings;
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<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 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> device;
};
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
};
extern Interface* interface;
extern Settings settings;
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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);

View File

@ -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<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 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> device;
};
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
};
extern Interface* interface;
extern Settings settings;
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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<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 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;
};
struct Settings {
bool blurEmulation = true;
bool colorEmulation = true;
bool scanlineEmulation = true;
};
extern Interface* interface;
extern Settings settings;
}

View File

@ -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();

View File

@ -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 {

View File

@ -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 {

View File

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

View File

@ -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);

View File

@ -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<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] = {
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<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 {
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;
};

View File

@ -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

View File

@ -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));
}

View File

@ -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([=] {

View File

@ -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};

View File

@ -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++;
}
}

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 {
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();

View File

@ -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());
}
}
}

View File

@ -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;

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 {
if(!audio) return;
audio->clear();

View File

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

View File

@ -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}};

View File

@ -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();
}

View File

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

View File

@ -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")) {

View File

@ -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)) {