From a0aa11dac0ae0e93cefcf24252d8638d71486c8d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 23 Jun 2025 02:45:13 +0300 Subject: [PATCH] Add optional texture hashing --- include/config.hpp | 4 ++ include/renderer.hpp | 4 ++ include/renderer_gl/textures.hpp | 84 +++++++++++++------------- include/renderer_mtl/mtl_texture.hpp | 8 ++- src/config.cpp | 6 +- src/core/renderer_gl/renderer_gl.cpp | 21 +++++-- src/core/renderer_mtl/renderer_mtl.cpp | 14 +++++ src/emulator.cpp | 10 ++- src/libretro_core.cpp | 4 ++ src/panda_qt/config_window.cpp | 5 ++ 10 files changed, 106 insertions(+), 54 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 49597214..5e1bd8a7 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -62,9 +62,13 @@ struct EmulatorConfig { static constexpr RendererType rendererDefault = RendererType::OpenGL; #endif + static constexpr bool hashTexturesDefault = true; + bool shaderJitEnabled = shaderJitDefault; bool useUbershaders = ubershaderDefault; bool accelerateShaders = accelerateShadersDefault; + bool hashTextures = hashTexturesDefault; + bool accurateShaderMul = false; bool discordRpcEnabled = false; diff --git a/include/renderer.hpp b/include/renderer.hpp index d32077f6..0798184d 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -51,6 +51,9 @@ class Renderer { u32 outputWindowWidth = 400; u32 outputWindowHeight = 240 * 2; + // Should hw renderers hash textures? Stored separately from emulatorConfig because we'll be accessing it constantly, might be merged eventually + bool hashTextures = false; + EmulatorConfig* emulatorConfig = nullptr; void doSoftwareTextureCopy(u32 inputAddr, u32 outputAddr, u32 copySize, u32 inputWidth, u32 inputGap, u32 outputWidth, u32 outputGap); @@ -123,4 +126,5 @@ class Renderer { } void setConfig(EmulatorConfig* config) { emulatorConfig = config; } + void setHashTextures(bool setting) { hashTextures = setting; } }; diff --git a/include/renderer_gl/textures.hpp b/include/renderer_gl/textures.hpp index 4c6ca2dd..9663c2b4 100644 --- a/include/renderer_gl/textures.hpp +++ b/include/renderer_gl/textures.hpp @@ -1,6 +1,8 @@ #pragma once #include #include + +#include "PICA/pica_hash.hpp" #include "PICA/regs.hpp" #include "boost/icl/interval.hpp" #include "helpers.hpp" @@ -11,55 +13,55 @@ template using Interval = boost::icl::right_open_interval; struct Texture { - u32 location; - u32 config; // Magnification/minification filter, wrapping configs, etc - PICA::TextureFmt format; - OpenGL::uvec2 size; - bool valid; + using Hash = PICAHash::HashType; - // Range of VRAM taken up by buffer - Interval range; - // OpenGL resources allocated to buffer - OpenGL::Texture texture; + u32 location; + u32 config; // Magnification/minification filter, wrapping configs, etc + Hash hash = Hash(0); - Texture() : valid(false) {} + PICA::TextureFmt format; + OpenGL::uvec2 size; + bool valid; - Texture(u32 loc, PICA::TextureFmt format, u32 x, u32 y, u32 config, bool valid = true) - : location(loc), format(format), size({x, y}), config(config), valid(valid) { + // Range of VRAM taken up by buffer + Interval range; + // OpenGL resources allocated to buffer + OpenGL::Texture texture; - u64 endLoc = (u64)loc + sizeInBytes(); - // Check if start and end are valid here - range = Interval(loc, (u32)endLoc); - } + Texture() : valid(false) {} - // For 2 textures to "match" we only care about their locations, formats, and dimensions to match - // For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture - bool matches(Texture& other) { - return location == other.location && format == other.format && - size.x() == other.size.x() && size.y() == other.size.y(); - } + Texture(u32 loc, PICA::TextureFmt format, u32 x, u32 y, u32 config, bool valid = true) + : location(loc), format(format), size({x, y}), config(config), valid(valid) { + u64 endLoc = (u64)loc + sizeInBytes(); + // Check if start and end are valid here + range = Interval(loc, (u32)endLoc); + } - void allocate(); - void setNewConfig(u32 newConfig); - void decodeTexture(std::span data); - void free(); - u64 sizeInBytes(); + // For 2 textures to "match" we only care about their locations, formats, and dimensions to match + // For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture + bool matches(Texture& other) { + return location == other.location && hash == other.hash && format == other.format && size.x() == other.size.x() && size.y() == other.size.y(); + } - u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, std::span data); + void allocate(); + void setNewConfig(u32 newConfig); + void decodeTexture(std::span data); + void free(); + u64 sizeInBytes(); - // Get the morton interleave offset of a texel based on its U and V values - static u32 mortonInterleave(u32 u, u32 v); - // Get the byte offset of texel (u, v) in the texture - static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel); - static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width); + u32 decodeTexel(u32 u, u32 v, PICA::TextureFmt fmt, std::span data); - // Returns the format of this texture as a string - std::string_view formatToString() { - return PICA::textureFormatToString(format); - } + // Get the morton interleave offset of a texel based on its U and V values + static u32 mortonInterleave(u32 u, u32 v); + // Get the byte offset of texel (u, v) in the texture + static u32 getSwizzledOffset(u32 u, u32 v, u32 width, u32 bytesPerPixel); + static u32 getSwizzledOffset_4bpp(u32 u, u32 v, u32 width); - // Returns the texel at coordinates (u, v) of an ETC1(A4) texture - // TODO: Make hasAlpha a template parameter - u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span data); - u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData); + // Returns the format of this texture as a string + std::string_view formatToString() { return PICA::textureFormatToString(format); } + + // Returns the texel at coordinates (u, v) of an ETC1(A4) texture + // TODO: Make hasAlpha a template parameter + u32 getTexelETC(bool hasAlpha, u32 u, u32 v, u32 width, std::span data); + u32 decodeETC(u32 alpha, u32 u, u32 v, u64 colourData); }; diff --git a/include/renderer_mtl/mtl_texture.hpp b/include/renderer_mtl/mtl_texture.hpp index fd8a6062..b6989434 100644 --- a/include/renderer_mtl/mtl_texture.hpp +++ b/include/renderer_mtl/mtl_texture.hpp @@ -4,6 +4,7 @@ #include #include +#include "PICA/pica_hash.hpp" #include "PICA/regs.hpp" #include "boost/icl/interval.hpp" #include "helpers.hpp" @@ -17,10 +18,14 @@ using Interval = boost::icl::right_open_interval; namespace Metal { struct Texture { + using Hash = PICAHash::HashType; + MTL::Device* device; u32 location; u32 config; // Magnification/minification filter, wrapping configs, etc + Hash hash = Hash(0); + PICA::TextureFmt format; OpenGL::uvec2 size; bool valid; @@ -45,7 +50,8 @@ namespace Metal { // For 2 textures to "match" we only care about their locations, formats, and dimensions to match // For other things, such as filtering mode, etc, we can just switch the attributes of the cached texture bool matches(Texture& other) { - return location == other.location && format == other.format && size.x() == other.size.x() && size.y() == other.size.y(); + return location == other.location && hash == other.hash && format == other.format && size.x() == other.size.x() && + size.y() == other.size.y(); } void allocate(); diff --git a/src/config.cpp b/src/config.cpp index 3ff83f89..59f10bd5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -90,6 +90,7 @@ void EmulatorConfig::load() { forceShadergenForLights = toml::find_or(gpu, "ForceShadergenForLighting", true); lightShadergenThreshold = toml::find_or(gpu, "ShadergenLightThreshold", 1); + hashTextures = toml::find_or(gpu, "HashTextures", hashTexturesDefault); enableRenderdoc = toml::find_or(gpu, "EnableRenderdoc", false); } } @@ -101,7 +102,7 @@ void EmulatorConfig::load() { auto dspCoreName = toml::find_or(audio, "DSPEmulation", "HLE"); dspType = Audio::DSPCore::typeFromString(dspCoreName); - + audioEnabled = toml::find_or(audio, "EnableAudio", audioEnabledDefault); aacEnabled = toml::find_or(audio, "EnableAACAudio", true); printDSPFirmware = toml::find_or(audio, "PrintDSPFirmware", false); @@ -180,7 +181,7 @@ void EmulatorConfig::save() { data["Window"]["WindowPosY"] = windowSettings.y; data["Window"]["WindowWidth"] = windowSettings.width; data["Window"]["WindowHeight"] = windowSettings.height; - + data["GPU"]["EnableShaderJIT"] = shaderJitEnabled; data["GPU"]["Renderer"] = std::string(Renderer::typeToString(rendererType)); data["GPU"]["EnableVSync"] = vsyncEnabled; @@ -190,6 +191,7 @@ void EmulatorConfig::save() { data["GPU"]["ShadergenLightThreshold"] = lightShadergenThreshold; data["GPU"]["AccelerateShaders"] = accelerateShaders; data["GPU"]["EnableRenderdoc"] = enableRenderdoc; + data["GPU"]["HashTextures"] = hashTextures; data["Audio"]["DSPEmulation"] = std::string(Audio::DSPCore::typeToString(dspType)); data["Audio"]["EnableAudio"] = audioEnabled; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 2d54e586..828ca120 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -8,6 +8,7 @@ #include "PICA/float_types.hpp" #include "PICA/gpu.hpp" #include "PICA/pica_frag_uniforms.hpp" +#include "PICA/pica_hash.hpp" #include "PICA/pica_simd.hpp" #include "PICA/regs.hpp" #include "PICA/shader_decompiler.hpp" @@ -359,17 +360,29 @@ void RendererGL::bindTexturesToSlots() { const u32 width = getBits<16, 11>(dim); const u32 addr = (regs[ioBase + 4] & 0x0FFFFFFF) << 3; u32 format = regs[ioBase + (i == 0 ? 13 : 5)] & 0xF; - glActiveTexture(GL_TEXTURE0 + i); if (addr != 0) [[likely]] { Texture targetTex(addr, static_cast(format), width, height, config); + + if (hashTextures) { + const u8* startPointer = gpu.getPointerPhys(targetTex.location); + const usize sizeInBytes = targetTex.sizeInBytes(); + + if (startPointer == nullptr || (sizeInBytes > 0 && gpu.getPointerPhys(targetTex.location + sizeInBytes - 1) == nullptr)) + [[unlikely]] { + Helpers::warn("Out-of-bounds texture fetch"); + } else { + targetTex.hash = PICAHash::computeHash((const char*)startPointer, sizeInBytes); + } + } + OpenGL::Texture tex = getTexture(targetTex); tex.bind(); } else { - // Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it is far easier. - // Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a PU/MMU and can do all sorts of - // Weird invalid memory accesses without crashing + // Mapping a texture from NULL. PICA seems to read the last sampled colour, but for now we will display a black texture instead since it + // is far easier. Games that do this don't really care what it does, they just expect the PICA to not crash, since it doesn't have a + // PU/MMU and can do all sorts of Weird invalid memory accesses without crashing blankTexture.bind(); } } diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp index e6fdf653..e2e4c085 100644 --- a/src/core/renderer_mtl/renderer_mtl.cpp +++ b/src/core/renderer_mtl/renderer_mtl.cpp @@ -9,6 +9,7 @@ #undef NO #include "PICA/gpu.hpp" +#include "PICA/pica_hash.hpp" #include "SDL_metal.h" using namespace PICA; @@ -729,6 +730,19 @@ void RendererMTL::bindTexturesToSlots() { if (addr != 0) [[likely]] { Metal::Texture targetTex(device, addr, static_cast(format), width, height, config); + + if (hashTextures) { + const u8* startPointer = gpu.getPointerPhys(targetTex.location); + const usize sizeInBytes = targetTex.sizeInBytes(); + + if (startPointer == nullptr || (sizeInBytes > 0 && gpu.getPointerPhys(targetTex.location + sizeInBytes - 1) == nullptr)) + [[unlikely]] { + Helpers::warn("Out-of-bounds texture fetch"); + } else { + targetTex.hash = PICAHash::computeHash((const char*)startPointer, sizeInBytes); + } + } + auto tex = getTexture(targetTex); commandEncoder.setFragmentTexture(tex.texture, i); commandEncoder.setFragmentSamplerState(tex.sampler ? tex.sampler : nearestSampler, i); diff --git a/src/emulator.cpp b/src/emulator.cpp index 11970d91..ca6f3f7d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -32,18 +32,14 @@ Emulator::Emulator() dspService.setDSPCore(dsp.get()); audioDevice.init(dsp->getSamples()); - setAudioEnabled(config.audioEnabled); - - if (Renderdoc::isSupported() && config.enableRenderdoc) { - loadRenderdoc(); - } - #ifdef PANDA3DS_ENABLE_DISCORD_RPC if (config.discordRpcEnabled) { discordRpc.init(); updateDiscord(); } #endif + + reloadSettings(); reset(ReloadOption::NoReload); } @@ -461,6 +457,8 @@ void Emulator::reloadSettings() { loadRenderdoc(); } + gpu.getRenderer()->setHashTextures(config.hashTextures); + #ifdef PANDA3DS_ENABLE_DISCORD_RPC // Reload RPC setting if we're compiling with RPC support diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index a9783320..24651516 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -176,6 +176,9 @@ static void configInit() { {"panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault ? "Use ubershaders (No stutter, maybe slower); enabled|disabled" : "Use ubershaders (No stutter, maybe slower); disabled|enabled"}, {"panda3ds_use_vsync", "Enable VSync; enabled|disabled"}, + {"panda3ds_hash_textures", EmulatorConfig::hashTexturesDefault ? "Hash textures (Better graphics, maybe slower); enabled|disabled" + : "Hash textures (Better graphics, maybe slower); disabled|enabled"}, + {"panda3ds_system_language", "System language; En|Fr|Es|De|It|Pt|Nl|Ru|Ja|Zh|Ko|Tw"}, {"panda3ds_dsp_emulation", "DSP emulation; HLE|LLE|Null"}, {"panda3ds_use_audio", EmulatorConfig::audioEnabledDefault ? "Enable audio; enabled|disabled" : "Enable audio; disabled|enabled"}, @@ -216,6 +219,7 @@ static void configUpdate() { config.accurateShaderMul = fetchVariableBool("panda3ds_accurate_shader_mul", false); config.useUbershaders = fetchVariableBool("panda3ds_use_ubershader", EmulatorConfig::ubershaderDefault); config.accelerateShaders = fetchVariableBool("panda3ds_accelerate_shaders", EmulatorConfig::accelerateShadersDefault); + config.hashTextures = fetchVariableBool("panda3ds_hash_textures", EmulatorConfig::hashTexturesDefault); config.forceShadergenForLights = fetchVariableBool("panda3ds_ubershader_lighting_override", true); config.lightShadergenThreshold = fetchVariableRange("panda3ds_ubershader_lighting_override_threshold", 1, 8); diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index d3e13306..0b2eb64b 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -231,6 +231,11 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win connectCheckbox(accelerateShaders, config.accelerateShaders); gpuLayout->addRow(accelerateShaders); + QCheckBox* hashTextures = new QCheckBox(tr("Hash textures")); + hashTextures->setToolTip(tr("Enable this to reduce texture mismatches at the cost of slightly lower performance")); + connectCheckbox(hashTextures, config.hashTextures); + gpuLayout->addRow(hashTextures); + QCheckBox* forceShadergenForLights = new QCheckBox(tr("Force shadergen when rendering lights")); connectCheckbox(forceShadergenForLights, config.forceShadergenForLights); gpuLayout->addRow(forceShadergenForLights);