diff --git a/bsnes/target-libretro/libretro.cpp b/bsnes/target-libretro/libretro.cpp index 31537e78..a9eadd7c 100644 --- a/bsnes/target-libretro/libretro.cpp +++ b/bsnes/target-libretro/libretro.cpp @@ -287,7 +287,32 @@ static void flush_variables() else run_ahead_frames = atoi(variable.value); } - + + variable = { "bsnes_video_filter", nullptr }; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &variable) && variable.value) + { + if (strcmp(variable.value, "None") == 0) { + program->filterRender = &Filter::None::render; + program->filterSize = &Filter::None::size; + } + else if (strcmp(variable.value, "NTSC (RF)") == 0) { + program->filterRender = &Filter::NTSC_RF::render; + program->filterSize = &Filter::NTSC_RF::size; + } + else if (strcmp(variable.value, "NTSC (Composite)") == 0) { + program->filterRender = &Filter::NTSC_Composite::render; + program->filterSize = &Filter::NTSC_Composite::size; + } + else if (strcmp(variable.value, "NTSC (S-Video)") == 0) { + program->filterRender = &Filter::NTSC_SVideo::render; + program->filterSize = &Filter::NTSC_SVideo::size; + } + else if (strcmp(variable.value, "NTSC (RGB)") == 0) { + program->filterRender = &Filter::NTSC_RGB::render; + program->filterSize = &Filter::NTSC_RGB::size; + } + } + // Refresh Geometry struct retro_system_av_info avinfo; retro_get_system_av_info(&avinfo); @@ -483,6 +508,7 @@ static void set_environment_info(retro_environment_t cb) { "bsnes_coprocessor_prefer_hle", "Coprocessor Prefer HLE; ON|OFF" }, { "bsnes_sgb_bios", "Preferred Super GameBoy BIOS (restart); SGB1.sfc|SGB2.sfc" }, { "bsnes_run_ahead_frames", "Amount of frames for run-ahead; OFF|1|2|3|4" }, + { "bsnes_video_filter", "Video Filter; None|NTSC (RF)|NTSC (Composite)|NTSC (S-Video)|NTSC (RGB)" }, { nullptr }, }; cb(RETRO_ENVIRONMENT_SET_VARIABLES, const_cast(vars)); @@ -653,13 +679,14 @@ RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code) RETRO_API bool retro_load_game(const retro_game_info *game) { - // bsnes uses 0RGB1555 internally but it is deprecated - // let software conversion happen in frontend - /*retro_pixel_format fmt = RETRO_PIXEL_FORMAT_0RGB1555; + retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) - return false;*/ + return false; emulator->configure("Audio/Frequency", SAMPLERATE); + program->filterRender = &Filter::None::render; + program->filterSize = &Filter::None::size; + program->updateVideoPalette(); flush_variables(); diff --git a/bsnes/target-libretro/program.cpp b/bsnes/target-libretro/program.cpp index 9f101908..9a5b12b9 100644 --- a/bsnes/target-libretro/program.cpp +++ b/bsnes/target-libretro/program.cpp @@ -46,6 +46,7 @@ struct Program : Emulator::Platform auto openRomBSMemory(string name, vfs::file::mode mode) -> shared_pointer; auto hackPatchMemory(vector& data) -> void; + auto updateVideoPalette() -> void; string base_name; @@ -79,6 +80,13 @@ public: struct BSMemory : Game { vector program; } bsMemory; + + uint32_t palette[32768]; + uint32_t paletteDimmed[32768]; + uint32_t videoOut[2304*2160]; + + Filter::Render filterRender; + Filter::Size filterSize; }; static Program *program = nullptr; @@ -157,6 +165,59 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> return result; } +auto Program::updateVideoPalette() -> void { + double luminance = 100.0 / 100.0; + double saturation = 100.0 / 100.0; + double gamma = 150.0 / 100.0; + + uint depth = 24; + + for(uint color : range(32768)) { + uint16 r = (color >> 10) & 31; + uint16 g = (color >> 5) & 31; + uint16 b = (color >> 0) & 31; + + r = r << 3 | r >> 2; r = r << 8 | r << 0; + g = g << 3 | g >> 2; g = g << 8 | g << 0; + b = b << 3 | b >> 2; b = b << 8 | b << 0; + + if(saturation != 1.0) { + uint16 grayscale = uclamp<16>((r + g + b) / 3); + 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(gamma != 1.0) { + double reciprocal = 1.0 / 32767.0; + r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma)); + g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma)); + b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma)); + } + + if(luminance != 1.0) { + r = uclamp<16>(r * luminance); + g = uclamp<16>(g * luminance); + b = uclamp<16>(b * luminance); + } + + switch(depth) { + case 24: palette[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break; + case 30: palette[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break; + } + + r >>= 1; + g >>= 1; + b >>= 1; + + switch(depth) { + case 24: paletteDimmed[color] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break; + case 30: paletteDimmed[color] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break; + } + } +} + auto Program::load() -> void { emulator->unload(); emulator->load(); @@ -227,12 +288,12 @@ auto Program::load(uint id, string name, string type, vector options) -> { if (loadGameBoy(gameBoy.location)) { - return { id, NULL }; + return { id, "" }; } } else if (id == 3) { if (loadBSMemory(bsMemory.location)) { - return { id, NULL }; + return { id, "" }; } } return { id, options(0) }; @@ -245,7 +306,20 @@ auto Program::videoFrame(const uint16* data, uint pitch, uint width, uint height data += 8 * (pitch >> 1) * multiplier; height -= 16 * multiplier; } - video_cb(data, width, height, pitch); + + uint filterWidth = width, filterHeight = height; + + filterSize(filterWidth, filterHeight); + + // Scale the NTSC filter properly for HD Mode 7 + if ((scale > 1) && (filterWidth == 602)) + { + filterWidth = 301 * scale; + } + + filterRender(palette, videoOut, filterWidth << 2, (const uint16_t*)data, pitch, width, height); + + video_cb(videoOut, filterWidth, filterHeight, filterWidth << 2); } // Double the fun!