From 8db961042ac2a3765d71647d19e5136bc6974b67 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 25 Dec 2020 18:02:38 +1000 Subject: [PATCH] GPU: Support replacing VRAM writes with new textures --- src/core/CMakeLists.txt | 2 + src/core/core.vcxproj | 29 +-- src/core/core.vcxproj.filters | 2 + src/core/gpu_commands.cpp | 21 ++- src/core/gpu_hw_d3d11.cpp | 56 ++++++ src/core/gpu_hw_d3d11.h | 5 + src/core/gpu_hw_opengl.cpp | 48 ++++- src/core/gpu_hw_opengl.h | 4 + src/core/gpu_hw_vulkan.cpp | 84 +++++++++ src/core/gpu_hw_vulkan.h | 11 +- src/core/settings.cpp | 26 ++- src/core/settings.h | 23 ++- src/core/system.cpp | 5 + src/core/texture_replacements.cpp | 301 ++++++++++++++++++++++++++++++ src/core/texture_replacements.h | 89 +++++++++ 15 files changed, 680 insertions(+), 26 deletions(-) create mode 100644 src/core/texture_replacements.cpp create mode 100644 src/core/texture_replacements.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 285012e73..1180558dd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -92,6 +92,8 @@ add_library(core spu.h system.cpp system.h + texture_replacements.cpp + texture_replacements.h timers.cpp timers.h timing_event.cpp diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 0ca308ce4..374f81ffb 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -149,6 +149,7 @@ + @@ -226,6 +227,7 @@ + @@ -246,6 +248,9 @@ {9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035} + + {09553c96-9f39-49bf-8ae6-7acbd07c410c} + {7ff9fdb9-d504-47db-a16a-b08071999620} @@ -464,7 +469,7 @@ WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -490,7 +495,7 @@ WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -516,7 +521,7 @@ WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -542,7 +547,7 @@ WITH_IMGUI=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -571,7 +576,7 @@ WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -600,7 +605,7 @@ WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) true ProgramDatabase - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) Default true false @@ -628,7 +633,7 @@ MaxSpeed true WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -655,7 +660,7 @@ MaxSpeed true WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -683,7 +688,7 @@ MaxSpeed true WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -710,7 +715,7 @@ MaxSpeed true WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -737,7 +742,7 @@ MaxSpeed true WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 @@ -765,7 +770,7 @@ MaxSpeed true WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) - $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\vixl\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) true true stdcpp17 diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index adf4f86e5..b50bf245a 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -55,6 +55,7 @@ + @@ -113,5 +114,6 @@ + \ No newline at end of file diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp index e027d0775..1cfc402bd 100644 --- a/src/core/gpu_commands.cpp +++ b/src/core/gpu_commands.cpp @@ -4,6 +4,7 @@ #include "gpu.h" #include "interrupt_controller.h" #include "system.h" +#include "texture_replacements.h" Log_SetChannel(GPU); #define CHECK_COMMAND_SIZE(num_words) \ @@ -497,13 +498,6 @@ bool GPU::HandleCopyRectangleCPUToVRAMCommand() void GPU::FinishVRAMWrite() { - if (g_settings.debugging.dump_cpu_to_vram_copies && m_blit_remaining_words == 0) - { - DumpVRAMToFile(StringUtil::StdStringFromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++).c_str(), - m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width, - m_blit_buffer.data(), true); - } - if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending()) SynchronizeCRTC(); @@ -511,6 +505,19 @@ void GPU::FinishVRAMWrite() if (m_blit_remaining_words == 0) { + if (g_settings.debugging.dump_cpu_to_vram_copies) + { + DumpVRAMToFile(StringUtil::StdStringFromFormat("cpu_to_vram_copy_%u.png", s_cpu_to_vram_dump_id++).c_str(), + m_vram_transfer.width, m_vram_transfer.height, sizeof(u16) * m_vram_transfer.width, + m_blit_buffer.data(), true); + } + + if (g_settings.texture_replacements.ShouldDumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height)) + { + g_texture_replacements.DumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height, + reinterpret_cast(m_blit_buffer.data())); + } + UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height, m_blit_buffer.data(), m_GPUSTAT.set_mask_while_drawing, m_GPUSTAT.check_mask_before_draw); } diff --git a/src/core/gpu_hw_d3d11.cpp b/src/core/gpu_hw_d3d11.cpp index 90ccc5eb4..9317c0cee 100644 --- a/src/core/gpu_hw_d3d11.cpp +++ b/src/core/gpu_hw_d3d11.cpp @@ -616,6 +616,52 @@ void GPU_HW_D3D11::DrawUtilityShader(ID3D11PixelShader* shader, const void* unif m_context->Draw(3, 0); } +bool GPU_HW_D3D11::BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, + u32 height) +{ + if (m_vram_replacement_texture.GetWidth() < tex->GetWidth() || + m_vram_replacement_texture.GetHeight() < tex->GetHeight()) + { + if (!m_vram_replacement_texture.Create(m_device.Get(), tex->GetWidth(), tex->GetHeight(), 1, + DXGI_FORMAT_R8G8B8A8_UNORM, D3D11_BIND_SHADER_RESOURCE, tex->GetPixels(), + tex->GetByteStride(), true)) + { + return false; + } + } + else + { + D3D11_MAPPED_SUBRESOURCE sr; + HRESULT hr = m_context->Map(m_vram_replacement_texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &sr); + if (FAILED(hr)) + { + Log_ErrorPrintf("Texture map failed: %08X", hr); + return false; + } + + const u32 copy_size = std::min(tex->GetByteStride(), sr.RowPitch); + const u8* src_ptr = reinterpret_cast(tex->GetPixels()); + u8* dst_ptr = static_cast(sr.pData); + for (u32 i = 0; i < tex->GetHeight(); i++) + { + std::memcpy(dst_ptr, src_ptr, copy_size); + src_ptr += tex->GetByteStride(); + dst_ptr += sr.RowPitch; + } + + m_context->Unmap(m_vram_replacement_texture, 0); + } + + m_context->OMSetDepthStencilState(m_depth_disabled_state.Get(), 0); + m_context->PSSetShaderResources(0, 1, m_vram_replacement_texture.GetD3DSRVArray()); + SetViewportAndScissor(dst_x, dst_y, width, height); + + const float uniforms[] = {0.0f, 0.0f, 1.0f, 1.0f}; + DrawUtilityShader(m_copy_pixel_shader.Get(), uniforms, sizeof(uniforms)); + RestoreGraphicsAPIState(); + return true; +} + void GPU_HW_D3D11::DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) { const bool textured = (m_batch.texture_mode != GPUTextureMode::Disabled); @@ -803,6 +849,16 @@ void GPU_HW_D3D11::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* d const Common::Rectangle bounds = GetVRAMTransferBounds(x, y, width, height); GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask); + if (!check_mask) + { + const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data); + if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale, + width * m_resolution_scale, height * m_resolution_scale)) + { + return; + } + } + const u32 num_pixels = width * height; const auto map_result = m_texture_stream_buffer.Map(m_context.Get(), sizeof(u16), num_pixels * sizeof(u16)); std::memcpy(map_result.pointer, data, num_pixels * sizeof(u16)); diff --git a/src/core/gpu_hw_d3d11.h b/src/core/gpu_hw_d3d11.h index 4c9dd3401..bab3afd9a 100644 --- a/src/core/gpu_hw_d3d11.h +++ b/src/core/gpu_hw_d3d11.h @@ -4,6 +4,7 @@ #include "common/d3d11/stream_buffer.h" #include "common/d3d11/texture.h" #include "gpu_hw.h" +#include "texture_replacements.h" #include #include #include @@ -68,6 +69,8 @@ private: void DrawUtilityShader(ID3D11PixelShader* shader, const void* uniforms, u32 uniforms_size); + bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + ComPtr m_device; ComPtr m_context; @@ -118,4 +121,6 @@ private: ComPtr m_vram_copy_pixel_shader; ComPtr m_vram_update_depth_pixel_shader; std::array, 3>, 2> m_display_pixel_shaders; // [depth_24][interlaced] + + D3D11::Texture m_vram_replacement_texture; }; diff --git a/src/core/gpu_hw_opengl.cpp b/src/core/gpu_hw_opengl.cpp index fb092a936..a253a9474 100644 --- a/src/core/gpu_hw_opengl.cpp +++ b/src/core/gpu_hw_opengl.cpp @@ -5,6 +5,7 @@ #include "gpu_hw_shadergen.h" #include "host_display.h" #include "system.h" +#include "texture_replacements.h" Log_SetChannel(GPU_HW_OpenGL); GPU_HW_OpenGL::GPU_HW_OpenGL() : GPU_HW() {} @@ -618,6 +619,37 @@ void GPU_HW_OpenGL::SetBlendMode() } } +bool GPU_HW_OpenGL::BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, + u32 height) +{ + if (!m_vram_write_replacement_texture.IsValid()) + { + if (!m_vram_write_replacement_texture.Create(tex->GetWidth(), tex->GetHeight(), 1, GL_RGBA, GL_RGBA, + GL_UNSIGNED_BYTE, tex->GetPixels()) || + !m_vram_write_replacement_texture.CreateFramebuffer()) + { + m_vram_write_replacement_texture.Destroy(); + return false; + } + } + else + { + m_vram_write_replacement_texture.Replace(tex->GetWidth(), tex->GetHeight(), GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, + tex->GetPixels()); + } + + glDisable(GL_SCISSOR_TEST); + m_vram_write_replacement_texture.BindFramebuffer(GL_READ_FRAMEBUFFER); + + dst_y = m_vram_texture.GetHeight() - dst_y - height; + glBlitFramebuffer(0, tex->GetHeight(), tex->GetWidth(), 0, dst_x, dst_y, dst_x + width, dst_y + height, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + m_vram_read_texture.Bind(); + glEnable(GL_SCISSOR_TEST); + return true; +} + void GPU_HW_OpenGL::SetDepthFunc() { SetDepthFunc(m_batch.use_depth_buffer ? GL_LEQUAL : (m_batch.check_mask_before_draw ? GL_GEQUAL : GL_ALWAYS)); @@ -849,12 +881,22 @@ void GPU_HW_OpenGL::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color) void GPU_HW_OpenGL::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) { + const Common::Rectangle bounds = GetVRAMTransferBounds(x, y, width, height); + GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask); + + if (!check_mask) + { + const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data); + if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale, + width * m_resolution_scale, height * m_resolution_scale)) + { + return; + } + } + const u32 num_pixels = width * height; if (num_pixels < m_max_texture_buffer_size || m_use_ssbo_for_vram_writes) { - const Common::Rectangle bounds = GetVRAMTransferBounds(x, y, width, height); - GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask); - const auto map_result = m_texture_stream_buffer->Map(sizeof(u16), num_pixels * sizeof(u16)); std::memcpy(map_result.pointer, data, num_pixels * sizeof(u16)); m_texture_stream_buffer->Unmap(num_pixels * sizeof(u16)); diff --git a/src/core/gpu_hw_opengl.h b/src/core/gpu_hw_opengl.h index 74ecd1638..9c3255cbf 100644 --- a/src/core/gpu_hw_opengl.h +++ b/src/core/gpu_hw_opengl.h @@ -5,6 +5,7 @@ #include "common/gl/texture.h" #include "glad.h" #include "gpu_hw.h" +#include "texture_replacements.h" #include #include #include @@ -67,12 +68,15 @@ private: void SetDepthFunc(GLenum func); void SetBlendMode(); + bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + // downsample texture - used for readbacks at >1xIR. GL::Texture m_vram_texture; GL::Texture m_vram_depth_texture; GL::Texture m_vram_read_texture; GL::Texture m_vram_encoding_texture; GL::Texture m_display_texture; + GL::Texture m_vram_write_replacement_texture; std::unique_ptr m_vertex_stream_buffer; GLuint m_vram_fbo_id = 0; diff --git a/src/core/gpu_hw_vulkan.cpp b/src/core/gpu_hw_vulkan.cpp index be760d14e..32e5e475e 100644 --- a/src/core/gpu_hw_vulkan.cpp +++ b/src/core/gpu_hw_vulkan.cpp @@ -1148,6 +1148,16 @@ void GPU_HW_Vulkan::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* const Common::Rectangle bounds = GetVRAMTransferBounds(x, y, width, height); GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask); + if (!check_mask) + { + const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data); + if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale, + width * m_resolution_scale, height * m_resolution_scale)) + { + return; + } + } + const u32 data_size = width * height * sizeof(u16); const u32 alignment = std::max(sizeof(u16), static_cast(g_vulkan_context->GetTexelBufferAlignment())); if (!m_texture_stream_buffer.ReserveMemory(data_size, alignment)) @@ -1326,6 +1336,80 @@ void GPU_HW_Vulkan::ClearDepthBuffer() m_last_depth_z = 1.0f; } +bool GPU_HW_Vulkan::CreateTextureReplacementStreamBuffer() +{ + if (m_texture_replacment_stream_buffer.IsValid()) + return true; + + if (!m_texture_replacment_stream_buffer.Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, TEXTURE_REPLACEMENT_BUFFER_SIZE)) + { + Log_ErrorPrint("Failed to allocate texture replacement streaming buffer"); + return false; + } + + return true; +} + +bool GPU_HW_Vulkan::BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, + u32 height) +{ + if (!CreateTextureReplacementStreamBuffer()) + return false; + + if (m_vram_write_replacement_texture.GetWidth() < tex->GetWidth() || + m_vram_write_replacement_texture.GetHeight() < tex->GetHeight()) + { + if (!m_vram_write_replacement_texture.Create(tex->GetWidth(), tex->GetHeight(), 1, 1, VK_FORMAT_R8G8B8A8_UNORM, + VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT)) + { + Log_ErrorPrint("Failed to create VRAM write replacement texture"); + return false; + } + } + + const u32 required_size = tex->GetWidth() * tex->GetHeight() * sizeof(u32); + const u32 alignment = static_cast(g_vulkan_context->GetBufferImageGranularity()); + if (!m_texture_replacment_stream_buffer.ReserveMemory(required_size, alignment)) + { + Log_PerfPrint("Executing command buffer while waiting for texture replacement buffer space"); + g_vulkan_context->ExecuteCommandBuffer(false); + if (!m_texture_replacment_stream_buffer.ReserveMemory(required_size, alignment)) + { + Log_ErrorPrintf("Failed to allocate %u bytes from texture replacement streaming buffer", required_size); + return false; + } + } + + // upload to buffer + const u32 buffer_offset = m_texture_replacment_stream_buffer.GetCurrentOffset(); + std::memcpy(m_texture_replacment_stream_buffer.GetCurrentHostPointer(), tex->GetPixels(), required_size); + m_texture_replacment_stream_buffer.CommitMemory(required_size); + + // buffer -> texture + VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer(); + m_vram_write_replacement_texture.UpdateFromBuffer(cmdbuf, 0, 0, 0, 0, tex->GetWidth(), tex->GetHeight(), + m_texture_replacment_stream_buffer.GetBuffer(), buffer_offset); + + // texture -> vram + const VkImageBlit blit = { + {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u}, + { + {0, 0, 0}, + {static_cast(tex->GetWidth()), static_cast(tex->GetHeight()), 1}, + }, + {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u}, + {{static_cast(dst_x), static_cast(dst_y), 0}, + {static_cast(dst_x + width), static_cast(dst_y + height), 1}}, + }; + m_vram_write_replacement_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + m_vram_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdBlitImage(cmdbuf, m_vram_write_replacement_texture.GetImage(), m_vram_write_replacement_texture.GetLayout(), + m_vram_texture.GetImage(), m_vram_texture.GetLayout(), 1, &blit, VK_FILTER_LINEAR); + m_vram_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + return true; +} + std::unique_ptr GPU::CreateHardwareVulkanRenderer() { return std::make_unique(); diff --git a/src/core/gpu_hw_vulkan.h b/src/core/gpu_hw_vulkan.h index 131703105..50afcfbef 100644 --- a/src/core/gpu_hw_vulkan.h +++ b/src/core/gpu_hw_vulkan.h @@ -4,6 +4,7 @@ #include "common/vulkan/stream_buffer.h" #include "common/vulkan/texture.h" #include "gpu_hw.h" +#include "texture_replacements.h" #include #include #include @@ -41,6 +42,7 @@ private: enum : u32 { MAX_PUSH_CONSTANTS_SIZE = 64, + TEXTURE_REPLACEMENT_BUFFER_SIZE = 64 * 1024 * 1024 }; void SetCapabilities(); void DestroyResources(); @@ -64,6 +66,10 @@ private: bool CompilePipelines(); void DestroyPipelines(); + bool CreateTextureReplacementStreamBuffer(); + + bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height); + VkRenderPass m_current_render_pass = VK_NULL_HANDLE; VkRenderPass m_vram_render_pass = VK_NULL_HANDLE; @@ -86,6 +92,7 @@ private: Vulkan::Texture m_vram_readback_texture; Vulkan::StagingTexture m_vram_readback_staging_texture; Vulkan::Texture m_display_texture; + bool m_use_ssbos_for_vram_writes = false; VkFramebuffer m_vram_framebuffer = VK_NULL_HANDLE; VkFramebuffer m_vram_update_depth_framebuffer = VK_NULL_HANDLE; @@ -123,5 +130,7 @@ private: // [depth_24][interlace_mode] DimensionalArray m_display_pipelines{}; - bool m_use_ssbos_for_vram_writes = false; + // texture replacements + Vulkan::Texture m_vram_write_replacement_texture; + Vulkan::StreamBuffer m_texture_replacment_stream_buffer; }; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 21943a9cc..178160791 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -255,6 +255,17 @@ void Settings::Load(SettingsInterface& si) debugging.show_timers_state = si.GetBoolValue("Debug", "ShowTimersState"); debugging.show_mdec_state = si.GetBoolValue("Debug", "ShowMDECState"); debugging.show_dma_state = si.GetBoolValue("Debug", "ShowDMAState"); + + texture_replacements.enable_vram_write_replacements = + si.GetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false); + texture_replacements.preload_textures = si.GetBoolValue("TextureReplacements", "PreloadTextures", false); + texture_replacements.dump_vram_writes = si.GetBoolValue("TextureReplacements", "DumpVRAMWrites", false); + texture_replacements.dump_vram_write_force_alpha_channel = + si.GetBoolValue("TextureReplacements", "DumpVRAMWriteForceAlphaChannel", true); + texture_replacements.dump_vram_write_width_threshold = + si.GetIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold", 128); + texture_replacements.dump_vram_write_height_threshold = + si.GetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128); } void Settings::Save(SettingsInterface& si) const @@ -381,6 +392,17 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Debug", "ShowTimersState", debugging.show_timers_state); si.SetBoolValue("Debug", "ShowMDECState", debugging.show_mdec_state); si.SetBoolValue("Debug", "ShowDMAState", debugging.show_dma_state); + + si.SetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", + texture_replacements.enable_vram_write_replacements); + si.SetBoolValue("TextureReplacements", "PreloadTextures", texture_replacements.preload_textures); + si.SetBoolValue("TextureReplacements", "DumpVRAMWrites", texture_replacements.dump_vram_writes); + si.SetBoolValue("TextureReplacements", "DumpVRAMWriteForceAlphaChannel", + texture_replacements.dump_vram_write_force_alpha_channel); + si.SetIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold", + texture_replacements.dump_vram_write_width_threshold); + si.SetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", + texture_replacements.dump_vram_write_height_threshold); } static std::array s_log_level_names = { @@ -635,8 +657,8 @@ static std::array s_display_aspect_ratio_names = {{"Auto (Game "19:9", "21:9", "32:9", "8:7", "5:4", "3:2", "2:1 (VRAM 1:1)", "1:1", "PAR 1:1"}}; static constexpr std::array s_display_aspect_ratio_values = { - {-1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 16.0f / 10.0f, 19.0f / 9.0f, 64.0f / 27.0f, 32.0f / 9.0f, 8.0f / 7.0f, 5.0f / 4.0f, 3.0f / 2.0f, - 2.0f / 1.0f, 1.0f, -1.0f}}; + {-1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 16.0f / 10.0f, 19.0f / 9.0f, 64.0f / 27.0f, 32.0f / 9.0f, 8.0f / 7.0f, 5.0f / 4.0f, + 3.0f / 2.0f, 2.0f / 1.0f, 1.0f, -1.0f}}; std::optional Settings::ParseDisplayAspectRatio(const char* str) { diff --git a/src/core/settings.h b/src/core/settings.h index 4ccf1cc89..eec5b40e2 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -171,6 +171,25 @@ struct Settings mutable bool show_dma_state = false; } debugging; + // texture replacements + struct TextureReplacementSettings + { + bool enable_vram_write_replacements = false; + bool preload_textures = false; + + bool dump_vram_writes = false; + bool dump_vram_write_force_alpha_channel = true; + u32 dump_vram_write_width_threshold = 128; + u32 dump_vram_write_height_threshold = 128; + + ALWAYS_INLINE bool AnyReplacementsEnabled() const { return enable_vram_write_replacements; } + + ALWAYS_INLINE bool ShouldDumpVRAMWrite(u32 width, u32 height) + { + return dump_vram_writes && width >= dump_vram_write_width_threshold && height >= dump_vram_write_height_threshold; + } + } texture_replacements; + // TODO: Controllers, memory cards, etc. bool bios_patch_tty_enable = false; @@ -228,7 +247,9 @@ struct Settings DEFAULT_DMA_MAX_SLICE_TICKS = 1000, DEFAULT_DMA_HALT_TICKS = 100, DEFAULT_GPU_FIFO_SIZE = 16, - DEFAULT_GPU_MAX_RUN_AHEAD = 128 + DEFAULT_GPU_MAX_RUN_AHEAD = 128, + DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD = 128, + DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD = 128, }; void Load(SettingsInterface& si); diff --git a/src/core/system.cpp b/src/core/system.cpp index b17c3e3a6..21542ff1e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -27,6 +27,7 @@ #include "save_state_version.h" #include "sio.h" #include "spu.h" +#include "texture_replacements.h" #include "timers.h" #include #include @@ -770,6 +771,8 @@ void Shutdown() if (s_state == State::Shutdown) return; + g_texture_replacements.Shutdown(); + g_sio.Shutdown(); g_mdec.Shutdown(); g_spu.Shutdown(); @@ -1691,6 +1694,8 @@ void UpdateRunningGame(const char* path, CDImage* image) s_running_game_code.c_str(), s_running_game_title.c_str()); } + g_texture_replacements.SetGameID(s_running_game_code); + g_host_interface->OnRunningGameChanged(); } diff --git a/src/core/texture_replacements.cpp b/src/core/texture_replacements.cpp new file mode 100644 index 000000000..12b46da03 --- /dev/null +++ b/src/core/texture_replacements.cpp @@ -0,0 +1,301 @@ +#include "texture_replacements.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/string_util.h" +#include "common/timer.h" +#include "host_interface.h" +#include "settings.h" +#include "xxhash.h" +#include +Log_SetChannel(TextureReplacements); + +TextureReplacements g_texture_replacements; + +static constexpr u32 RGBA5551ToRGBA8888(u16 color) +{ + u8 r = Truncate8(color & 31); + u8 g = Truncate8((color >> 5) & 31); + u8 b = Truncate8((color >> 10) & 31); + u8 a = Truncate8((color >> 15) & 1); + + // 00012345 -> 1234545 + b = (b << 3) | (b & 0b111); + g = (g << 3) | (g & 0b111); + r = (r << 3) | (r & 0b111); + a = a ? 255 : 0; + + return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24); +} + +std::string TextureReplacementHash::ToString() const +{ + return StringUtil::StdStringFromFormat("%" PRIx64 "%" PRIx64, high, low); +} + +bool TextureReplacementHash::ParseString(const std::string_view& sv) +{ + if (sv.length() != 32) + return false; + + std::optional high_value = StringUtil::FromChars(sv.substr(0, 16), 16); + std::optional low_value = StringUtil::FromChars(sv.substr(16), 16); + if (!high_value.has_value() || !low_value.has_value()) + return false; + + low = low_value.value(); + high = high_value.value(); + return true; +} + +TextureReplacements::TextureReplacements() = default; + +TextureReplacements::~TextureReplacements() = default; + +void TextureReplacements::SetGameID(std::string game_id) +{ + if (m_game_id == game_id) + return; + + m_game_id = game_id; + Reload(); +} + +const TextureReplacementTexture* TextureReplacements::GetVRAMWriteReplacement(u32 width, u32 height, const void* pixels) +{ + const TextureReplacementHash hash = GetVRAMWriteHash(width, height, pixels); + + const auto it = m_vram_write_replacements.find(hash); + if (it == m_vram_write_replacements.end()) + return nullptr; + + return LoadTexture(it->second); +} + +void TextureReplacements::DumpVRAMWrite(u32 width, u32 height, const void* pixels) +{ + std::string filename = GetVRAMWriteDumpFilename(width, height, pixels); + if (filename.empty()) + return; + + Common::RGBA8Image image; + image.SetSize(width, height); + + const u16* src_pixels = reinterpret_cast(pixels); + + for (u32 y = 0; y < height; y++) + { + for (u32 x = 0; x < width; x++) + { + image.SetPixel(x, y, RGBA5551ToRGBA8888(*src_pixels)); + src_pixels++; + } + } + + if (g_settings.texture_replacements.dump_vram_write_force_alpha_channel) + { + for (u32 y = 0; y < height; y++) + { + for (u32 x = 0; x < width; x++) + image.SetPixel(x, y, image.GetPixel(x, y) | 0xFF000000u); + } + } + + Log_InfoPrintf("Dumping %ux%u VRAM write to '%s'", width, height, filename.c_str()); + if (!Common::WriteImageToFile(image, filename.c_str())) + Log_ErrorPrintf("Failed to dump %ux%u VRAM write to '%s'", width, height, filename.c_str()); +} + +void TextureReplacements::Shutdown() +{ + m_texture_cache.clear(); + m_vram_write_replacements.clear(); + m_game_id.clear(); +} + +std::string TextureReplacements::GetSourceDirectory() const +{ + return g_host_interface->GetUserDirectoryRelativePath("textures/%s", m_game_id.c_str()); +} + +TextureReplacementHash TextureReplacements::GetVRAMWriteHash(u32 width, u32 height, const void* pixels) const +{ + XXH128_hash_t hash = XXH3_128bits(pixels, width * height * sizeof(u16)); + return {hash.low64, hash.high64}; +} + +std::string TextureReplacements::GetVRAMWriteDumpFilename(u32 width, u32 height, const void* pixels) const +{ + if (m_game_id.empty()) + return {}; + + const TextureReplacementHash hash = GetVRAMWriteHash(width, height, pixels); + std::string filename = g_host_interface->GetUserDirectoryRelativePath("dump/textures/%s/vram-write-%s.png", + m_game_id.c_str(), hash.ToString().c_str()); + + if (FileSystem::FileExists(filename.c_str())) + return {}; + + const std::string dump_directory = + g_host_interface->GetUserDirectoryRelativePath("dump/textures/%s", m_game_id.c_str()); + if (!FileSystem::DirectoryExists(dump_directory.c_str()) && + !FileSystem::CreateDirectory(dump_directory.c_str(), false)) + { + return {}; + } + + return filename; +} + +void TextureReplacements::Reload() +{ + m_vram_write_replacements.clear(); + + if (g_settings.texture_replacements.AnyReplacementsEnabled()) + FindTextures(GetSourceDirectory()); + + if (g_settings.texture_replacements.preload_textures) + PreloadTextures(); + + PurgeUnreferencedTexturesFromCache(); +} + +void TextureReplacements::PurgeUnreferencedTexturesFromCache() +{ + TextureCache old_map = std::move(m_texture_cache); + for (const auto& it : m_vram_write_replacements) + { + auto it2 = old_map.find(it.second); + if (it2 != old_map.end()) + { + m_texture_cache[it.second] = std::move(it2->second); + old_map.erase(it2); + } + } +} + +bool TextureReplacements::ParseReplacementFilename(const std::string& filename, + TextureReplacementHash* replacement_hash, + ReplacmentType* replacement_type) +{ + const char* extension = std::strrchr(filename.c_str(), '.'); + const char* title = std::strrchr(filename.c_str(), '/'); +#ifdef WIN32 + const char* title2 = std::strrchr(filename.c_str(), '\\'); + if (title2 && (!title || title2 > title)) + title = title2; +#endif + + if (!title || !extension) + return false; + + title++; + + const char* hashpart; + + if (StringUtil::Strncasecmp(title, "vram-write-", 11) == 0) + { + hashpart = title + 11; + *replacement_type = ReplacmentType::VRAMWrite; + } + else + { + return false; + } + + if (!replacement_hash->ParseString(std::string_view(hashpart, static_cast(extension - hashpart)))) + return false; + + extension++; + + bool valid_extension = false; + for (const char* test_extension : {"png", "jpg", "tga", "bmp"}) + { + if (StringUtil::Strcasecmp(extension, test_extension) == 0) + { + valid_extension = true; + break; + } + } + + return valid_extension; +} + +void TextureReplacements::FindTextures(const std::string& dir) +{ + FileSystem::FindResultsArray files; + FileSystem::FindFiles(dir.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE, &files); + + for (FILESYSTEM_FIND_DATA& fd : files) + { + if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) + continue; + + TextureReplacementHash hash; + ReplacmentType type; + if (!ParseReplacementFilename(fd.FileName, &hash, &type)) + continue; + + switch (type) + { + case ReplacmentType::VRAMWrite: + { + auto it = m_vram_write_replacements.find(hash); + if (it != m_vram_write_replacements.end()) + { + Log_WarningPrintf("Duplicate VRAM write replacement: '%s' and '%s'", it->second.c_str(), fd.FileName.c_str()); + continue; + } + + m_vram_write_replacements.emplace(hash, std::move(fd.FileName)); + } + break; + } + } + + Log_InfoPrintf("Found %zu replacement VRAM writes for '%s'", m_vram_write_replacements.size(), m_game_id.c_str()); +} + +const TextureReplacementTexture* TextureReplacements::LoadTexture(const std::string& filename) +{ + auto it = m_texture_cache.find(filename); + if (it != m_texture_cache.end()) + return &it->second; + + Common::RGBA8Image image; + if (!Common::LoadImageFromFile(&image, filename.c_str())) + { + Log_ErrorPrintf("Failed to load '%s'", filename.c_str()); + return nullptr; + } + + Log_InfoPrintf("Loaded '%s': %ux%u", filename.c_str(), image.GetWidth(), image.GetHeight()); + it = m_texture_cache.emplace(filename, std::move(image)).first; + return &it->second; +} + +void TextureReplacements::PreloadTextures() +{ + static constexpr float UPDATE_INTERVAL = 1.0f; + + Common::Timer last_update_time; + u32 num_textures_loaded = 0; + const u32 total_textures = static_cast(m_vram_write_replacements.size()); + +#define UPDATE_PROGRESS() \ + if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \ + { \ + g_host_interface->DisplayLoadingScreen("Preloading replacement textures...", 0, static_cast(total_textures), \ + static_cast(num_textures_loaded)); \ + last_update_time.Reset(); \ + } + + for (const auto& it : m_vram_write_replacements) + { + UPDATE_PROGRESS(); + + LoadTexture(it.second); + num_textures_loaded++; + } + +#undef UPDATE_PROGRESS +} diff --git a/src/core/texture_replacements.h b/src/core/texture_replacements.h new file mode 100644 index 000000000..4c19ab7b0 --- /dev/null +++ b/src/core/texture_replacements.h @@ -0,0 +1,89 @@ +#pragma once +#include "common/hash_combine.h" +#include "common/image.h" +#include "types.h" +#include +#include +#include +#include + +struct TextureReplacementHash +{ + u64 low; + u64 high; + + std::string ToString() const; + bool ParseString(const std::string_view& sv); + + bool operator<(const TextureReplacementHash& rhs) const { return std::tie(low, high) < std::tie(rhs.low, rhs.high); } + bool operator==(const TextureReplacementHash& rhs) const { return low == rhs.low && high == rhs.high; } + bool operator!=(const TextureReplacementHash& rhs) const { return low != rhs.low || high != rhs.high; } +}; + +namespace std { +template<> +struct hash +{ + size_t operator()(const TextureReplacementHash& h) const + { + size_t hash_hash = std::hash{}(h.low); + hash_combine(hash_hash, h.high); + return hash_hash; + } +}; +} // namespace std + +using TextureReplacementTexture = Common::RGBA8Image; + +class TextureReplacements +{ +public: + enum class ReplacmentType + { + VRAMWrite + }; + + TextureReplacements(); + ~TextureReplacements(); + + const std::string GetGameID() const { return m_game_id; } + void SetGameID(std::string game_id); + + void Reload(); + + const TextureReplacementTexture* GetVRAMWriteReplacement(u32 width, u32 height, const void* pixels); + void DumpVRAMWrite(u32 width, u32 height, const void* pixels); + + void Shutdown(); + +private: + struct ReplacementHashMapHash + { + size_t operator()(const TextureReplacementHash& hash); + }; + + using VRAMWriteReplacementMap = std::unordered_map; + using TextureCache = std::unordered_map; + + static bool ParseReplacementFilename(const std::string& filename, TextureReplacementHash* replacement_hash, + ReplacmentType* replacement_type); + + std::string GetSourceDirectory() const; + + TextureReplacementHash GetVRAMWriteHash(u32 width, u32 height, const void* pixels) const; + std::string GetVRAMWriteDumpFilename(u32 width, u32 height, const void* pixels) const; + + void FindTextures(const std::string& dir); + + const TextureReplacementTexture* LoadTexture(const std::string& filename); + void PreloadTextures(); + void PurgeUnreferencedTexturesFromCache(); + + std::string m_game_id; + + TextureCache m_texture_cache; + + VRAMWriteReplacementMap m_vram_write_replacements; +}; + +extern TextureReplacements g_texture_replacements; \ No newline at end of file