mirror of https://github.com/bsnes-emu/bsnes.git
Update to v098r04 release.
byuu says: Changelog: - SFC: fixed behavior of 21fx $21fe register when no device is connected (must return zero) - SFC: reduced 21fx buffer size to 1024 bytes in both directions to mirror the FT232H we are using - SFC: eliminated dsp/modulo-array.hpp [1] - higan: implemented higan/video interface and migrated all cores to it [2] [1] the echo history buffer was 8-bytes, so there was no need for it at all here. Not sure what I was thinking. The BRR buffer was 12-bytes, and has very weird behavior ... but there's only a single location in the code where it actually writes to this buffer. It's much easier to just write to the buffer three times there instead of implementing an entire class just to abstract away two lines of code. This change actually boosted the speed from ~124.5fps to around ~127.5fps, but that's within the margin of error for GCC. I doubt it's actually faster this way. The DSP core could really use a ton of work. It comes from a port of blargg's spc_dsp to my coding style, but he was extremely fond of using 32-bit signed integers everywhere. There's a lot of opportunity to remove red tape masking by resizing the variables to their actual state sizes. I really need to find where I put spc_dsp6.sfc from blargg. It's a great test to verify if I've made any mistakes in my implementation that would cause regressions. Don't suppose anyone has it? [2] so again, the idea is that higan/audio and higan/video are going to sit between the emulation cores and the user interfaces. The hope is to output raw encoding data from the emulation cores without having to worry about the video display format (generally 24-bit RGB) of the host display. And also to avoid having to repeat myself with eg three separate implementations of interframe blending, and so on. Furthermore, the idea is that the user interface can configure its side of the settings, and the emulation cores can configure their sides. Thus, neither has to worry about the other end. And now we can spin off new user interfaces much easier without having to mess with all of these things. Right now, I've implemented color emulation, interframe blending and SNES horizontal color bleed. I did not implement scanlines (and interlace effects for them) yet, but I probably will at some point. Further, for right now, the WonderSwan/Color screen rotation is busted and will only show games in the horizontal orientation. Obviously this must be fixed before the next official release, but I'll want to think about how to implement it. Also, the SNES light gun pointers are missing for now. Things are a bit messy right now as I've gone through several revisions of how to handle these things, so a good house cleaning is in order once everything is feature-complete again. I need to sit down and think through how and where I want to handle things like light gun cursors, LCD icons, and maybe even rasterized text messages. And obviously ... higan/audio is still just nall::DSP's headers. I need to revamp that whole interface. I want to make it quite powerful with a true audio mixer so I can handle things like SNES+SGB+MSU1+Voicer-Kun+SNES-CD (five separate audio streams at once.) The video system has the concept of "effects" for things like color bleed and interframe blending. I want to extend on this with useful other effects, such as NTSC simulation, maybe bringing back my mini-HQ2x filter, etc. I'd also like to restore the saturation/gamma/luma adjustment sliders ... I always liked allowing people to compensate for their displays without having to change settings system-wide. Lastly, I've always wanted to see some audio effects. Although I doubt we'll ever get my dream of CoreAudio-style profiles, I'd like to get some basic equalizer settings and echo/reverb effects in there.
This commit is contained in:
parent
1929ad47d2
commit
a2d3b8ba15
|
@ -4,10 +4,11 @@
|
|||
using namespace nall;
|
||||
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "098.03";
|
||||
static const string Version = "098.04";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -182,7 +182,11 @@ auto Interface::get(const string& name) -> any {
|
|||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Famicom {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "serialization.cpp"
|
||||
|
||||
|
@ -43,7 +42,7 @@ auto PPU::scanline() -> void {
|
|||
|
||||
auto PPU::frame() -> void {
|
||||
status.field ^= 1;
|
||||
video.refresh();
|
||||
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[256 * 480];
|
||||
paletteLiteral = new uint32[1 << 9];
|
||||
paletteStandard = new uint32[1 << 9];
|
||||
paletteEmulation = new uint32[1 << 9];
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
memory::fill(output(), 256 * 480);
|
||||
|
||||
for(auto color : range(1 << 9)) {
|
||||
paletteLiteral[color] = color;
|
||||
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::refresh() -> void {
|
||||
auto output = this->output();
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
if(settings.scanlineEmulation) {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto targetLo = output + y * 512;
|
||||
auto targetHi = output + y * 512 + 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
auto color = palette[*source++];
|
||||
*targetLo++ = color;
|
||||
*targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1);
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 480);
|
||||
} else {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto target = output + y * 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
*target++ = palette[*source++];
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::generateColor(
|
||||
uint n, double saturation, double hue,
|
||||
double contrast, double brightness, double gamma
|
||||
) -> uint32 {
|
||||
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
|
||||
|
||||
static const double black = 0.518, white = 1.962, attenuation = 0.746;
|
||||
static const double levels[8] = {
|
||||
0.350, 0.518, 0.962, 1.550,
|
||||
1.094, 1.506, 1.962, 1.962,
|
||||
};
|
||||
|
||||
double lo_and_hi[2] = {
|
||||
levels[level + 4 * (color == 0x0)],
|
||||
levels[level + 4 * (color < 0xd)],
|
||||
};
|
||||
|
||||
double y = 0.0, i = 0.0, q = 0.0;
|
||||
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
|
||||
for(int p : range(12)) {
|
||||
double spot = lo_and_hi[wave(p, color)];
|
||||
|
||||
if(((n & 0x040) && wave(p, 12))
|
||||
|| ((n & 0x080) && wave(p, 4))
|
||||
|| ((n & 0x100) && wave(p, 8))
|
||||
) spot *= attenuation;
|
||||
|
||||
double v = (spot - black) / (white - black);
|
||||
|
||||
v = (v - 0.5) * contrast + 0.5;
|
||||
v *= brightness / 12.0;
|
||||
|
||||
y += v;
|
||||
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
|
||||
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
|
||||
}
|
||||
|
||||
i *= saturation;
|
||||
q *= saturation;
|
||||
|
||||
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
|
||||
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(r, g, b);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
struct Video {
|
||||
Video();
|
||||
|
||||
auto reset() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
auto generateColor(uint, double, double, double, double, double) -> uint32;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Famicom {
|
||||
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
|
@ -23,6 +24,7 @@ auto System::load() -> void {
|
|||
auto document = BML::unserialize(information.manifest);
|
||||
cartridge.load();
|
||||
serializeInit();
|
||||
configureVideo();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
|
@ -48,7 +50,6 @@ auto System::reset() -> void {
|
|||
ppu.reset();
|
||||
input.reset();
|
||||
scheduler.reset();
|
||||
video.reset();
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
|
|
|
@ -12,6 +12,11 @@ struct System {
|
|||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideo() -> void;
|
||||
auto configureVideoPalette() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
auto System::configureVideo() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoPalette() -> void {
|
||||
auto generateColor = [](uint n, double saturation, double hue, double contrast, double brightness, double gamma) -> uint64 {
|
||||
int color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
|
||||
|
||||
static const double black = 0.518, white = 1.962, attenuation = 0.746;
|
||||
static const double levels[8] = {
|
||||
0.350, 0.518, 0.962, 1.550,
|
||||
1.094, 1.506, 1.962, 1.962,
|
||||
};
|
||||
|
||||
double lo_and_hi[2] = {
|
||||
levels[level + 4 * (color == 0x0)],
|
||||
levels[level + 4 * (color < 0xd)],
|
||||
};
|
||||
|
||||
double y = 0.0, i = 0.0, q = 0.0;
|
||||
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
|
||||
for(int p : range(12)) {
|
||||
double spot = lo_and_hi[wave(p, color)];
|
||||
|
||||
if(((n & 0x040) && wave(p, 12))
|
||||
|| ((n & 0x080) && wave(p, 4))
|
||||
|| ((n & 0x100) && wave(p, 8))
|
||||
) spot *= attenuation;
|
||||
|
||||
double v = (spot - black) / (white - black);
|
||||
|
||||
v = (v - 0.5) * contrast + 0.5;
|
||||
v *= brightness / 12.0;
|
||||
|
||||
y += v;
|
||||
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
|
||||
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
|
||||
}
|
||||
|
||||
i *= saturation;
|
||||
q *= saturation;
|
||||
|
||||
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
|
||||
uint64 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
|
||||
uint64 g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
|
||||
uint64 b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
};
|
||||
|
||||
Emulator::video.setPalette(1 << 9, [&](uint32 color) -> uint64 {
|
||||
auto gamma = settings.colorEmulation ? 1.8 : 2.2;
|
||||
return generateColor(color, 2.0, 0.0, 1.0, 1.0, gamma);
|
||||
});
|
||||
}
|
|
@ -195,8 +195,18 @@ auto Interface::get(const string& name) -> any {
|
|||
}
|
||||
|
||||
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 == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
namespace GameBoy {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "mmio.cpp"
|
||||
#include "dmg.cpp"
|
||||
#include "cgb.cpp"
|
||||
|
@ -62,7 +60,7 @@ auto PPU::main() -> void {
|
|||
|
||||
if(++status.ly == 154) {
|
||||
status.ly = 0;
|
||||
video.refresh();
|
||||
if(!system.sgb()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, MMIO {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[160 * 144];
|
||||
paletteLiteral = new uint32[1 << 15];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output(), 160 * 144 * sizeof(uint32));
|
||||
|
||||
if(system.dmg()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint L = image::normalize(3 - color, 2, 16);
|
||||
paletteStandard[color] = interface->videoColor(L, L, L);
|
||||
|
||||
uint R = monochrome[color][0];
|
||||
uint G = monochrome[color][1];
|
||||
uint B = monochrome[color][2];
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
|
||||
if(system.sgb()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
paletteLiteral[color] = color;
|
||||
paletteStandard[color] = color;
|
||||
paletteEmulation[color] = color;
|
||||
}
|
||||
}
|
||||
|
||||
if(system.cgb()) {
|
||||
for(auto color : range(1 << 15)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint r = (uint5)(color >> 0);
|
||||
uint g = (uint5)(color >> 5);
|
||||
uint b = (uint5)(color >> 10);
|
||||
|
||||
uint R = image::normalize(r, 5, 16);
|
||||
uint G = image::normalize(g, 5, 16);
|
||||
uint B = image::normalize(b, 5, 16);
|
||||
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto output = this->output();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface->videoRefresh(output, 4 * 160, 160, 144);
|
||||
}
|
||||
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 Video::monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#else //DMG_PALETTE_WHITE
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
struct Video {
|
||||
Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
|
||||
static const uint16 monochrome[4][3];
|
||||
};
|
||||
|
||||
extern Video video;
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
|
@ -49,6 +50,7 @@ auto System::load(Revision revision) -> void {
|
|||
|
||||
cartridge.load(revision);
|
||||
serializeInit();
|
||||
configureVideo();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
|
@ -64,7 +66,6 @@ auto System::power() -> void {
|
|||
cpu.power();
|
||||
ppu.power();
|
||||
apu.power();
|
||||
video.power();
|
||||
scheduler.power();
|
||||
|
||||
_clocksExecuted = 0;
|
||||
|
|
|
@ -29,6 +29,11 @@ struct System {
|
|||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideo() -> void;
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
auto System::configureVideo() -> void {
|
||||
if(sgb()) return;
|
||||
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
}
|
||||
|
||||
auto System::configureVideoPalette() -> void {
|
||||
if(sgb()) return;
|
||||
|
||||
if(dmg()) Emulator::video.setPalette(1 << 2, [&](uint32 color) -> uint64 {
|
||||
if(!settings.colorEmulation) {
|
||||
uint64 L = image::normalize(3 - color, 2, 16);
|
||||
return L << 32 | L << 16 | L << 0;
|
||||
} else {
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#elif defined(DMG_PALETTE_WHITE)
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint64 R = monochrome[color][0];
|
||||
uint64 G = monochrome[color][1];
|
||||
uint64 B = monochrome[color][2];
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
});
|
||||
|
||||
if(cgb()) Emulator::video.setPalette(1 << 15, [&](uint32 color) -> uint64 {
|
||||
uint r = color.bits( 0, 4);
|
||||
uint g = color.bits( 5, 9);
|
||||
uint b = color.bits(10,14);
|
||||
|
||||
uint64_t R = image::normalize(r, 5, 16);
|
||||
uint64_t G = image::normalize(g, 5, 16);
|
||||
uint64_t B = image::normalize(b, 5, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
});
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
if(sgb()) return;
|
||||
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
}
|
|
@ -169,8 +169,18 @@ auto Interface::get(const string& name) -> any {
|
|||
}
|
||||
|
||||
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 == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
namespace GameBoyAdvance {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "background.cpp"
|
||||
#include "object.cpp"
|
||||
#include "mosaic.cpp"
|
||||
|
@ -180,7 +178,7 @@ auto PPU::scanline() -> void {
|
|||
|
||||
auto PPU::frame() -> void {
|
||||
player.frame();
|
||||
video.refresh();
|
||||
Emulator::video.refresh(output, 240 * sizeof(uint32), 240, 160);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, MMIO {
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[240 * 160];
|
||||
paletteLiteral = new uint32[1 << 15];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output(), 240 * 160 * sizeof(uint32));
|
||||
|
||||
for(auto color : range(1 << 15)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint B = (uint5)(color >> 10);
|
||||
uint G = (uint5)(color >> 5);
|
||||
uint R = (uint5)(color >> 0);
|
||||
|
||||
uint b = image::normalize(B, 5, 16);
|
||||
uint g = image::normalize(G, 5, 16);
|
||||
uint r = image::normalize(R, 5, 16);
|
||||
paletteStandard[color] = interface->videoColor(r, g, b);
|
||||
|
||||
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);
|
||||
paletteEmulation[color] = interface->videoColor(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto output = this->output();
|
||||
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);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
struct Video {
|
||||
Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
|
@ -3,6 +3,7 @@
|
|||
namespace GameBoyAdvance {
|
||||
|
||||
#include "bios.cpp"
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
BIOS bios;
|
||||
System system;
|
||||
|
@ -22,7 +23,6 @@ auto System::power() -> void {
|
|||
ppu.power();
|
||||
apu.power();
|
||||
cartridge.power();
|
||||
video.power();
|
||||
scheduler.power();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ auto System::load() -> void {
|
|||
|
||||
cartridge.load();
|
||||
serializeInit();
|
||||
configureVideo();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,12 @@ struct System {
|
|||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideo() -> void;
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
auto System::configureVideo() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
}
|
||||
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette(1 << 15, [&](uint32 color) -> uint64 {
|
||||
uint R = color.bits( 0, 4);
|
||||
uint G = color.bits( 5, 9);
|
||||
uint B = color.bits(10,14);
|
||||
|
||||
uint64 r = image::normalize(R, 5, 16);
|
||||
uint64 g = image::normalize(G, 5, 16);
|
||||
uint64 b = image::normalize(B, 5, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
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);
|
||||
r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
}
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
});
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
struct Coprocessor : Cothread {
|
||||
};
|
||||
|
||||
#include <sfc/coprocessor/icd2/icd2.hpp>
|
||||
#include <sfc/coprocessor/mcc/mcc.hpp>
|
||||
#include <sfc/coprocessor/nss/nss.hpp>
|
||||
|
|
|
@ -20,8 +20,8 @@ auto DSP::brrDecode(Voice& v) -> void {
|
|||
}
|
||||
|
||||
//apply IIR filter (2 is the most commonly used)
|
||||
const int p1 = v.buffer[v.bufferOffset - 1];
|
||||
const int p2 = v.buffer[v.bufferOffset - 2] >> 1;
|
||||
const int p1 = v.buffer[12 + v.bufferOffset - 1];
|
||||
const int p2 = v.buffer[12 + v.bufferOffset - 2] >> 1;
|
||||
|
||||
switch(filter) {
|
||||
case 0:
|
||||
|
@ -50,10 +50,12 @@ auto DSP::brrDecode(Voice& v) -> void {
|
|||
break;
|
||||
}
|
||||
|
||||
//adjust and write sample
|
||||
//adjust and write sample (mirror the written sample for wrapping)
|
||||
s = sclamp<16>(s);
|
||||
s = (int16)(s << 1);
|
||||
v.buffer.write(v.bufferOffset++, s);
|
||||
if(v.bufferOffset >= BrrBufferSize) v.bufferOffset = 0;
|
||||
v.buffer[v.bufferOffset + 0] = s;
|
||||
v.buffer[v.bufferOffset + 12] = s;
|
||||
v.buffer[v.bufferOffset + 24] = s;
|
||||
if(++v.bufferOffset >= 12) v.bufferOffset = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -230,51 +230,13 @@ auto DSP::write(uint8 addr, uint8 data) -> void {
|
|||
/* initialization */
|
||||
|
||||
auto DSP::power() -> void {
|
||||
for(auto& r : state.regs) r = 0;
|
||||
state.echoHistoryOffset = 0;
|
||||
state.everyOtherSample = false;
|
||||
state.kon = 0;
|
||||
state.noise = 0;
|
||||
state.counter = 0;
|
||||
state.echoOffset = 0;
|
||||
state.echoLength = 0;
|
||||
state.konBuffer = 0;
|
||||
state.endxBuffer = 0;
|
||||
state.envxBuffer = 0;
|
||||
state.outxBuffer = 0;
|
||||
state._pmon = 0;
|
||||
state._non = 0;
|
||||
state._eon = 0;
|
||||
state._dir = 0;
|
||||
state._koff = 0;
|
||||
state._brrNextAddress = 0;
|
||||
state._adsr0 = 0;
|
||||
state._brrHeader = 0;
|
||||
state._brrByte = 0;
|
||||
state._srcn = 0;
|
||||
state._esa = 0;
|
||||
state._echoDisabled = 0;
|
||||
state._dirAddress = 0;
|
||||
state._pitch = 0;
|
||||
state._output = 0;
|
||||
state._looped = 0;
|
||||
state._echoPointer = 0;
|
||||
state._mainOut[0] = state._mainOut[1] = 0;
|
||||
state._echoOut[0] = state._echoOut[1] = 0;
|
||||
state._echoIn[0] = state._echoIn[1] = 0;
|
||||
memory::fill(&state, sizeof(State));
|
||||
|
||||
for(auto n : range(8)) {
|
||||
voice[n].bufferOffset = 0;
|
||||
voice[n].gaussianOffset = 0;
|
||||
voice[n].brrAddress = 0;
|
||||
memory::fill(&voice[n], sizeof(Voice));
|
||||
voice[n].brrOffset = 1;
|
||||
voice[n].vbit = 1 << n;
|
||||
voice[n].vidx = n * 0x10;
|
||||
voice[n].konDelay = 0;
|
||||
voice[n].envelopeMode = EnvelopeRelease;
|
||||
voice[n].envelope = 0;
|
||||
voice[n].hiddenEnvelope = 0;
|
||||
voice[n]._envxOut = 0;
|
||||
}
|
||||
|
||||
audio.coprocessorEnable(false);
|
||||
|
|
|
@ -16,11 +16,10 @@ struct DSP : Thread {
|
|||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
privileged:
|
||||
#include "modulo-array.hpp"
|
||||
|
||||
enum GlobalRegister : uint {
|
||||
MVOLL = 0x0c, MVOLR = 0x1c,
|
||||
EVOLL = 0x2c, EVOLR = 0x3c,
|
||||
|
@ -48,8 +47,6 @@ privileged:
|
|||
};
|
||||
|
||||
enum : uint {
|
||||
EchoHistorySize = 8,
|
||||
BrrBufferSize = 12,
|
||||
BrrBlockSize = 9,
|
||||
CounterRange = 2048 * 5 * 3, //30720 (0x7800)
|
||||
};
|
||||
|
@ -57,8 +54,8 @@ privileged:
|
|||
struct State {
|
||||
uint8 regs[128];
|
||||
|
||||
ModuloArray<int, EchoHistorySize> echoHistory[2]; //echo history keeps most recent 8 samples
|
||||
int echoHistoryOffset;
|
||||
int echoHistory[2][8]; //echo history keeps most recent 8 stereo samples
|
||||
uint3 echoHistoryOffset;
|
||||
|
||||
bool everyOtherSample; //toggles every sample
|
||||
int kon; //KON value when last checked
|
||||
|
@ -105,7 +102,7 @@ privileged:
|
|||
} state;
|
||||
|
||||
struct Voice {
|
||||
ModuloArray<int, BrrBufferSize> buffer; //decoded samples
|
||||
int buffer[12 * 3]; //12 decoded samples (mirrored for wrapping)
|
||||
int bufferOffset; //place in buffer where next samples will be decoded
|
||||
int gaussianOffset; //relative fractional position in sample (0x1000 = 1.0)
|
||||
int brrAddress; //address of current BRR block
|
||||
|
@ -119,29 +116,29 @@ privileged:
|
|||
int _envxOut;
|
||||
} voice[8];
|
||||
|
||||
//gaussian
|
||||
//gaussian.cpp
|
||||
static const int16 GaussianTable[512];
|
||||
auto gaussianInterpolate(const Voice& v) -> int;
|
||||
|
||||
//counter
|
||||
//counter.cpp
|
||||
static const uint16 CounterRate[32];
|
||||
static const uint16 CounterOffset[32];
|
||||
auto counterTick() -> void;
|
||||
auto counterPoll(uint rate) -> bool;
|
||||
|
||||
//envelope
|
||||
//envelope.cpp
|
||||
auto envelopeRun(Voice& v) -> void;
|
||||
|
||||
//brr
|
||||
//brr.cpp
|
||||
auto brrDecode(Voice& v) -> void;
|
||||
|
||||
//misc
|
||||
//misc.cpp
|
||||
auto misc27() -> void;
|
||||
auto misc28() -> void;
|
||||
auto misc29() -> void;
|
||||
auto misc30() -> void;
|
||||
|
||||
//voice
|
||||
//voice.cpp
|
||||
auto voiceOutput(Voice& v, bool channel) -> void;
|
||||
auto voice1 (Voice& v) -> void;
|
||||
auto voice2 (Voice& v) -> void;
|
||||
|
@ -156,8 +153,8 @@ privileged:
|
|||
auto voice8 (Voice& v) -> void;
|
||||
auto voice9 (Voice& v) -> void;
|
||||
|
||||
//echo
|
||||
auto calculateFIR(int i, bool channel) -> int;
|
||||
//echo.cpp
|
||||
auto calculateFIR(bool channel, int index) -> int;
|
||||
auto echoOutput(bool channel) -> int;
|
||||
auto echoRead(bool channel) -> void;
|
||||
auto echoWrite(bool channel) -> void;
|
||||
|
@ -171,7 +168,7 @@ privileged:
|
|||
auto echo29() -> void;
|
||||
auto echo30() -> void;
|
||||
|
||||
//dsp
|
||||
//dsp.cpp
|
||||
static auto Enter() -> void;
|
||||
auto tick() -> void;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
auto DSP::calculateFIR(int i, bool channel) -> int {
|
||||
int s = state.echoHistory[channel][state.echoHistoryOffset + i + 1];
|
||||
return (s * (int8)REG(FIR + i * 0x10)) >> 6;
|
||||
auto DSP::calculateFIR(bool channel, int index) -> int {
|
||||
int sample = state.echoHistory[channel][(uint3)(state.echoHistoryOffset + index + 1)];
|
||||
return (sample * (int8)REG(FIR + index * 0x10)) >> 6;
|
||||
}
|
||||
|
||||
auto DSP::echoOutput(bool channel) -> int {
|
||||
|
@ -14,7 +14,7 @@ auto DSP::echoRead(bool channel) -> void {
|
|||
uint8 lo = smp.apuram[(uint16)(addr + 0)];
|
||||
uint8 hi = smp.apuram[(uint16)(addr + 1)];
|
||||
int s = (int16)((hi << 8) + lo);
|
||||
state.echoHistory[channel].write(state.echoHistoryOffset, s >> 1);
|
||||
state.echoHistory[channel][state.echoHistoryOffset] = s >> 1;
|
||||
}
|
||||
|
||||
auto DSP::echoWrite(bool channel) -> void {
|
||||
|
@ -31,22 +31,21 @@ auto DSP::echoWrite(bool channel) -> void {
|
|||
auto DSP::echo22() -> void {
|
||||
//history
|
||||
state.echoHistoryOffset++;
|
||||
if(state.echoHistoryOffset >= EchoHistorySize) state.echoHistoryOffset = 0;
|
||||
|
||||
state._echoPointer = (uint16)((state._esa << 8) + state.echoOffset);
|
||||
echoRead(0);
|
||||
|
||||
//FIR
|
||||
int l = calculateFIR(0, 0);
|
||||
int r = calculateFIR(0, 1);
|
||||
int r = calculateFIR(1, 0);
|
||||
|
||||
state._echoIn[0] = l;
|
||||
state._echoIn[1] = r;
|
||||
}
|
||||
|
||||
auto DSP::echo23() -> void {
|
||||
int l = calculateFIR(1, 0) + calculateFIR(2, 0);
|
||||
int r = calculateFIR(1, 1) + calculateFIR(2, 1);
|
||||
int l = calculateFIR(0, 1) + calculateFIR(0, 2);
|
||||
int r = calculateFIR(1, 1) + calculateFIR(1, 2);
|
||||
|
||||
state._echoIn[0] += l;
|
||||
state._echoIn[1] += r;
|
||||
|
@ -55,22 +54,22 @@ auto DSP::echo23() -> void {
|
|||
}
|
||||
|
||||
auto DSP::echo24() -> void {
|
||||
int l = calculateFIR(3, 0) + calculateFIR(4, 0) + calculateFIR(5, 0);
|
||||
int r = calculateFIR(3, 1) + calculateFIR(4, 1) + calculateFIR(5, 1);
|
||||
int l = calculateFIR(0, 3) + calculateFIR(0, 4) + calculateFIR(0, 5);
|
||||
int r = calculateFIR(1, 3) + calculateFIR(1, 4) + calculateFIR(1, 5);
|
||||
|
||||
state._echoIn[0] += l;
|
||||
state._echoIn[1] += r;
|
||||
}
|
||||
|
||||
auto DSP::echo25() -> void {
|
||||
int l = state._echoIn[0] + calculateFIR(6, 0);
|
||||
int r = state._echoIn[1] + calculateFIR(6, 1);
|
||||
int l = state._echoIn[0] + calculateFIR(0, 6);
|
||||
int r = state._echoIn[1] + calculateFIR(1, 6);
|
||||
|
||||
l = (int16)l;
|
||||
r = (int16)r;
|
||||
|
||||
l += (int16)calculateFIR(7, 0);
|
||||
r += (int16)calculateFIR(7, 1);
|
||||
l += (int16)calculateFIR(0, 7);
|
||||
r += (int16)calculateFIR(1, 7);
|
||||
|
||||
state._echoIn[0] = sclamp<16>(l) & ~1;
|
||||
state._echoIn[1] = sclamp<16>(r) & ~1;
|
||||
|
|
|
@ -35,11 +35,11 @@ const int16 DSP::GaussianTable[512] = {
|
|||
|
||||
auto DSP::gaussianInterpolate(const Voice& v) -> int {
|
||||
//make pointers into gaussian table based on fractional position between samples
|
||||
int offset = (v.gaussianOffset >> 4) & 0xff;
|
||||
uint8 offset = v.gaussianOffset >> 4;
|
||||
const int16* forward = GaussianTable + 255 - offset;
|
||||
const int16* reverse = GaussianTable + offset; //mirror left half of gaussian table
|
||||
|
||||
offset = v.bufferOffset + (v.gaussianOffset >> 12);
|
||||
offset = 12 + v.bufferOffset + (v.gaussianOffset >> 12);
|
||||
int output;
|
||||
output = (forward[ 0] * v.buffer[offset + 0]) >> 11;
|
||||
output += (forward[256] * v.buffer[offset + 1]) >> 11;
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
template<typename T, uint size>
|
||||
struct ModuloArray {
|
||||
inline auto operator[](int index) const -> T {
|
||||
return buffer[size + index];
|
||||
}
|
||||
|
||||
inline auto read(int index) const -> T {
|
||||
return buffer[size + index];
|
||||
}
|
||||
|
||||
inline auto write(uint index, const T value) -> void {
|
||||
buffer[index] =
|
||||
buffer[index + size] =
|
||||
buffer[index + size + size] = value;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void {
|
||||
s.array(buffer, size * 3);
|
||||
}
|
||||
|
||||
private:
|
||||
T buffer[size * 3] = {0};
|
||||
};
|
|
@ -2,8 +2,8 @@ void DSP::serialize(serializer& s) {
|
|||
Thread::serialize(s);
|
||||
|
||||
s.array(state.regs, 128);
|
||||
state.echoHistory[0].serialize(s);
|
||||
state.echoHistory[1].serialize(s);
|
||||
s.array(state.echoHistory[0]);
|
||||
s.array(state.echoHistory[1]);
|
||||
s.integer(state.echoHistoryOffset);
|
||||
|
||||
s.integer(state.everyOtherSample);
|
||||
|
@ -43,7 +43,7 @@ void DSP::serialize(serializer& s) {
|
|||
s.array(state._echoIn, 2);
|
||||
|
||||
for(auto n : range(8)) {
|
||||
voice[n].buffer.serialize(s);
|
||||
s.array(voice[n].buffer);
|
||||
s.integer(voice[n].bufferOffset);
|
||||
s.integer(voice[n].gaussianOffset);
|
||||
s.integer(voice[n].brrAddress);
|
||||
|
|
|
@ -75,15 +75,15 @@ auto S21FX::read(uint24 addr, uint8 data) -> uint8 {
|
|||
|
||||
if(addr >= 0x2184 && addr <= 0x21fd) return ram[addr - 0x2184];
|
||||
|
||||
if(addr == 0x21fe) return (
|
||||
if(addr == 0x21fe) return !link.open() ? 0 : (
|
||||
(linkBuffer.size() > 0) << 7 //1 = readable
|
||||
| (snesBuffer.size() < 65536) << 6 //1 = writable
|
||||
| (snesBuffer.size() < 1024) << 6 //1 = writable
|
||||
| (link.open()) << 5 //1 = connected
|
||||
);
|
||||
|
||||
if(addr == 0x21ff) {
|
||||
if(linkBuffer.size() > 0) {
|
||||
data = linkBuffer.takeFirst();
|
||||
return linkBuffer.takeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ auto S21FX::write(uint24 addr, uint8 data) -> void {
|
|||
addr &= 0x40ffff;
|
||||
|
||||
if(addr == 0x21ff) {
|
||||
if(snesBuffer.size() < 65536) {
|
||||
if(snesBuffer.size() < 1024) {
|
||||
snesBuffer.append(data);
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ auto S21FX::readable() -> bool {
|
|||
|
||||
auto S21FX::writable() -> bool {
|
||||
step(1);
|
||||
return linkBuffer.size() < 65536;
|
||||
return linkBuffer.size() < 1024;
|
||||
}
|
||||
|
||||
//SNES -> Link
|
||||
|
@ -131,7 +131,7 @@ auto S21FX::read() -> uint8 {
|
|||
//Link -> SNES
|
||||
auto S21FX::write(uint8 data) -> void {
|
||||
step(1);
|
||||
if(linkBuffer.size() < 65536) {
|
||||
if(linkBuffer.size() < 1024) {
|
||||
linkBuffer.append(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -503,8 +503,16 @@ auto Interface::get(const string& name) -> any {
|
|||
}
|
||||
|
||||
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 == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace SuperFamicom {
|
||||
|
||||
PPU ppu;
|
||||
#include "video.cpp"
|
||||
|
||||
#include "memory.cpp"
|
||||
#include "mmio.cpp"
|
||||
|
@ -22,9 +21,11 @@ sprite(*this),
|
|||
window(*this),
|
||||
screen(*this) {
|
||||
output = new uint32[512 * 512];
|
||||
output += 16 * 512; //overscan offset
|
||||
}
|
||||
|
||||
PPU::~PPU() {
|
||||
output -= 16 * 512;
|
||||
delete[] output;
|
||||
}
|
||||
|
||||
|
@ -190,7 +191,6 @@ auto PPU::reset() -> void {
|
|||
sprite.reset();
|
||||
window.reset();
|
||||
screen.reset();
|
||||
video.reset();
|
||||
|
||||
frame();
|
||||
}
|
||||
|
@ -213,7 +213,12 @@ auto PPU::scanline() -> void {
|
|||
screen.scanline();
|
||||
|
||||
if(vcounter() == 241) {
|
||||
video.refresh();
|
||||
auto output = this->output;
|
||||
if(!overscan()) output -= 14 * 512;
|
||||
auto pitch = 1024 >> interlace();
|
||||
auto width = 512;
|
||||
auto height = !interlace() ? 240 : 480;
|
||||
Emulator::video.refresh(output, pitch * sizeof(uint32), width, height);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, PPUcounter {
|
||||
alwaysinline auto interlace() const -> bool { return display.interlace; }
|
||||
alwaysinline auto overscan() const -> bool { return display.overscan; }
|
||||
|
|
|
@ -1,42 +1,5 @@
|
|||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[512 * 512];
|
||||
paletteLiteral = new uint32[1 << 19];
|
||||
paletteStandard = new uint32[1 << 19];
|
||||
paletteEmulation = new uint32[1 << 19];
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
memory::fill(output(), 512 * 512 * sizeof(uint32));
|
||||
|
||||
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);
|
||||
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.5);
|
||||
uint R = L * image::normalize(r, 5, 16);
|
||||
uint G = L * image::normalize(g, 5, 16);
|
||||
uint B = L * image::normalize(b, 5, 16);
|
||||
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||
|
||||
static const uint8 gammaRamp[32] = {
|
||||
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
|
||||
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
|
||||
0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
|
||||
0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
|
||||
};
|
||||
|
||||
R = L * gammaRamp[r] * 0x0101;
|
||||
G = L * gammaRamp[g] * 0x0101;
|
||||
B = L * gammaRamp[b] * 0x0101;
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
//note: this source file is currently unused
|
||||
//saved temporarily only for reference
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto output = this->output() + 16 * 512; //add offset for overscan
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
struct Video {
|
||||
Video();
|
||||
|
||||
auto reset() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
auto drawCursor(uint32 color, int x, int y) -> void;
|
||||
auto drawCursors() -> void;
|
||||
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
|
@ -4,6 +4,7 @@ namespace SuperFamicom {
|
|||
|
||||
System system;
|
||||
|
||||
#include "video.cpp"
|
||||
#include "peripherals.cpp"
|
||||
#include "random.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
@ -90,6 +91,7 @@ auto System::load() -> void {
|
|||
if(cartridge.hasSufamiTurboSlots()) sufamiturboA.load(), sufamiturboB.load();
|
||||
|
||||
serializeInit();
|
||||
configureVideo();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@ struct System {
|
|||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideo() -> void;
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
auto System::configureVideo() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
}
|
||||
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette(1 << 19, [&](uint32 color) -> uint64 {
|
||||
uint r = color.bits( 0, 4);
|
||||
uint g = color.bits( 5, 9);
|
||||
uint b = color.bits(10,14);
|
||||
uint l = color.bits(15,18);
|
||||
|
||||
double L = (1.0 + l) / 16.0 * (l ? 1.0 : 0.5);
|
||||
uint64 R = L * image::normalize(r, 5, 16);
|
||||
uint64 G = L * image::normalize(g, 5, 16);
|
||||
uint64 B = L * image::normalize(b, 5, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
static const uint8 gammaRamp[32] = {
|
||||
0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
|
||||
0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
|
||||
0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
|
||||
0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
|
||||
};
|
||||
R = L * gammaRamp[r] * 0x0101;
|
||||
G = L * gammaRamp[g] * 0x0101;
|
||||
B = L * gammaRamp[b] * 0x0101;
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
});
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::ColorBleed, settings.blurEmulation);
|
||||
}
|
|
@ -1,5 +1,89 @@
|
|||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace {
|
||||
namespace Emulator {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::~Video() {
|
||||
reset();
|
||||
}
|
||||
|
||||
auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void {
|
||||
if(this->width != width || this->height != height) {
|
||||
delete output;
|
||||
output = new uint32[width * height]();
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
pitch >>= 2; //bytes to words
|
||||
for(uint y : range(height)) {
|
||||
auto source = input + y * pitch;
|
||||
auto target = output + y * width;
|
||||
|
||||
if(!effects.interframeBlending) {
|
||||
for(uint x : range(width)) {
|
||||
auto color = palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
} else {
|
||||
for(uint x : range(width)) {
|
||||
auto a = *target;
|
||||
auto b = palette[*source++];
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(effects.colorBleed) {
|
||||
for(uint y : range(height)) {
|
||||
auto target = output + y * width;
|
||||
for(uint x : range(width)) {
|
||||
auto a = target[x];
|
||||
auto b = target[x + (x != width - 1)];
|
||||
target[x] = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface->videoRefresh(output, width * sizeof(uint32), width, height);
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
interface = nullptr;
|
||||
delete output;
|
||||
output = nullptr;
|
||||
delete palette;
|
||||
palette = nullptr;
|
||||
width = 0;
|
||||
height = 0;
|
||||
effects.colorBleed = false;
|
||||
effects.interframeBlending = false;
|
||||
}
|
||||
|
||||
auto Video::setInterface(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
auto Video::setPalette(uint32 size, function<uint64 (uint32)> callback) -> void {
|
||||
delete palette;
|
||||
palette = new uint32[size];
|
||||
for(auto index : range(size)) {
|
||||
//convert color from 16-bits/channel to 8-bits/channel; force alpha to 1.0
|
||||
uint64 color = callback(index);
|
||||
palette[index] = 255u << 24 | color.bits(40,47) << 16 | color.bits(24,31) << 8 | color.bits(8,15) << 0;
|
||||
}
|
||||
colors = size;
|
||||
}
|
||||
|
||||
auto Video::setEffect(Effect effect, const any& value) -> void {
|
||||
if(effect == Effect::ColorBleed && value.is<bool>()) {
|
||||
effects.colorBleed = value.get<bool>();
|
||||
}
|
||||
|
||||
if(effect == Effect::InterframeBlending && value.is<bool>()) {
|
||||
effects.interframeBlending = value.get<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
namespace {
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface;
|
||||
|
||||
struct Video {
|
||||
enum class Effect : uint {
|
||||
ColorBleed,
|
||||
InterframeBlending,
|
||||
};
|
||||
|
||||
~Video();
|
||||
auto refresh(uint32* input, uint pitch, uint width, uint height) -> void;
|
||||
auto reset() -> void;
|
||||
auto setInterface(Interface* interface) -> void;
|
||||
auto setPalette(uint32 size, function<uint64 (uint32)> callback) -> void;
|
||||
auto setEffect(Effect effect, const any& value) -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
uint32* output = nullptr;
|
||||
uint32* palette = nullptr;
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
uint colors = 0;
|
||||
|
||||
struct Effects {
|
||||
bool colorBleed = false;
|
||||
bool interframeBlending = false;
|
||||
} effects;
|
||||
};
|
||||
|
||||
extern Video video;
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ Interface::Interface() {
|
|||
information.manufacturer = "Bandai";
|
||||
information.name = "WonderSwan";
|
||||
information.width = 224; //note: technically 224x144; but screen can be rotated
|
||||
information.height = 224; //by using a square size; this can be done in the core
|
||||
information.height = 144; //by using a square size; this can be done in the core
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
@ -214,8 +214,18 @@ auto Interface::get(const string& name) -> any {
|
|||
}
|
||||
|
||||
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 == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ PPU ppu;
|
|||
#include "io.cpp"
|
||||
#include "latch.cpp"
|
||||
#include "render.cpp"
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
|
@ -73,7 +72,7 @@ auto PPU::scanline() -> void {
|
|||
auto PPU::frame() -> void {
|
||||
s.field = !s.field;
|
||||
s.vclk = 0;
|
||||
video.refresh();
|
||||
Emulator::video.refresh(output, 224 * sizeof(uint32), 224, 144);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
|
@ -100,8 +99,6 @@ auto PPU::power() -> void {
|
|||
r.lcdEnable = 1;
|
||||
r.vtotal = 158;
|
||||
r.vblank = 155;
|
||||
|
||||
video.power();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include "video.hpp"
|
||||
|
||||
struct PPU : Thread, IO {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
@ -36,7 +34,7 @@ struct PPU : Thread, IO {
|
|||
uint12 color;
|
||||
};
|
||||
|
||||
uint12 output[224 * 144];
|
||||
uint32 output[224 * 144];
|
||||
|
||||
struct State {
|
||||
bool field;
|
||||
|
|
|
@ -1,38 +1,5 @@
|
|||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[224 * 224];
|
||||
paletteLiteral = new uint32[1 << 12];
|
||||
paletteStandard = new uint32[1 << 12];
|
||||
paletteEmulation = new uint32[1 << 12];
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output(), 224 * 224 * sizeof(uint32));
|
||||
|
||||
for(uint12 color : range(1 << 12)) {
|
||||
paletteLiteral[color] = color;
|
||||
|
||||
uint b = color.bits(0, 3);
|
||||
uint g = color.bits(4, 7);
|
||||
uint r = color.bits(8,11);
|
||||
|
||||
uint R = image::normalize(r, 4, 16);
|
||||
uint G = image::normalize(g, 4, 16);
|
||||
uint B = image::normalize(b, 4, 16);
|
||||
paletteStandard[color] = interface->videoColor(R, G, B);
|
||||
|
||||
//todo: this uses the Game Boy Advance color emulation algorithm
|
||||
//need to determine proper color emulation for WonderSwan systems
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(480, R), 9, 16);
|
||||
G = image::normalize(min(480, G), 9, 16);
|
||||
B = image::normalize(min(480, B), 9, 16);
|
||||
paletteEmulation[color] = interface->videoColor(R, G, B);
|
||||
}
|
||||
}
|
||||
//note: this file is currently not compiled into higan
|
||||
//it is temporarily only used as a reference
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto& palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
struct Video {
|
||||
Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
private:
|
||||
unique_pointer<uint32[]> output;
|
||||
unique_pointer<uint32[]> paletteLiteral;
|
||||
unique_pointer<uint32[]> paletteStandard;
|
||||
unique_pointer<uint32[]> paletteEmulation;
|
||||
};
|
||||
|
||||
extern Video video;
|
|
@ -4,6 +4,7 @@ namespace WonderSwan {
|
|||
|
||||
System system;
|
||||
#include "io.cpp"
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
|
@ -39,9 +40,10 @@ auto System::load(Model model) -> void {
|
|||
}
|
||||
|
||||
cartridge.load();
|
||||
_loaded = true;
|
||||
_orientation = cartridge.information.orientation;
|
||||
serializeInit();
|
||||
configureVideo();
|
||||
_orientation = cartridge.information.orientation;
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
|
|
|
@ -20,6 +20,11 @@ struct System : IO {
|
|||
auto portRead(uint16 addr) -> uint8 override;
|
||||
auto portWrite(uint16 addr, uint8 data) -> void override;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideo() -> void;
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serializeInit() -> void;
|
||||
auto serialize() -> serializer;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
auto System::configureVideo() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
}
|
||||
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette(1 << 12, [&](uint32 color) -> uint64 {
|
||||
uint b = color.bits(0, 3);
|
||||
uint g = color.bits(4, 7);
|
||||
uint r = color.bits(8,11);
|
||||
|
||||
uint64_t R = image::normalize(r, 4, 16);
|
||||
uint64_t G = image::normalize(g, 4, 16);
|
||||
uint64_t B = image::normalize(b, 4, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(480, R), 9, 16);
|
||||
G = image::normalize(min(480, G), 9, 16);
|
||||
B = image::normalize(min(480, B), 9, 16);
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
});
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
}
|
Loading…
Reference in New Issue