GPU: Support replacing VRAM writes with new textures

This commit is contained in:
Connor McLaughlin 2020-12-25 18:02:38 +10:00
parent a66f14b5c3
commit 8db961042a
15 changed files with 680 additions and 26 deletions

View File

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

View File

@ -149,6 +149,7 @@
<ClCompile Include="sio.cpp" />
<ClCompile Include="spu.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="texture_replacements.cpp" />
<ClCompile Include="timers.cpp" />
<ClCompile Include="timing_event.cpp" />
</ItemGroup>
@ -226,6 +227,7 @@
<ClInclude Include="sio.h" />
<ClInclude Include="spu.h" />
<ClInclude Include="system.h" />
<ClInclude Include="texture_replacements.h" />
<ClInclude Include="timers.h" />
<ClInclude Include="timing_event.h" />
<ClInclude Include="types.h" />
@ -246,6 +248,9 @@
<ProjectReference Include="..\..\dep\vulkan-loader\vulkan-loader.vcxproj">
<Project>{9c8ddeb0-2b8f-4f5f-ba86-127cdf27f035}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\xxhash\xxhash.vcxproj">
<Project>{09553c96-9f39-49bf-8ae6-7acbd07c410c}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\zlib\zlib.vcxproj">
<Project>{7ff9fdb9-d504-47db-a16a-b08071999620}</Project>
</ProjectReference>
@ -464,7 +469,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -490,7 +495,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -516,7 +521,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -542,7 +547,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -571,7 +576,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -600,7 +605,7 @@
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -628,7 +633,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -655,7 +660,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -683,7 +688,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -710,7 +715,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -737,7 +742,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_MMAP_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -765,7 +770,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_IMGUI=1;WITH_RECOMPILER=1;WITH_FASTMEM=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(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)</AdditionalIncludeDirectories>
<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)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>

View File

@ -55,6 +55,7 @@
<ClCompile Include="gpu_backend.cpp" />
<ClCompile Include="gpu_sw_backend.cpp" />
<ClCompile Include="libcrypt_game_codes.cpp" />
<ClCompile Include="texture_replacements.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -113,5 +114,6 @@
<ClInclude Include="gpu_backend.h" />
<ClInclude Include="gpu_sw_backend.h" />
<ClInclude Include="libcrypt_game_codes.h" />
<ClInclude Include="texture_replacements.h" />
</ItemGroup>
</Project>

View File

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

View File

@ -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<const u8*>(tex->GetPixels());
u8* dst_ptr = static_cast<u8*>(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<u32> 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));

View File

@ -4,6 +4,7 @@
#include "common/d3d11/stream_buffer.h"
#include "common/d3d11/texture.h"
#include "gpu_hw.h"
#include "texture_replacements.h"
#include <array>
#include <d3d11.h>
#include <memory>
@ -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<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context;
@ -118,4 +121,6 @@ private:
ComPtr<ID3D11PixelShader> m_vram_copy_pixel_shader;
ComPtr<ID3D11PixelShader> m_vram_update_depth_pixel_shader;
std::array<std::array<ComPtr<ID3D11PixelShader>, 3>, 2> m_display_pixel_shaders; // [depth_24][interlaced]
D3D11::Texture m_vram_replacement_texture;
};

View File

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

View File

@ -5,6 +5,7 @@
#include "common/gl/texture.h"
#include "glad.h"
#include "gpu_hw.h"
#include "texture_replacements.h"
#include <array>
#include <memory>
#include <tuple>
@ -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<GL::StreamBuffer> m_vertex_stream_buffer;
GLuint m_vram_fbo_id = 0;

View File

@ -1148,6 +1148,16 @@ void GPU_HW_Vulkan::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
const Common::Rectangle<u32> 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<u32>(sizeof(u16), static_cast<u32>(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<u32>(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<int32_t>(tex->GetWidth()), static_cast<int32_t>(tex->GetHeight()), 1},
},
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 0u, 1u},
{{static_cast<int32_t>(dst_x), static_cast<int32_t>(dst_y), 0},
{static_cast<int32_t>(dst_x + width), static_cast<int32_t>(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> GPU::CreateHardwareVulkanRenderer()
{
return std::make_unique<GPU_HW_Vulkan>();

View File

@ -4,6 +4,7 @@
#include "common/vulkan/stream_buffer.h"
#include "common/vulkan/texture.h"
#include "gpu_hw.h"
#include "texture_replacements.h"
#include <array>
#include <memory>
#include <tuple>
@ -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<VkPipeline, 3, 2> 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;
};

View File

@ -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<const char*, LOGLEVEL_COUNT> s_log_level_names = {
@ -635,8 +657,8 @@ static std::array<const char*, 13> 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<float, 13> 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<DisplayAspectRatio> Settings::ParseDisplayAspectRatio(const char* str)
{

View File

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

View File

@ -27,6 +27,7 @@
#include "save_state_version.h"
#include "sio.h"
#include "spu.h"
#include "texture_replacements.h"
#include "timers.h"
#include <cctype>
#include <cstdio>
@ -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();
}

View File

@ -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 <cinttypes>
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<u64> high_value = StringUtil::FromChars<u64>(sv.substr(0, 16), 16);
std::optional<u64> low_value = StringUtil::FromChars<u64>(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<const u16*>(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<size_t>(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<u32>(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<int>(total_textures), \
static_cast<int>(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
}

View File

@ -0,0 +1,89 @@
#pragma once
#include "common/hash_combine.h"
#include "common/image.h"
#include "types.h"
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
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<TextureReplacementHash>
{
size_t operator()(const TextureReplacementHash& h) const
{
size_t hash_hash = std::hash<u64>{}(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<TextureReplacementHash, std::string>;
using TextureCache = std::unordered_map<std::string, TextureReplacementTexture>;
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;