GPU: Add hardware texture cache
This commit is contained in:
parent
4132b5ef3d
commit
e06f1f1002
|
@ -2998,6 +2998,10 @@ public:
|
|||
return GSVector4i(vsetq_lane_s32(high, vsetq_lane_s32(low, vdupq_n_s32(0), 0), 1));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE GSVector2 xy() const { return GSVector2(vget_low_s32(v4s)); }
|
||||
|
||||
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(vget_high_s32(v4s)); }
|
||||
|
||||
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
|
||||
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const \
|
||||
{ \
|
||||
|
|
|
@ -2289,6 +2289,10 @@ public:
|
|||
return GSVector4i(static_cast<s32>(F64[0]), static_cast<s32>(F64[1]), 0, 0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE GSVector2 xy() const { return GSVector2(x, y); }
|
||||
|
||||
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(z, w); }
|
||||
|
||||
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
|
||||
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const { return GSVector4(F32[xn], F32[yn], F32[zn], F32[wn]); }
|
||||
|
||||
|
|
|
@ -2365,6 +2365,10 @@ public:
|
|||
|
||||
ALWAYS_INLINE GSVector4i f64toi32() const { return GSVector4i(_mm_cvttpd_epi32(_mm_castps_pd(m))); }
|
||||
|
||||
ALWAYS_INLINE GSVector2 xy() const { return GSVector2(m); }
|
||||
|
||||
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(_mm_shuffle_ps(m, m, _MM_SHUFFLE(3, 2, 3, 2))); }
|
||||
|
||||
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
|
||||
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const \
|
||||
{ \
|
||||
|
|
|
@ -51,6 +51,8 @@ add_library(core
|
|||
gpu_hw.h
|
||||
gpu_hw_shadergen.cpp
|
||||
gpu_hw_shadergen.h
|
||||
gpu_hw_texture_cache.cpp
|
||||
gpu_hw_texture_cache.h
|
||||
gpu_shadergen.cpp
|
||||
gpu_shadergen.h
|
||||
gpu_sw.cpp
|
||||
|
@ -108,8 +110,6 @@ add_library(core
|
|||
spu.h
|
||||
system.cpp
|
||||
system.h
|
||||
texture_replacements.cpp
|
||||
texture_replacements.h
|
||||
timers.cpp
|
||||
timers.h
|
||||
timing_event.cpp
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<ClCompile Include="gpu_backend.cpp" />
|
||||
<ClCompile Include="gpu_commands.cpp" />
|
||||
<ClCompile Include="gpu_hw_shadergen.cpp" />
|
||||
<ClCompile Include="gpu_hw_texture_cache.cpp" />
|
||||
<ClCompile Include="gpu_shadergen.cpp" />
|
||||
<ClCompile Include="gpu_sw.cpp" />
|
||||
<ClCompile Include="gpu_sw_backend.cpp" />
|
||||
|
@ -81,7 +82,6 @@
|
|||
<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>
|
||||
|
@ -124,6 +124,7 @@
|
|||
<ClInclude Include="gdb_server.h" />
|
||||
<ClInclude Include="gpu_backend.h" />
|
||||
<ClInclude Include="gpu_hw_shadergen.h" />
|
||||
<ClInclude Include="gpu_hw_texture_cache.h" />
|
||||
<ClInclude Include="gpu_shadergen.h" />
|
||||
<ClInclude Include="gpu_sw.h" />
|
||||
<ClInclude Include="gpu_sw_backend.h" />
|
||||
|
@ -161,7 +162,6 @@
|
|||
<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" />
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
<ClCompile Include="cpu_recompiler_code_generator_aarch32.cpp" />
|
||||
<ClCompile Include="gpu_backend.cpp" />
|
||||
<ClCompile Include="gpu_sw_backend.cpp" />
|
||||
<ClCompile Include="texture_replacements.cpp" />
|
||||
<ClCompile Include="multitap.cpp" />
|
||||
<ClCompile Include="host.cpp" />
|
||||
<ClCompile Include="game_database.cpp" />
|
||||
|
@ -67,6 +66,7 @@
|
|||
<ClCompile Include="justifier.cpp" />
|
||||
<ClCompile Include="gdb_server.cpp" />
|
||||
<ClCompile Include="gpu_sw_rasterizer.cpp" />
|
||||
<ClCompile Include="gpu_hw_texture_cache.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -116,7 +116,6 @@
|
|||
<ClInclude Include="gpu_types.h" />
|
||||
<ClInclude Include="gpu_backend.h" />
|
||||
<ClInclude Include="gpu_sw_backend.h" />
|
||||
<ClInclude Include="texture_replacements.h" />
|
||||
<ClInclude Include="multitap.h" />
|
||||
<ClInclude Include="host.h" />
|
||||
<ClInclude Include="achievements.h" />
|
||||
|
@ -140,6 +139,7 @@
|
|||
<ClInclude Include="justifier.h" />
|
||||
<ClInclude Include="gdb_server.h" />
|
||||
<ClInclude Include="gpu_sw_rasterizer.h" />
|
||||
<ClInclude Include="gpu_hw_texture_cache.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="gpu_sw_rasterizer.inl" />
|
||||
|
|
|
@ -4502,23 +4502,45 @@ void FullscreenUI::DrawDisplaySettingsPage()
|
|||
|
||||
MenuHeading(FSUI_CSTR("Texture Replacements"));
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable VRAM Write Texture Replacement"),
|
||||
FSUI_CSTR("Enables the replacement of background textures in supported games."),
|
||||
"TextureReplacements", "EnableVRAMWriteReplacements", false);
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Preload Replacement Textures"),
|
||||
FSUI_CSTR("Loads all replacement texture to RAM, reducing stuttering at runtime."),
|
||||
"TextureReplacements", "PreloadTextures", false);
|
||||
ActiveButton(FSUI_CSTR("The texture cache is currently experimental, and may cause rendering errors in some games."),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, g_large_font);
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable Texture Cache"),
|
||||
FSUI_CSTR("Enables caching of guest textures, required for texture replacement."), "GPU",
|
||||
"EnableTextureCache", false);
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Use Old MDEC Routines"),
|
||||
FSUI_CSTR("Enables the older, less accurate MDEC decoding routines. May be required for old "
|
||||
"replacement backgrounds to match/load."),
|
||||
"Hacks", "UseOldMDECRoutines", false);
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Dump Replaceable VRAM Writes"),
|
||||
FSUI_CSTR("Writes textures which can be replaced to the dump directory."), "TextureReplacements",
|
||||
const bool texture_cache_enabled = GetEffectiveBoolSetting(bsi, "GPU", "EnableTextureCache", false);
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable Texture Replacements"),
|
||||
FSUI_CSTR("Enables loading of replacement textures. Not compatible with all games."),
|
||||
"TextureReplacements", "EnableTextureReplacements", false, texture_cache_enabled);
|
||||
DrawToggleSetting(
|
||||
bsi, FSUI_CSTR("Enable Texture Dumping"),
|
||||
FSUI_CSTR("Enables dumping of textures to image files, which can be replaced. Not compatible with all games."),
|
||||
"TextureReplacements", "DumpTextures", false, texture_cache_enabled);
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Dump Replaced Textures"),
|
||||
FSUI_CSTR("Dumps textures that have replacements already loaded."), "TextureReplacements",
|
||||
"DumpReplacedTextures", false, texture_cache_enabled);
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable VRAM Write Texture Replacement"),
|
||||
FSUI_CSTR("Enables the replacement of background textures in supported games."),
|
||||
"TextureReplacements", "EnableVRAMWriteReplacements", false);
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Enable VRAM Write Dumping"),
|
||||
FSUI_CSTR("Writes backgrounds that can be replaced to the dump directory."), "TextureReplacements",
|
||||
"DumpVRAMWrites", false);
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Set VRAM Write Dump Alpha Channel"),
|
||||
FSUI_CSTR("Clears the mask/transparency bit in VRAM write dumps."), "TextureReplacements",
|
||||
"DumpVRAMWriteForceAlphaChannel", true);
|
||||
|
||||
DrawToggleSetting(bsi, FSUI_CSTR("Preload Replacement Textures"),
|
||||
FSUI_CSTR("Loads all replacement texture to RAM, reducing stuttering at runtime."),
|
||||
"TextureReplacements", "PreloadTextures", false,
|
||||
((texture_cache_enabled &&
|
||||
GetEffectiveBoolSetting(bsi, "TextureReplacements", "EnableTextureReplacements", false)) ||
|
||||
GetEffectiveBoolSetting(bsi, "TextureReplacements", "EnableVRAMWriteReplacements", false)));
|
||||
|
||||
DrawFolderSetting(bsi, FSUI_CSTR("Textures Directory"), "Folders", "Textures", EmuFolders::Textures);
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
|
@ -7239,7 +7261,6 @@ TRANSLATE_NOOP("FullscreenUI", "Clear Settings");
|
|||
TRANSLATE_NOOP("FullscreenUI", "Clear Shaders");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Clears a shader from the chain.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Clears all settings set for this game.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Clears the mask/transparency bit in VRAM write dumps.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Close");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Close Game");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Close Menu");
|
||||
|
@ -7319,7 +7340,8 @@ TRANSLATE_NOOP("FullscreenUI", "Downsampling");
|
|||
TRANSLATE_NOOP("FullscreenUI", "Downsampling Display Scale");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Duck icon by icons8 (https://icons8.com/icon/74847/platforms.undefined.short-title)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "DuckStation is a free simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Dump Replaceable VRAM Writes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Dump Replaced Textures");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Dumps textures that have replacements already loaded.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulation Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulation Speed");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable 8MB RAM");
|
||||
|
@ -7338,6 +7360,10 @@ TRANSLATE_NOOP("FullscreenUI", "Enable Rewinding");
|
|||
TRANSLATE_NOOP("FullscreenUI", "Enable SDL Input Source");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Subdirectory Scanning");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable TTY Logging");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Texture Cache");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Texture Dumping");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Texture Replacements");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable VRAM Write Dumping");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable VRAM Write Texture Replacement");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable XInput Input Source");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable debugging when supported by the host's renderer API. Only for developer use.");
|
||||
|
@ -7345,6 +7371,9 @@ TRANSLATE_NOOP("FullscreenUI", "Enable/Disable the Player LED on DualSense contr
|
|||
TRANSLATE_NOOP("FullscreenUI", "Enables alignment and bus exceptions. Not needed for any known games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables an additional three controller slots on each port. Not supported in all games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables caching of guest textures, required for texture replacement.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables dumping of textures to image files, which can be replaced. Not compatible with all games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables loading of replacement textures. Not compatible with all games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables smooth scrolling of menus in Big Picture UI.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables the older, less accurate MDEC decoding routines. May be required for old replacement backgrounds to match/load.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables the replacement of background textures in supported games.");
|
||||
|
@ -7626,7 +7655,6 @@ TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to
|
|||
TRANSLATE_NOOP("FullscreenUI", "Serial");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Session: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Set Input Binding");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Set VRAM Write Dump Alpha Channel");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sets a threshold for discarding precise values when exceeded. May help with glitches in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sets a threshold for discarding the emulated depth buffer. May help in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sets the fast forward speed. It is not guaranteed that this speed will be reached on all systems.");
|
||||
|
@ -7705,10 +7733,12 @@ TRANSLATE_NOOP("FullscreenUI", "Temporarily disables all enhancements, useful wh
|
|||
TRANSLATE_NOOP("FullscreenUI", "Test Unofficial Achievements");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Filtering");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Replacements");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Textures Directory");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The SDL input source supports most controllers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The XInput source provides support for XBox 360/XBox One/XBox Series controllers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The audio backend determines how frames produced by the emulator are submitted to the host.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The selected memory card image will be used in shared mode for this slot.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "The texture cache is currently experimental, and may cause rendering errors in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This game has no achievements.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This game has no leaderboards.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Threaded Rendering");
|
||||
|
@ -7763,7 +7793,7 @@ TRANSLATE_NOOP("FullscreenUI", "When playing a multi-disc game and using per-gam
|
|||
TRANSLATE_NOOP("FullscreenUI", "When this option is chosen, the clock speed set below will be used.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Widescreen Rendering");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Wireframe Rendering");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Writes textures which can be replaced to the dump directory.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Writes backgrounds that can be replaced to the dump directory.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Yes, {} now and risk memory card corruption.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "\"PlayStation\" and \"PSX\" are registered trademarks of Sony Interactive Entertainment Europe Limited. This software is not affiliated in any way with Sony Interactive Entertainment.");
|
||||
|
|
|
@ -1555,8 +1555,8 @@ void GPU::SetDrawMode(u16 value)
|
|||
if (new_mode_reg.bits == m_draw_mode.mode_reg.bits)
|
||||
return;
|
||||
|
||||
m_draw_mode.texture_page_changed |= ((new_mode_reg.bits & GPUDrawModeReg::TEXTURE_PAGE_MASK) !=
|
||||
(m_draw_mode.mode_reg.bits & GPUDrawModeReg::TEXTURE_PAGE_MASK));
|
||||
m_draw_mode.texture_page_changed |= ((new_mode_reg.bits & GPUDrawModeReg::TEXTURE_MODE_AND_PAGE_MASK) !=
|
||||
(m_draw_mode.mode_reg.bits & GPUDrawModeReg::TEXTURE_MODE_AND_PAGE_MASK));
|
||||
m_draw_mode.mode_reg.bits = new_mode_reg.bits;
|
||||
|
||||
if (m_GPUSTAT.draw_to_displayed_field != new_mode_reg.draw_to_displayed_field)
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "gpu.h"
|
||||
#include "gpu_hw_texture_cache.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "system.h"
|
||||
#include "texture_replacements.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
|
@ -532,10 +532,10 @@ void GPU::FinishVRAMWrite()
|
|||
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))
|
||||
if (GPUTextureCache::ShouldDumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height))
|
||||
{
|
||||
TextureReplacements::DumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height,
|
||||
reinterpret_cast<const u16*>(m_blit_buffer.data()));
|
||||
GPUTextureCache::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,
|
||||
|
|
|
@ -194,6 +194,8 @@ GPU_HW::GPU_HW() : GPU()
|
|||
|
||||
GPU_HW::~GPU_HW()
|
||||
{
|
||||
GPUTextureCache::Shutdown();
|
||||
|
||||
if (m_sw_renderer)
|
||||
{
|
||||
m_sw_renderer->Shutdown();
|
||||
|
@ -262,6 +264,8 @@ bool GPU_HW::Initialize()
|
|||
m_clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering);
|
||||
m_compute_uv_range = m_clamp_uvs;
|
||||
m_allow_sprite_mode = ShouldAllowSpriteMode(m_resolution_scale, m_texture_filtering, m_sprite_texture_filtering);
|
||||
m_use_texture_cache = g_settings.gpu_texture_cache;
|
||||
m_texture_dumping = m_use_texture_cache && g_settings.texture_replacements.dump_textures;
|
||||
|
||||
CheckSettings();
|
||||
|
||||
|
@ -282,13 +286,27 @@ bool GPU_HW::Initialize()
|
|||
return false;
|
||||
}
|
||||
|
||||
if (m_use_texture_cache)
|
||||
{
|
||||
if (!GPUTextureCache::Initialize())
|
||||
{
|
||||
ERROR_LOG("Failed to initialize texture cache, disabling.");
|
||||
m_use_texture_cache = false;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDownsamplingLevels();
|
||||
|
||||
RestoreDeviceContext();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GPU_HW::Reset(bool clear_vram)
|
||||
{
|
||||
// Texture cache needs to be invalidated before we load, otherwise we dump black.
|
||||
if (m_use_texture_cache)
|
||||
GPUTextureCache::Invalidate();
|
||||
|
||||
if (m_batch_vertex_ptr)
|
||||
UnmapGPUBuffer(0, 0);
|
||||
|
||||
|
@ -364,6 +382,7 @@ bool GPU_HW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
|||
else if (sw.IsReading())
|
||||
{
|
||||
// Need to update the VRAM copy on the GPU with the state data.
|
||||
// Would invalidate the TC, but base DoState() calls Reset().
|
||||
UpdateVRAMOnGPU(0, 0, VRAM_WIDTH, VRAM_HEIGHT, g_vram, VRAM_WIDTH * sizeof(u16), false, false, VRAM_SIZE_RECT);
|
||||
}
|
||||
|
||||
|
@ -373,10 +392,12 @@ bool GPU_HW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
|||
DebugAssert(!m_batch_vertex_ptr && !m_batch_index_ptr);
|
||||
ClearVRAMDirtyRectangle();
|
||||
SetFullVRAMDirtyRectangle();
|
||||
UpdateVRAMReadTexture(true, false);
|
||||
ClearVRAMDirtyRectangle();
|
||||
ResetBatchVertexDepth();
|
||||
}
|
||||
|
||||
return true;
|
||||
return GPUTextureCache::DoState(sw, !m_use_texture_cache);
|
||||
}
|
||||
|
||||
void GPU_HW::RestoreDeviceContext()
|
||||
|
@ -401,7 +422,8 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
|||
const bool clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering);
|
||||
const bool framebuffer_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples ||
|
||||
g_settings.IsUsingAccurateBlending() != old_settings.IsUsingAccurateBlending() ||
|
||||
m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer());
|
||||
m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() ||
|
||||
(!old_settings.gpu_texture_cache && g_settings.gpu_texture_cache));
|
||||
const bool shaders_changed =
|
||||
(m_resolution_scale != resolution_scale || m_multisamples != multisamples ||
|
||||
m_true_color != g_settings.gpu_true_color || prev_force_progressive_scan != m_force_progressive_scan ||
|
||||
|
@ -468,6 +490,8 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
|||
m_clamp_uvs = clamp_uvs;
|
||||
m_compute_uv_range = m_clamp_uvs;
|
||||
m_allow_sprite_mode = ShouldAllowSpriteMode(resolution_scale, m_texture_filtering, m_sprite_texture_filtering);
|
||||
m_use_texture_cache = g_settings.gpu_texture_cache;
|
||||
m_texture_dumping = m_use_texture_cache && g_settings.texture_replacements.dump_textures;
|
||||
m_batch.sprite_mode = (m_allow_sprite_mode && m_batch.sprite_mode);
|
||||
|
||||
const bool depth_buffer_changed = (m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer());
|
||||
|
@ -521,6 +545,23 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
|||
UpdateDepthBufferFromMaskBit();
|
||||
}
|
||||
|
||||
if (m_use_texture_cache && !old_settings.gpu_texture_cache)
|
||||
{
|
||||
if (!GPUTextureCache::Initialize())
|
||||
{
|
||||
ERROR_LOG("Failed to initialize texture cache, disabling.");
|
||||
m_use_texture_cache = false;
|
||||
}
|
||||
}
|
||||
else if (!m_use_texture_cache && old_settings.gpu_texture_cache)
|
||||
{
|
||||
GPUTextureCache::Shutdown();
|
||||
}
|
||||
else if (m_use_texture_cache)
|
||||
{
|
||||
GPUTextureCache::UpdateSettings(old_settings);
|
||||
}
|
||||
|
||||
if (g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
|
||||
(g_settings.gpu_downsample_mode == GPUDownsampleMode::Box &&
|
||||
g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale))
|
||||
|
@ -728,6 +769,9 @@ void GPU_HW::AddWrittenRectangle(const GSVector4i rect)
|
|||
{
|
||||
m_vram_dirty_write_rect = m_vram_dirty_write_rect.runion(rect);
|
||||
SetTexPageChangedOnOverlap(m_vram_dirty_write_rect);
|
||||
|
||||
if (m_use_texture_cache)
|
||||
GPUTextureCache::AddWrittenRectangle(rect);
|
||||
}
|
||||
|
||||
void GPU_HW::AddDrawnRectangle(const GSVector4i rect)
|
||||
|
@ -735,13 +779,22 @@ void GPU_HW::AddDrawnRectangle(const GSVector4i rect)
|
|||
// Normally, we would check for overlap here. But the GPU's texture cache won't actually reload until the page
|
||||
// changes, or it samples a larger region, so we can get away without doing so. This reduces copies considerably in
|
||||
// games like Mega Man Legends 2.
|
||||
m_vram_dirty_draw_rect = m_vram_dirty_draw_rect.runion(rect);
|
||||
if (m_current_draw_rect.rcontains(rect))
|
||||
return;
|
||||
|
||||
m_current_draw_rect = m_current_draw_rect.runion(rect);
|
||||
m_vram_dirty_draw_rect = m_vram_dirty_draw_rect.runion(m_current_draw_rect);
|
||||
|
||||
if (m_use_texture_cache)
|
||||
GPUTextureCache::AddDrawnRectangle(m_current_draw_rect, m_clamped_drawing_area);
|
||||
}
|
||||
|
||||
void GPU_HW::AddUnclampedDrawnRectangle(const GSVector4i rect)
|
||||
{
|
||||
m_vram_dirty_draw_rect = m_vram_dirty_draw_rect.runion(rect);
|
||||
SetTexPageChangedOnOverlap(m_vram_dirty_draw_rect);
|
||||
if (m_use_texture_cache)
|
||||
GPUTextureCache::AddDrawnRectangle(rect, rect);
|
||||
}
|
||||
|
||||
void GPU_HW::SetTexPageChangedOnOverlap(const GSVector4i update_rect)
|
||||
|
@ -749,9 +802,9 @@ void GPU_HW::SetTexPageChangedOnOverlap(const GSVector4i update_rect)
|
|||
// the vram area can include the texture page, but the game can leave it as-is. in this case, set it as dirty so the
|
||||
// shadow texture is updated
|
||||
if (!m_draw_mode.IsTexturePageChanged() && m_batch.texture_mode != BatchTextureMode::Disabled &&
|
||||
(m_draw_mode.mode_reg.GetTexturePageRectangle().rintersects(update_rect) ||
|
||||
(GetTextureRect(m_draw_mode.mode_reg.texture_page, m_draw_mode.mode_reg.texture_mode).rintersects(update_rect) ||
|
||||
(m_draw_mode.mode_reg.IsUsingPalette() &&
|
||||
m_draw_mode.palette_reg.GetRectangle(m_draw_mode.mode_reg.texture_mode).rintersects(update_rect))))
|
||||
GetPaletteRect(m_draw_mode.palette_reg, m_draw_mode.mode_reg.texture_mode).rintersects(update_rect))))
|
||||
{
|
||||
m_draw_mode.SetTexturePageChanged();
|
||||
}
|
||||
|
@ -882,6 +935,8 @@ void GPU_HW::ClearFramebuffer()
|
|||
g_gpu_device->ClearDepth(m_vram_depth_texture.get(), m_pgxp_depth_buffer ? 1.0f : 0.0f);
|
||||
}
|
||||
ClearVRAMDirtyRectangle();
|
||||
if (m_use_texture_cache)
|
||||
GPUTextureCache::Invalidate();
|
||||
m_last_depth_z = 1.0f;
|
||||
}
|
||||
|
||||
|
@ -986,7 +1041,7 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
const u32 active_texture_modes =
|
||||
m_allow_sprite_mode ? NUM_TEXTURE_MODES :
|
||||
(NUM_TEXTURE_MODES - (NUM_TEXTURE_MODES - static_cast<u32>(BatchTextureMode::SpriteStart)));
|
||||
const u32 total_vertex_shaders = (m_allow_sprite_mode ? 5 : 3);
|
||||
const u32 total_vertex_shaders = (m_allow_sprite_mode ? 7 : 3);
|
||||
const u32 total_fragment_shaders =
|
||||
((needs_rov_depth ? 2 : 1) * 5 * 5 * active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
|
||||
(1 + BoolToUInt32(!m_force_progressive_scan)) * (1 + BoolToUInt32(needs_rov_depth)));
|
||||
|
@ -1013,7 +1068,7 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
// vertex shaders - [textured/palette/sprite]
|
||||
// fragment shaders - [depth_test][render_mode][transparency_mode][texture_mode][check_mask][dithering][interlacing]
|
||||
static constexpr auto destroy_shader = [](std::unique_ptr<GPUShader>& s) { s.reset(); };
|
||||
DimensionalArray<std::unique_ptr<GPUShader>, 2, 2, 2> batch_vertex_shaders{};
|
||||
DimensionalArray<std::unique_ptr<GPUShader>, 2, 3, 2> batch_vertex_shaders{};
|
||||
DimensionalArray<std::unique_ptr<GPUShader>, 2, 2, 2, NUM_TEXTURE_MODES, 5, 5, 2> batch_fragment_shaders{};
|
||||
ScopedGuard batch_shader_guard([&batch_vertex_shaders, &batch_fragment_shaders]() {
|
||||
batch_vertex_shaders.enumerate(destroy_shader);
|
||||
|
@ -1022,7 +1077,7 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
|
||||
for (u8 textured = 0; textured < 2; textured++)
|
||||
{
|
||||
for (u8 palette = 0; palette < 2; palette++)
|
||||
for (u8 palette = 0; palette < 3; palette++)
|
||||
{
|
||||
if (palette && !textured)
|
||||
continue;
|
||||
|
@ -1034,7 +1089,7 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
|
||||
const bool uv_limits = ShouldClampUVs(sprite ? m_sprite_texture_filtering : m_texture_filtering);
|
||||
const std::string vs = shadergen.GenerateBatchVertexShader(
|
||||
textured != 0, palette != 0, uv_limits, !sprite && force_round_texcoords, m_pgxp_depth_buffer);
|
||||
textured != 0, palette == 1, palette == 2, uv_limits, !sprite && force_round_texcoords, m_pgxp_depth_buffer);
|
||||
if (!(batch_vertex_shaders[textured][palette][sprite] =
|
||||
g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(), vs, error)))
|
||||
{
|
||||
|
@ -1203,6 +1258,8 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
static_cast<BatchTextureMode>(texture_mode) == BatchTextureMode::Palette8Bit ||
|
||||
static_cast<BatchTextureMode>(texture_mode) == BatchTextureMode::SpritePalette4Bit ||
|
||||
static_cast<BatchTextureMode>(texture_mode) == BatchTextureMode::SpritePalette8Bit);
|
||||
const bool page_texture =
|
||||
(static_cast<BatchTextureMode>(texture_mode) == BatchTextureMode::PageTexture);
|
||||
const bool sprite = (static_cast<BatchTextureMode>(texture_mode) >= BatchTextureMode::SpriteStart);
|
||||
const bool uv_limits = ShouldClampUVs(sprite ? m_sprite_texture_filtering : m_texture_filtering);
|
||||
const bool use_shader_blending = (render_mode == static_cast<u8>(BatchRenderMode::ShaderBlend));
|
||||
|
@ -1216,7 +1273,9 @@ bool GPU_HW::CompilePipelines(Error* error)
|
|||
std::span<const GPUPipeline::VertexAttribute>(vertex_attributes, NUM_BATCH_VERTEX_ATTRIBUTES);
|
||||
|
||||
plconfig.vertex_shader =
|
||||
batch_vertex_shaders[BoolToUInt8(textured)][BoolToUInt8(palette)][BoolToUInt8(sprite)].get();
|
||||
batch_vertex_shaders[BoolToUInt8(textured)][page_texture ? 2 : BoolToUInt8(palette)]
|
||||
[BoolToUInt8(sprite)]
|
||||
.get();
|
||||
plconfig.fragment_shader =
|
||||
batch_fragment_shaders[BoolToUInt8(depth_test && needs_rov_depth)][render_mode]
|
||||
[use_shader_blending ? transparency_mode :
|
||||
|
@ -1836,19 +1895,26 @@ void GPU_HW::UnmapGPUBuffer(u32 used_vertices, u32 used_indices)
|
|||
}
|
||||
|
||||
ALWAYS_INLINE_RELEASE void GPU_HW::DrawBatchVertices(BatchRenderMode render_mode, u32 num_indices, u32 base_index,
|
||||
u32 base_vertex)
|
||||
u32 base_vertex, const GPUTextureCache::Source* texture)
|
||||
{
|
||||
// [depth_test][transparency_mode][render_mode][texture_mode][dithering][interlacing][check_mask]
|
||||
const u8 texture_mode = static_cast<u8>(m_batch.texture_mode) +
|
||||
((m_batch.texture_mode != BatchTextureMode::Disabled && m_batch.sprite_mode) ?
|
||||
static_cast<u8>(BatchTextureMode::SpriteStart) :
|
||||
0);
|
||||
const u8 texture_mode = texture ? static_cast<u8>(BatchTextureMode::PageTexture) :
|
||||
(static_cast<u8>(m_batch.texture_mode) +
|
||||
((m_batch.texture_mode < BatchTextureMode::PageTexture && m_batch.sprite_mode) ?
|
||||
static_cast<u8>(BatchTextureMode::SpriteStart) :
|
||||
0));
|
||||
const u8 depth_test = BoolToUInt8(m_batch.use_depth_buffer);
|
||||
const u8 check_mask = BoolToUInt8(m_batch.check_mask_before_draw);
|
||||
g_gpu_device->SetPipeline(m_batch_pipelines[depth_test][static_cast<u8>(m_batch.transparency_mode)][static_cast<u8>(
|
||||
render_mode)][texture_mode][BoolToUInt8(m_batch.dithering)][BoolToUInt8(m_batch.interlacing)][check_mask]
|
||||
.get());
|
||||
|
||||
if (m_use_texture_cache && texture_mode != static_cast<u8>(BatchTextureMode::Disabled))
|
||||
{
|
||||
g_gpu_device->SetTextureSampler(0, texture ? texture->texture : m_vram_read_texture.get(),
|
||||
g_gpu_device->GetNearestSampler());
|
||||
}
|
||||
|
||||
GL_INS_FMT("Texture mode: {}", s_batch_texture_modes[texture_mode]);
|
||||
GL_INS_FMT("Transparency mode: {}", s_transparency_modes[static_cast<u8>(m_batch.transparency_mode)]);
|
||||
GL_INS_FMT("Render mode: {}", s_batch_render_modes[static_cast<u8>(render_mode)]);
|
||||
|
@ -2199,7 +2265,7 @@ void GPU_HW::ComputePolygonUVLimits(BatchVertex* vertices, u32 num_vertices)
|
|||
for (u32 i = 0; i < num_vertices; i++)
|
||||
vertices[i].SetUVLimits(min_u, max_u, min_v, max_v);
|
||||
|
||||
if (m_texpage_dirty != 0)
|
||||
if (ShouldCheckForTexPageOverlap())
|
||||
CheckForTexPageOverlap(GSVector4i(min).upl32(GSVector4i(max)).u16to32());
|
||||
}
|
||||
|
||||
|
@ -2620,7 +2686,7 @@ void GPU_HW::LoadVertices()
|
|||
const u32 tex_right = tex_left + quad_width;
|
||||
const u32 uv_limits = BatchVertex::PackUVLimits(tex_left, tex_right - 1, tex_top, tex_bottom - 1);
|
||||
|
||||
if (rc.texture_enable && m_texpage_dirty != 0)
|
||||
if (rc.texture_enable && ShouldCheckForTexPageOverlap())
|
||||
{
|
||||
CheckForTexPageOverlap(GSVector4i(static_cast<s32>(tex_left), static_cast<s32>(tex_top),
|
||||
static_cast<s32>(tex_right), static_cast<s32>(tex_bottom)));
|
||||
|
@ -2801,7 +2867,7 @@ void GPU_HW::LoadVertices()
|
|||
}
|
||||
}
|
||||
|
||||
bool GPU_HW::BlitVRAMReplacementTexture(const TextureReplacements::ReplacementImage* tex, u32 dst_x, u32 dst_y,
|
||||
bool GPU_HW::BlitVRAMReplacementTexture(const GPUTextureCache::TextureReplacementImage* tex, u32 dst_x, u32 dst_y,
|
||||
u32 width, u32 height)
|
||||
{
|
||||
if (!m_vram_replacement_texture || m_vram_replacement_texture->GetWidth() < tex->GetWidth() ||
|
||||
|
@ -2844,7 +2910,7 @@ bool GPU_HW::BlitVRAMReplacementTexture(const TextureReplacements::ReplacementIm
|
|||
|
||||
ALWAYS_INLINE_RELEASE void GPU_HW::CheckForTexPageOverlap(GSVector4i uv_rect)
|
||||
{
|
||||
DebugAssert(m_texpage_dirty != 0 && m_batch.texture_mode != BatchTextureMode::Disabled);
|
||||
DebugAssert((m_texpage_dirty != 0 || m_texture_dumping) && m_batch.texture_mode != BatchTextureMode::Disabled);
|
||||
|
||||
if (m_texture_window_active)
|
||||
{
|
||||
|
@ -2871,6 +2937,34 @@ ALWAYS_INLINE_RELEASE void GPU_HW::CheckForTexPageOverlap(GSVector4i uv_rect)
|
|||
m_current_uv_rect = new_uv_rect;
|
||||
|
||||
bool update_drawn = false, update_written = false;
|
||||
if (m_texpage_dirty & TEXPAGE_DIRTY_PAGE_RECT)
|
||||
{
|
||||
DebugAssert(!(m_texpage_dirty & (TEXPAGE_DIRTY_DRAWN_RECT | TEXPAGE_DIRTY_WRITTEN_RECT)));
|
||||
DebugAssert(m_batch.texture_mode == BatchTextureMode::PageTexture &&
|
||||
m_batch.texture_cache_key.page < NUM_VRAM_PAGES);
|
||||
|
||||
if (GPUTextureCache::AreSourcePagesDrawn(m_batch.texture_cache_key, m_current_uv_rect))
|
||||
{
|
||||
// UVs intersect with drawn area, can't use TC
|
||||
if (m_batch_index_count > 0)
|
||||
{
|
||||
FlushRender();
|
||||
EnsureVertexBufferSpaceForCurrentCommand();
|
||||
}
|
||||
|
||||
// We need to swap the dirty tracking over to drawn/written.
|
||||
const GSVector4i page_rect = GetTextureRect(m_batch.texture_cache_key.page, m_batch.texture_cache_key.mode);
|
||||
m_texpage_dirty = (m_vram_dirty_draw_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_DRAWN_RECT : 0) |
|
||||
(m_vram_dirty_write_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_WRITTEN_RECT : 0);
|
||||
m_compute_uv_range = (ShouldCheckForTexPageOverlap() || m_clamp_uvs);
|
||||
m_batch.texture_mode = static_cast<BatchTextureMode>(m_draw_mode.mode_reg.texture_mode.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Page isn't drawn, we're done.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (m_texpage_dirty & TEXPAGE_DIRTY_DRAWN_RECT)
|
||||
{
|
||||
DebugAssert(!m_vram_dirty_draw_rect.eq(INVALID_RECT));
|
||||
|
@ -2905,6 +2999,11 @@ ALWAYS_INLINE_RELEASE void GPU_HW::CheckForTexPageOverlap(GSVector4i uv_rect)
|
|||
}
|
||||
}
|
||||
|
||||
bool GPU_HW::ShouldCheckForTexPageOverlap() const
|
||||
{
|
||||
return (m_texpage_dirty != 0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool GPU_HW::IsFlushed() const
|
||||
{
|
||||
return (m_batch_index_count == 0);
|
||||
|
@ -3003,8 +3102,13 @@ void GPU_HW::UpdateSoftwareRenderer(bool copy_vram_from_hw)
|
|||
{
|
||||
const bool current_enabled = (m_sw_renderer != nullptr);
|
||||
const bool new_enabled = g_settings.gpu_use_software_renderer_for_readbacks;
|
||||
const bool use_thread = !g_settings.gpu_texture_cache;
|
||||
if (current_enabled == new_enabled)
|
||||
{
|
||||
if (m_sw_renderer)
|
||||
m_sw_renderer->SetThreadEnabled(use_thread);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!new_enabled)
|
||||
{
|
||||
|
@ -3015,7 +3119,7 @@ void GPU_HW::UpdateSoftwareRenderer(bool copy_vram_from_hw)
|
|||
}
|
||||
|
||||
std::unique_ptr<GPU_SW_Backend> sw_renderer = std::make_unique<GPU_SW_Backend>();
|
||||
if (!sw_renderer->Initialize(true))
|
||||
if (!sw_renderer->Initialize(use_thread))
|
||||
return;
|
||||
|
||||
// We need to fill in the SW renderer's VRAM with the current state for hot toggles.
|
||||
|
@ -3080,7 +3184,17 @@ void GPU_HW::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color)
|
|||
GL_INS_FMT("Dirty draw area before: {}", m_vram_dirty_draw_rect);
|
||||
|
||||
const GSVector4i bounds = GetVRAMTransferBounds(x, y, width, height);
|
||||
AddUnclampedDrawnRectangle(bounds);
|
||||
|
||||
// If TC is enabled, we have to update local memory.
|
||||
if (m_use_texture_cache && !IsInterlacedRenderingEnabled())
|
||||
{
|
||||
AddWrittenRectangle(bounds);
|
||||
GPU_SW_Rasterizer::FillVRAM(x, y, width, height, color, false, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddUnclampedDrawnRectangle(bounds);
|
||||
}
|
||||
|
||||
GL_INS_FMT("Dirty draw area after: {}", m_vram_dirty_draw_rect);
|
||||
|
||||
|
@ -3126,6 +3240,8 @@ void GPU_HW::ReadVRAM(u32 x, u32 y, u32 width, u32 height)
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Only read if it's in the drawn area
|
||||
|
||||
// Get bounds with wrap-around handled.
|
||||
GSVector4i copy_rect = GetVRAMTransferBounds(x, y, width, height);
|
||||
|
||||
|
@ -3177,7 +3293,17 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b
|
|||
{
|
||||
GL_SCOPE_FMT("UpdateVRAM({},{} => {},{} ({}x{})", x, y, x + width, y + height, width, height);
|
||||
|
||||
if (m_sw_renderer)
|
||||
// TODO: Handle wrapped transfers... break them up or something
|
||||
const GSVector4i bounds = GetVRAMTransferBounds(x, y, width, height);
|
||||
DebugAssert(bounds.right <= static_cast<s32>(VRAM_WIDTH) && bounds.bottom <= static_cast<s32>(VRAM_HEIGHT));
|
||||
AddWrittenRectangle(bounds);
|
||||
|
||||
// We want to dump *before* the write goes through, otherwise we dump bad data.
|
||||
if (m_use_texture_cache)
|
||||
{
|
||||
GPUTextureCache::WriteVRAM(x, y, width, height, data, set_mask, check_mask, bounds);
|
||||
}
|
||||
else if (m_sw_renderer)
|
||||
{
|
||||
const u32 num_words = width * height;
|
||||
GPUBackendUpdateVRAMCommand* cmd = m_sw_renderer->NewUpdateVRAMCommand(num_words);
|
||||
|
@ -3192,10 +3318,6 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b
|
|||
m_sw_renderer->PushCommand(cmd);
|
||||
}
|
||||
|
||||
const GSVector4i bounds = GetVRAMTransferBounds(x, y, width, height);
|
||||
DebugAssert(bounds.right <= static_cast<s32>(VRAM_WIDTH) && bounds.bottom <= static_cast<s32>(VRAM_HEIGHT));
|
||||
AddWrittenRectangle(bounds);
|
||||
|
||||
if (check_mask)
|
||||
{
|
||||
// set new vertex counter since we want this to take into consideration previous masked pixels
|
||||
|
@ -3203,7 +3325,7 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b
|
|||
}
|
||||
else
|
||||
{
|
||||
const TextureReplacements::ReplacementImage* rtex = TextureReplacements::GetVRAMReplacement(width, height, data);
|
||||
const GPUTextureCache::TextureReplacementImage* rtex = GPUTextureCache::GetVRAMReplacement(width, height, data);
|
||||
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
|
||||
width * m_resolution_scale, height * m_resolution_scale))
|
||||
{
|
||||
|
@ -3283,7 +3405,26 @@ void GPU_HW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
|
|||
{
|
||||
GL_SCOPE_FMT("CopyVRAM({}x{} @ {},{} => {},{}", width, height, src_x, src_y, dst_x, dst_y);
|
||||
|
||||
if (m_sw_renderer)
|
||||
// masking enabled, oversized, or overlapping
|
||||
const bool use_shader =
|
||||
(m_GPUSTAT.IsMaskingEnabled() || ((src_x % VRAM_WIDTH) + width) > VRAM_WIDTH ||
|
||||
((src_y % VRAM_HEIGHT) + height) > VRAM_HEIGHT || ((dst_x % VRAM_WIDTH) + width) > VRAM_WIDTH ||
|
||||
((dst_y % VRAM_HEIGHT) + height) > VRAM_HEIGHT);
|
||||
const GSVector4i src_bounds = GetVRAMTransferBounds(src_x, src_y, width, height);
|
||||
const GSVector4i dst_bounds = GetVRAMTransferBounds(dst_x, dst_y, width, height);
|
||||
|
||||
// If we're copying a region that hasn't been drawn to, and we're using the TC, we can do it in local memory.
|
||||
if (m_use_texture_cache && !GPUTextureCache::IsRectDrawn(src_bounds))
|
||||
{
|
||||
GL_INS("Performed in local memory.");
|
||||
GPUTextureCache::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height, m_GPUSTAT.set_mask_while_drawing,
|
||||
m_GPUSTAT.check_mask_before_draw, src_bounds, dst_bounds);
|
||||
UpdateVRAMOnGPU(dst_bounds.left, dst_bounds.top, dst_bounds.width(), dst_bounds.height(),
|
||||
&g_vram[dst_bounds.top * VRAM_WIDTH + dst_bounds.left], VRAM_WIDTH * sizeof(u16), false, false,
|
||||
dst_bounds);
|
||||
return;
|
||||
}
|
||||
else if (m_sw_renderer)
|
||||
{
|
||||
GPUBackendCopyVRAMCommand* cmd = m_sw_renderer->NewCopyVRAMCommand();
|
||||
FillBackendCommandParameters(cmd);
|
||||
|
@ -3296,16 +3437,8 @@ void GPU_HW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
|
|||
m_sw_renderer->PushCommand(cmd);
|
||||
}
|
||||
|
||||
// masking enabled, oversized, or overlapping
|
||||
const bool use_shader =
|
||||
(m_GPUSTAT.IsMaskingEnabled() || ((src_x % VRAM_WIDTH) + width) > VRAM_WIDTH ||
|
||||
((src_y % VRAM_HEIGHT) + height) > VRAM_HEIGHT || ((dst_x % VRAM_WIDTH) + width) > VRAM_WIDTH ||
|
||||
((dst_y % VRAM_HEIGHT) + height) > VRAM_HEIGHT);
|
||||
const GSVector4i src_bounds = GetVRAMTransferBounds(src_x, src_y, width, height);
|
||||
const GSVector4i dst_bounds = GetVRAMTransferBounds(dst_x, dst_y, width, height);
|
||||
const bool intersect_with_draw = m_vram_dirty_draw_rect.rintersects(src_bounds);
|
||||
const bool intersect_with_write = m_vram_dirty_write_rect.rintersects(src_bounds);
|
||||
|
||||
if (use_shader || IsUsingMultisampling())
|
||||
{
|
||||
if (intersect_with_draw || intersect_with_write)
|
||||
|
@ -3343,6 +3476,7 @@ void GPU_HW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
|
|||
g_gpu_device->SetViewportAndScissor(dst_bounds_scaled);
|
||||
g_gpu_device->SetPipeline(
|
||||
m_vram_copy_pipelines[BoolToUInt8(m_GPUSTAT.check_mask_before_draw && m_write_mask_as_depth)].get());
|
||||
g_gpu_device->SetTextureSampler(0, m_vram_read_texture.get(), g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->PushUniformBuffer(&uniforms, sizeof(uniforms));
|
||||
g_gpu_device->Draw(3, 0);
|
||||
RestoreDeviceContext();
|
||||
|
@ -3362,7 +3496,8 @@ void GPU_HW::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32
|
|||
UpdateVRAMReadTexture(intersect_with_draw, intersect_with_write);
|
||||
}
|
||||
|
||||
if (intersect_with_draw)
|
||||
// We don't have it in local memory, so TC can't read it.
|
||||
if (intersect_with_draw || m_use_texture_cache)
|
||||
{
|
||||
AddUnclampedDrawnRectangle(dst_bounds);
|
||||
}
|
||||
|
@ -3398,77 +3533,112 @@ void GPU_HW::DispatchRenderCommand()
|
|||
{
|
||||
const GPURenderCommand rc{m_render_command.bits};
|
||||
|
||||
BatchTextureMode texture_mode = BatchTextureMode::Disabled;
|
||||
// TODO: avoid all this for vertex loading, only do when the type of draw changes
|
||||
BatchTextureMode texture_mode = rc.IsTexturingEnabled() ? m_batch.texture_mode : BatchTextureMode::Disabled;
|
||||
GPUTextureCache::SourceKey texture_cache_key = m_batch.texture_cache_key;
|
||||
if (rc.IsTexturingEnabled())
|
||||
{
|
||||
// texture page changed - check that the new page doesn't intersect the drawing area
|
||||
if (m_draw_mode.IsTexturePageChanged())
|
||||
if (m_draw_mode.IsTexturePageChanged() || texture_mode == BatchTextureMode::Disabled)
|
||||
{
|
||||
m_draw_mode.ClearTexturePageChangedFlag();
|
||||
|
||||
#if 0
|
||||
if (!m_vram_dirty_draw_rect.eq(INVALID_RECT) || !m_vram_dirty_write_rect.eq(INVALID_RECT))
|
||||
{
|
||||
GL_INS_FMT("VRAM DIRTY: {} {}", m_vram_dirty_draw_rect, m_vram_dirty_write_rect);
|
||||
GL_INS_FMT("PAGE RECT: {}", m_draw_mode.mode_reg.GetTexturePageRectangle());
|
||||
if (m_draw_mode.mode_reg.IsUsingPalette())
|
||||
GL_INS_FMT("PALETTE RECT: {}", m_draw_mode.palette_reg.GetRectangle(m_draw_mode.mode_reg.texture_mode));
|
||||
}
|
||||
#endif
|
||||
// start by assuming we can use the TC
|
||||
bool use_texture_cache = m_use_texture_cache;
|
||||
|
||||
// check that the palette isn't in a drawn area
|
||||
if (m_draw_mode.mode_reg.IsUsingPalette())
|
||||
{
|
||||
const GSVector4i palette_rect = m_draw_mode.palette_reg.GetRectangle(m_draw_mode.mode_reg.texture_mode);
|
||||
const bool update_drawn = palette_rect.rintersects(m_vram_dirty_draw_rect);
|
||||
const bool update_written = palette_rect.rintersects(m_vram_dirty_write_rect);
|
||||
if (update_drawn || update_written)
|
||||
const GSVector4i palette_rect =
|
||||
GetPaletteRect(m_draw_mode.palette_reg, m_draw_mode.mode_reg.texture_mode, use_texture_cache);
|
||||
if (!use_texture_cache || GPUTextureCache::IsRectDrawn(palette_rect))
|
||||
{
|
||||
GL_INS("Palette in VRAM dirty area, flushing cache");
|
||||
if (!IsFlushed())
|
||||
FlushRender();
|
||||
if (use_texture_cache)
|
||||
GL_INS_FMT("Palette at {} is in drawn area, can't use TC", palette_rect);
|
||||
use_texture_cache = false;
|
||||
|
||||
UpdateVRAMReadTexture(update_drawn, update_written);
|
||||
const bool update_drawn = palette_rect.rintersects(m_vram_dirty_draw_rect);
|
||||
const bool update_written = palette_rect.rintersects(m_vram_dirty_write_rect);
|
||||
if (update_drawn || update_written)
|
||||
{
|
||||
GL_INS("Palette in VRAM dirty area, flushing cache");
|
||||
if (!IsFlushed())
|
||||
FlushRender();
|
||||
|
||||
UpdateVRAMReadTexture(update_drawn, update_written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GSVector4i page_rect = m_draw_mode.mode_reg.GetTexturePageRectangle();
|
||||
GSVector4i::storel(m_current_texture_page_offset, page_rect);
|
||||
m_compute_uv_range = (m_clamp_uvs || m_texture_dumping);
|
||||
|
||||
u8 new_texpage_dirty = m_vram_dirty_draw_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_DRAWN_RECT : 0;
|
||||
new_texpage_dirty |= m_vram_dirty_write_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_WRITTEN_RECT : 0;
|
||||
const GPUTextureMode gpu_texture_mode =
|
||||
(m_draw_mode.mode_reg.texture_mode == GPUTextureMode::Reserved_Direct16Bit) ? GPUTextureMode::Direct16Bit :
|
||||
m_draw_mode.mode_reg.texture_mode;
|
||||
const GSVector4i page_rect = GetTextureRect(m_draw_mode.mode_reg.texture_page, m_draw_mode.mode_reg.texture_mode);
|
||||
|
||||
if (new_texpage_dirty != 0)
|
||||
// TODO: This will result in incorrect global-space UVs when the texture page wraps around.
|
||||
// Need to deal with it if it becomes a problem.
|
||||
m_current_texture_page_offset[0] = static_cast<s32>(m_draw_mode.mode_reg.GetTexturePageBaseX());
|
||||
m_current_texture_page_offset[1] = static_cast<s32>(m_draw_mode.mode_reg.GetTexturePageBaseY());
|
||||
|
||||
if (use_texture_cache)
|
||||
{
|
||||
GL_INS("Texpage is in dirty area, checking UV ranges");
|
||||
m_texpage_dirty = new_texpage_dirty;
|
||||
m_compute_uv_range = true;
|
||||
m_current_uv_rect = INVALID_RECT;
|
||||
texture_mode = BatchTextureMode::PageTexture;
|
||||
texture_cache_key =
|
||||
GPUTextureCache::SourceKey(m_draw_mode.mode_reg.texture_page, m_draw_mode.palette_reg, gpu_texture_mode);
|
||||
|
||||
const bool is_drawn = GPUTextureCache::IsRectDrawn(page_rect);
|
||||
if (is_drawn)
|
||||
GL_INS_FMT("Texpage [{}] {} is drawn in TC, checking UV ranges", texture_cache_key.page, page_rect);
|
||||
|
||||
m_texpage_dirty =
|
||||
(is_drawn ? TEXPAGE_DIRTY_PAGE_RECT : 0) | (m_texture_dumping ? TEXPAGE_DIRTY_ONLY_UV_RECT : 0);
|
||||
m_compute_uv_range |= ShouldCheckForTexPageOverlap();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_compute_uv_range = m_clamp_uvs;
|
||||
if (m_texpage_dirty)
|
||||
GL_INS("Texpage is no longer dirty");
|
||||
m_texpage_dirty = 0;
|
||||
texture_mode = static_cast<BatchTextureMode>(gpu_texture_mode);
|
||||
m_texpage_dirty = (m_vram_dirty_draw_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_DRAWN_RECT : 0) |
|
||||
(m_vram_dirty_write_rect.rintersects(page_rect) ? TEXPAGE_DIRTY_WRITTEN_RECT : 0);
|
||||
if (m_texpage_dirty & TEXPAGE_DIRTY_DRAWN_RECT)
|
||||
GL_INS_FMT("Texpage {} is in dirty DRAWN area {}", page_rect, m_vram_dirty_draw_rect);
|
||||
if (m_texpage_dirty & TEXPAGE_DIRTY_WRITTEN_RECT)
|
||||
GL_INS_FMT("Texpage {} is in dirty WRITTEN area {}", page_rect, m_vram_dirty_write_rect);
|
||||
|
||||
// Current UV rect _must_ be cleared here, because we're only check for texpage intersection when it grows in
|
||||
// size, a switch from a non-contained page to a contained page would go undetected otherwise.
|
||||
if (m_texpage_dirty != 0)
|
||||
{
|
||||
m_compute_uv_range = true;
|
||||
m_current_uv_rect = INVALID_RECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
texture_mode = (m_draw_mode.mode_reg.texture_mode == GPUTextureMode::Reserved_Direct16Bit) ?
|
||||
BatchTextureMode::Direct16Bit :
|
||||
static_cast<BatchTextureMode>(m_draw_mode.mode_reg.texture_mode.GetValue());
|
||||
}
|
||||
|
||||
DebugAssert((rc.IsTexturingEnabled() && (texture_mode == BatchTextureMode::PageTexture &&
|
||||
texture_cache_key.mode == m_draw_mode.mode_reg.texture_mode) ||
|
||||
texture_mode == static_cast<BatchTextureMode>(m_draw_mode.mode_reg.texture_mode.GetValue())) ||
|
||||
(!rc.IsTexturingEnabled() && texture_mode == BatchTextureMode::Disabled));
|
||||
DebugAssert(!(m_texpage_dirty & TEXPAGE_DIRTY_PAGE_RECT) || texture_mode == BatchTextureMode::PageTexture ||
|
||||
!rc.IsTexturingEnabled());
|
||||
|
||||
// has any state changed which requires a new batch?
|
||||
// Reverse blending breaks with mixed transparent and opaque pixels, so we have to do one draw per polygon.
|
||||
// If we have fbfetch, we don't need to draw it in two passes. Test case: Suikoden 2 shadows.
|
||||
const GPUTransparencyMode transparency_mode =
|
||||
rc.transparency_enable ? m_draw_mode.mode_reg.transparency_mode : GPUTransparencyMode::Disabled;
|
||||
const bool dithering_enable = (!m_true_color && rc.IsDitheringEnabled()) ? m_GPUSTAT.dither_enable : false;
|
||||
if (texture_mode != m_batch.texture_mode || transparency_mode != m_batch.transparency_mode ||
|
||||
(transparency_mode == GPUTransparencyMode::BackgroundMinusForeground && !m_allow_shader_blend) ||
|
||||
dithering_enable != m_batch.dithering)
|
||||
if (!IsFlushed())
|
||||
{
|
||||
FlushRender();
|
||||
if (texture_mode != m_batch.texture_mode || transparency_mode != m_batch.transparency_mode ||
|
||||
(transparency_mode == GPUTransparencyMode::BackgroundMinusForeground && !m_allow_shader_blend) ||
|
||||
dithering_enable != m_batch.dithering ||
|
||||
(texture_mode == BatchTextureMode::PageTexture && m_batch.texture_cache_key != texture_cache_key))
|
||||
{
|
||||
FlushRender();
|
||||
}
|
||||
}
|
||||
|
||||
EnsureVertexBufferSpaceForCurrentCommand();
|
||||
|
@ -3512,6 +3682,7 @@ void GPU_HW::DispatchRenderCommand()
|
|||
m_batch.texture_mode = texture_mode;
|
||||
m_batch.transparency_mode = transparency_mode;
|
||||
m_batch.dithering = dithering_enable;
|
||||
m_batch.texture_cache_key = texture_cache_key;
|
||||
|
||||
if (m_draw_mode.IsTextureWindowChanged())
|
||||
{
|
||||
|
@ -3577,10 +3748,21 @@ void GPU_HW::FlushRender()
|
|||
return;
|
||||
|
||||
#ifdef _DEBUG
|
||||
GL_SCOPE_FMT("Hardware Draw {}", ++s_draw_number);
|
||||
GL_SCOPE_FMT("Hardware Draw {}: {}", ++s_draw_number, m_current_draw_rect);
|
||||
#endif
|
||||
|
||||
GL_INS_FMT("Dirty draw area: {}", m_vram_dirty_draw_rect);
|
||||
if (m_compute_uv_range)
|
||||
GL_INS_FMT("UV rect: {}", m_current_uv_rect);
|
||||
|
||||
const GPUTextureCache::Source* texture = nullptr;
|
||||
if (m_batch.texture_mode == BatchTextureMode::PageTexture)
|
||||
{
|
||||
texture = LookupSource(m_batch.texture_cache_key, m_current_uv_rect,
|
||||
m_batch.transparency_mode != GPUTransparencyMode::Disabled ?
|
||||
GPUTextureCache::PaletteRecordFlags::HasSemiTransparentDraws :
|
||||
GPUTextureCache::PaletteRecordFlags::None);
|
||||
}
|
||||
|
||||
if (m_batch_ubo_dirty)
|
||||
{
|
||||
|
@ -3589,21 +3771,24 @@ void GPU_HW::FlushRender()
|
|||
m_batch_ubo_dirty = false;
|
||||
}
|
||||
|
||||
m_current_draw_rect = INVALID_RECT;
|
||||
m_current_uv_rect = INVALID_RECT;
|
||||
|
||||
if (m_wireframe_mode != GPUWireframeMode::OnlyWireframe)
|
||||
{
|
||||
if (NeedsShaderBlending(m_batch.transparency_mode, m_batch.texture_mode, m_batch.check_mask_before_draw) ||
|
||||
m_rov_active || (m_use_rov_for_shader_blend && m_pgxp_depth_buffer))
|
||||
{
|
||||
DrawBatchVertices(BatchRenderMode::ShaderBlend, index_count, base_index, base_vertex);
|
||||
DrawBatchVertices(BatchRenderMode::ShaderBlend, index_count, base_index, base_vertex, texture);
|
||||
}
|
||||
else if (NeedsTwoPassRendering())
|
||||
{
|
||||
DrawBatchVertices(BatchRenderMode::OnlyOpaque, index_count, base_index, base_vertex);
|
||||
DrawBatchVertices(BatchRenderMode::OnlyTransparent, index_count, base_index, base_vertex);
|
||||
DrawBatchVertices(BatchRenderMode::OnlyOpaque, index_count, base_index, base_vertex, texture);
|
||||
DrawBatchVertices(BatchRenderMode::OnlyTransparent, index_count, base_index, base_vertex, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBatchVertices(m_batch.GetRenderMode(), index_count, base_index, base_vertex);
|
||||
DrawBatchVertices(m_batch.GetRenderMode(), index_count, base_index, base_vertex, texture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3623,6 +3808,8 @@ void GPU_HW::UpdateDisplay()
|
|||
|
||||
GL_SCOPE("UpdateDisplay()");
|
||||
|
||||
GPUTextureCache::Compact();
|
||||
|
||||
if (g_settings.debugging.show_vram)
|
||||
{
|
||||
if (IsUsingMultisampling())
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "gpu.h"
|
||||
#include "texture_replacements.h"
|
||||
#include "gpu_hw_texture_cache.h"
|
||||
|
||||
#include "util/gpu_device.h"
|
||||
|
||||
|
@ -38,6 +38,7 @@ public:
|
|||
Palette4Bit,
|
||||
Palette8Bit,
|
||||
Direct16Bit,
|
||||
PageTexture,
|
||||
Disabled,
|
||||
|
||||
SpritePalette4Bit,
|
||||
|
@ -52,6 +53,11 @@ public:
|
|||
static_cast<u8>(BatchTextureMode::Palette8Bit) == static_cast<u8>(GPUTextureMode::Palette8Bit) &&
|
||||
static_cast<u8>(BatchTextureMode::Direct16Bit) == static_cast<u8>(GPUTextureMode::Direct16Bit));
|
||||
|
||||
static constexpr GSVector4i VRAM_SIZE_RECT = GSVector4i::cxpr(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
|
||||
static constexpr GSVector4i INVALID_RECT =
|
||||
GSVector4i::cxpr(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(),
|
||||
std::numeric_limits<s32>::min());
|
||||
|
||||
GPU_HW();
|
||||
~GPU_HW() override;
|
||||
|
||||
|
@ -83,6 +89,8 @@ private:
|
|||
{
|
||||
TEXPAGE_DIRTY_DRAWN_RECT = (1 << 0),
|
||||
TEXPAGE_DIRTY_WRITTEN_RECT = (1 << 1),
|
||||
TEXPAGE_DIRTY_PAGE_RECT = (1 << 2),
|
||||
TEXPAGE_DIRTY_ONLY_UV_RECT = (1 << 3),
|
||||
};
|
||||
|
||||
static_assert(GPUDevice::MIN_TEXEL_BUFFER_ELEMENTS >= (VRAM_WIDTH * VRAM_HEIGHT));
|
||||
|
@ -116,6 +124,8 @@ private:
|
|||
bool use_depth_buffer = false;
|
||||
bool sprite_mode = false;
|
||||
|
||||
GPUTextureCache::SourceKey texture_cache_key = {};
|
||||
|
||||
// Returns the render mode for this batch.
|
||||
BatchRenderMode GetRenderMode() const;
|
||||
};
|
||||
|
@ -140,11 +150,6 @@ private:
|
|||
u32 num_uniform_buffer_updates;
|
||||
};
|
||||
|
||||
static constexpr GSVector4i VRAM_SIZE_RECT = GSVector4i::cxpr(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
|
||||
static constexpr GSVector4i INVALID_RECT =
|
||||
GSVector4i::cxpr(std::numeric_limits<s32>::max(), std::numeric_limits<s32>::max(), std::numeric_limits<s32>::min(),
|
||||
std::numeric_limits<s32>::min());
|
||||
|
||||
/// Returns true if a depth buffer should be created.
|
||||
GPUTexture::Format GetDepthBufferFormat() const;
|
||||
|
||||
|
@ -169,7 +174,8 @@ private:
|
|||
void DeactivateROV();
|
||||
void MapGPUBuffer(u32 required_vertices, u32 required_indices);
|
||||
void UnmapGPUBuffer(u32 used_vertices, u32 used_indices);
|
||||
void DrawBatchVertices(BatchRenderMode render_mode, u32 num_indices, u32 base_index, u32 base_vertex);
|
||||
void DrawBatchVertices(BatchRenderMode render_mode, u32 num_indices, u32 base_index, u32 base_vertex,
|
||||
const GPUTextureCache::Source* texture);
|
||||
|
||||
u32 CalculateResolutionScale() const;
|
||||
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
|
||||
|
@ -186,6 +192,7 @@ private:
|
|||
void SetTexPageChangedOnOverlap(const GSVector4i update_rect);
|
||||
|
||||
void CheckForTexPageOverlap(GSVector4i uv_rect);
|
||||
bool ShouldCheckForTexPageOverlap() const;
|
||||
|
||||
bool IsFlushed() const;
|
||||
void EnsureVertexBufferSpace(u32 required_vertices, u32 required_indices);
|
||||
|
@ -217,7 +224,7 @@ private:
|
|||
|
||||
void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask,
|
||||
bool check_mask, const GSVector4i bounds);
|
||||
bool BlitVRAMReplacementTexture(const TextureReplacements::ReplacementImage* tex, u32 dst_x, u32 dst_y, u32 width,
|
||||
bool BlitVRAMReplacementTexture(const GPUTextureCache::TextureReplacementImage* tex, u32 dst_x, u32 dst_y, u32 width,
|
||||
u32 height);
|
||||
|
||||
/// Expands a line into two triangles.
|
||||
|
@ -290,6 +297,9 @@ private:
|
|||
bool m_texture_window_active : 1 = false;
|
||||
bool m_rov_active : 1 = false;
|
||||
|
||||
bool m_use_texture_cache : 1 = false;
|
||||
bool m_texture_dumping : 1 = false;
|
||||
|
||||
u8 m_texpage_dirty = 0;
|
||||
|
||||
BatchConfig m_batch;
|
||||
|
@ -300,8 +310,9 @@ private:
|
|||
|
||||
// Bounding box of VRAM area that the GPU has drawn into.
|
||||
GSVector4i m_vram_dirty_draw_rect = INVALID_RECT;
|
||||
GSVector4i m_vram_dirty_write_rect = INVALID_RECT;
|
||||
GSVector4i m_vram_dirty_write_rect = INVALID_RECT; // TODO: Don't use in TC mode, should be kept at zero.
|
||||
GSVector4i m_current_uv_rect = INVALID_RECT;
|
||||
GSVector4i m_current_draw_rect = INVALID_RECT;
|
||||
s32 m_current_texture_page_offset[2] = {};
|
||||
|
||||
std::unique_ptr<GPUPipeline> m_wireframe_pipeline;
|
||||
|
|
|
@ -55,13 +55,14 @@ void GPU_HW_ShaderGen::WriteBatchUniformBuffer(std::stringstream& ss)
|
|||
false);
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool palette, bool uv_limits,
|
||||
std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool palette, bool page_texture, bool uv_limits,
|
||||
bool force_round_texcoords, bool pgxp_depth)
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DefineMacro(ss, "TEXTURED", textured);
|
||||
DefineMacro(ss, "PALETTE", palette);
|
||||
DefineMacro(ss, "PAGE_TEXTURE", page_texture);
|
||||
DefineMacro(ss, "UV_LIMITS", uv_limits);
|
||||
DefineMacro(ss, "FORCE_ROUND_TEXCOORDS", force_round_texcoords);
|
||||
DefineMacro(ss, "PGXP_DEPTH", pgxp_depth);
|
||||
|
@ -69,7 +70,22 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pale
|
|||
|
||||
WriteBatchUniformBuffer(ss);
|
||||
|
||||
if (textured)
|
||||
if (textured && page_texture)
|
||||
{
|
||||
if (uv_limits)
|
||||
{
|
||||
DeclareVertexEntryPoint(
|
||||
ss, {"float4 a_pos", "float4 a_col0", "uint a_texcoord", "uint a_texpage", "float4 a_uv_limits"}, 1, 1,
|
||||
{{"nointerpolation", "float4 v_uv_limits"}}, false, "", UsingMSAA(), UsingPerSampleShading(),
|
||||
m_disable_color_perspective);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeclareVertexEntryPoint(ss, {"float4 a_pos", "float4 a_col0", "uint a_texcoord", "uint a_texpage"}, 1, 1, {},
|
||||
false, "", UsingMSAA(), UsingPerSampleShading(), m_disable_color_perspective);
|
||||
}
|
||||
}
|
||||
else if (textured)
|
||||
{
|
||||
if (uv_limits)
|
||||
{
|
||||
|
@ -127,16 +143,18 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pale
|
|||
v_col0 = a_col0;
|
||||
#if TEXTURED
|
||||
v_tex0 = float2(uint2(a_texcoord & 0xFFFFu, a_texcoord >> 16));
|
||||
#if !PALETTE
|
||||
#if !PALETTE && !PAGE_TEXTURE
|
||||
v_tex0 *= u_resolution_scale;
|
||||
#endif
|
||||
|
||||
// base_x,base_y,palette_x,palette_y
|
||||
v_texpage.x = (a_texpage & 15u) * 64u;
|
||||
v_texpage.y = ((a_texpage >> 4) & 1u) * 256u;
|
||||
#if PALETTE
|
||||
v_texpage.z = ((a_texpage >> 16) & 63u) * 16u;
|
||||
v_texpage.w = ((a_texpage >> 22) & 511u);
|
||||
#if !PAGE_TEXTURE
|
||||
// base_x,base_y,palette_x,palette_y
|
||||
v_texpage.x = (a_texpage & 15u) * 64u;
|
||||
v_texpage.y = ((a_texpage >> 4) & 1u) * 256u;
|
||||
#if PALETTE
|
||||
v_texpage.z = ((a_texpage >> 16) & 63u) * 16u;
|
||||
v_texpage.w = ((a_texpage >> 22) & 511u);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if UV_LIMITS
|
||||
|
@ -146,7 +164,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pale
|
|||
// Add 0.5 to the upper bounds when upscaling, to work around interpolation differences.
|
||||
// Limited to force-round-texcoord hack, to avoid breaking other games.
|
||||
v_uv_limits.zw += 0.5;
|
||||
#elif !PALETTE
|
||||
#elif !PAGE_TEXTURE && !PALETTE
|
||||
// Treat coordinates as being in upscaled space, and extend the UV range to all "upscaled"
|
||||
// pixels. This means 1-pixel-high polygon-based framebuffer effects won't be downsampled.
|
||||
// (e.g. Mega Man Legends 2 haze effect)
|
||||
|
@ -707,6 +725,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
|
|||
const bool textured = (texture_mode != GPU_HW::BatchTextureMode::Disabled);
|
||||
const bool palette =
|
||||
(texture_mode == GPU_HW::BatchTextureMode::Palette4Bit || texture_mode == GPU_HW::BatchTextureMode::Palette8Bit);
|
||||
const bool page_texture = (texture_mode == GPU_HW::BatchTextureMode::PageTexture);
|
||||
const bool shader_blending = (render_mode == GPU_HW::BatchRenderMode::ShaderBlend);
|
||||
const bool use_dual_source = (!shader_blending && !use_rov && m_supports_dual_source_blend &&
|
||||
((render_mode != GPU_HW::BatchRenderMode::TransparencyDisabled &&
|
||||
|
@ -725,6 +744,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
|
|||
DefineMacro(ss, "PALETTE", palette);
|
||||
DefineMacro(ss, "PALETTE_4_BIT", texture_mode == GPU_HW::BatchTextureMode::Palette4Bit);
|
||||
DefineMacro(ss, "PALETTE_8_BIT", texture_mode == GPU_HW::BatchTextureMode::Palette8Bit);
|
||||
DefineMacro(ss, "PAGE_TEXTURE", page_texture);
|
||||
DefineMacro(ss, "DITHERING", dithering);
|
||||
DefineMacro(ss, "DITHERING_SCALED", m_scaled_dithering);
|
||||
DefineMacro(ss, "INTERLACING", interlacing);
|
||||
|
@ -804,6 +824,8 @@ uint2 FloatToIntegerCoords(float2 coords)
|
|||
return uint2((UPSCALED == 0 || FORCE_ROUND_TEXCOORDS != 0) ? roundEven(coords) : floor(coords));
|
||||
}
|
||||
|
||||
#if !PAGE_TEXTURE
|
||||
|
||||
float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
|
||||
{
|
||||
#if PALETTE
|
||||
|
@ -855,11 +877,43 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
|
|||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
float4 SampleFromPageTexture(float2 coords)
|
||||
{
|
||||
// Cached textures.
|
||||
#if UPSCALED == 0 || FORCE_ROUND_TEXCOORDS != 0
|
||||
float2 fpart = coords - roundEven(coords);
|
||||
#else
|
||||
float2 fpart = frac(coords);
|
||||
#endif
|
||||
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
|
||||
coords = (float2(icoord) + fpart) * (1.0f / 256.0f);
|
||||
return SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // TEXTURED
|
||||
)";
|
||||
|
||||
const u32 num_fragment_outputs = use_rov ? 0 : (use_dual_source ? 2 : 1);
|
||||
if (textured)
|
||||
if (textured && page_texture)
|
||||
{
|
||||
if (uv_limits)
|
||||
{
|
||||
DeclareFragmentEntryPoint(ss, 1, 1, {{"nointerpolation", "float4 v_uv_limits"}}, true, num_fragment_outputs,
|
||||
use_dual_source, m_write_mask_as_depth, UsingMSAA(), UsingPerSampleShading(), false,
|
||||
m_disable_color_perspective, shader_blending && !use_rov, use_rov);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeclareFragmentEntryPoint(ss, 1, 1, {}, true, num_fragment_outputs, use_dual_source, m_write_mask_as_depth,
|
||||
UsingMSAA(), UsingPerSampleShading(), false, m_disable_color_perspective,
|
||||
shader_blending && !use_rov, use_rov);
|
||||
}
|
||||
}
|
||||
else if (textured)
|
||||
{
|
||||
if (texture_filtering != GPUTextureFilter::Nearest)
|
||||
WriteBatchTextureFilter(ss, texture_filtering);
|
||||
|
@ -905,7 +959,17 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
|
|||
|
||||
#if TEXTURED
|
||||
float4 texcol;
|
||||
#if TEXTURE_FILTERING
|
||||
#if PAGE_TEXTURE
|
||||
#if UV_LIMITS
|
||||
texcol = SampleFromPageTexture(clamp(v_tex0, v_uv_limits.xy, v_uv_limits.zw));
|
||||
#else
|
||||
texcol = SampleFromPageTexture(v_tex0);
|
||||
#endif
|
||||
if (VECTOR_EQ(texcol, TRANSPARENT_PIXEL_COLOR))
|
||||
discard;
|
||||
|
||||
ialpha = 1.0;
|
||||
#elif TEXTURE_FILTERING
|
||||
FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
|
||||
if (ialpha < 0.5)
|
||||
discard;
|
||||
|
@ -1687,3 +1751,33 @@ std::string GPU_HW_ShaderGen::GenerateBoxSampleDownsampleFragmentShader(u32 fact
|
|||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitransparent)
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DefineMacro(ss, "SEMITRANSPARENT", semitransparent);
|
||||
DeclareUniformBuffer(ss, {"float4 u_src_rect"}, true);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
float2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
|
||||
float4 color = SAMPLE_TEXTURE(samp0, coords);
|
||||
o_col0.rgb = color.rgb;
|
||||
|
||||
// Alpha processing.
|
||||
#if SEMITRANSPARENT
|
||||
// Map anything not 255 to 1 for semitransparent, otherwise zero for opaque.
|
||||
o_col0.a = (color.a <= 0.95f) ? 1.0f : 0.0f;
|
||||
o_col0.a = VECTOR_EQ(color, float4(0.0, 0.0, 0.0, 0.0)) ? 0.0f : o_col0.a;
|
||||
#else
|
||||
// Leave (0,0,0,0) as 0000 for opaque replacements for cutout alpha.
|
||||
o_col0.a = color.a;
|
||||
#endif
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
|
@ -15,8 +15,8 @@ public:
|
|||
bool supports_dual_source_blend, bool supports_framebuffer_fetch);
|
||||
~GPU_HW_ShaderGen();
|
||||
|
||||
std::string GenerateBatchVertexShader(bool textured, bool palette, bool uv_limits, bool force_round_texcoords,
|
||||
bool pgxp_depth);
|
||||
std::string GenerateBatchVertexShader(bool textured, bool palette, bool page_texture, bool uv_limits,
|
||||
bool force_round_texcoords, bool pgxp_depth);
|
||||
std::string GenerateBatchFragmentShader(GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency,
|
||||
GPU_HW::BatchTextureMode texture_mode, GPUTextureFilter texture_filtering,
|
||||
bool uv_limits, bool force_round_texcoords, bool dithering, bool interlacing,
|
||||
|
@ -36,6 +36,8 @@ public:
|
|||
std::string GenerateAdaptiveDownsampleCompositeFragmentShader();
|
||||
std::string GenerateBoxSampleDownsampleFragmentShader(u32 factor);
|
||||
|
||||
std::string GenerateReplacementMergeFragmentShader(bool semitransparent);
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE bool UsingMSAA() const { return m_multisamples > 1; }
|
||||
ALWAYS_INLINE bool UsingPerSampleShading() const { return m_multisamples > 1 && m_per_sample_shading; }
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,147 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_types.h"
|
||||
|
||||
class GPUTexture;
|
||||
class RGBA8Image;
|
||||
class StateWrapper;
|
||||
|
||||
struct Settings;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Texture Cache
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
namespace GPUTextureCache {
|
||||
|
||||
/// 4 pages in C16 mode, 2+4 pages in P8 mode, 1+1 pages in P4 mode.
|
||||
static constexpr u32 MAX_PAGE_REFS_PER_SOURCE = 6;
|
||||
|
||||
static constexpr u32 MAX_PAGE_REFS_PER_WRITE = 32;
|
||||
|
||||
enum class PaletteRecordFlags : u32
|
||||
{
|
||||
None = 0,
|
||||
HasSemiTransparentDraws = (1 << 0),
|
||||
};
|
||||
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(PaletteRecordFlags);
|
||||
|
||||
using HashType = u64;
|
||||
using TextureReplacementImage = RGBA8Image;
|
||||
|
||||
struct Source;
|
||||
struct HashCacheEntry;
|
||||
|
||||
template<typename T>
|
||||
struct TList;
|
||||
template<typename T>
|
||||
struct TListNode;
|
||||
|
||||
template<typename T>
|
||||
struct TList
|
||||
{
|
||||
TListNode<T>* head;
|
||||
TListNode<T>* tail;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct TListNode
|
||||
{
|
||||
// why inside itself? because we have 3 lists
|
||||
T* ref;
|
||||
TList<T>* list;
|
||||
TListNode<T>* prev;
|
||||
TListNode<T>* next;
|
||||
};
|
||||
|
||||
struct SourceKey
|
||||
{
|
||||
u8 page;
|
||||
GPUTextureMode mode;
|
||||
GPUTexturePaletteReg palette;
|
||||
|
||||
SourceKey() = default;
|
||||
ALWAYS_INLINE constexpr SourceKey(u8 page_, GPUTexturePaletteReg palette_, GPUTextureMode mode_)
|
||||
: page(page_), mode(mode_), palette(palette_)
|
||||
{
|
||||
}
|
||||
ALWAYS_INLINE constexpr SourceKey(const SourceKey& k) : page(k.page), mode(k.mode), palette(k.palette) {}
|
||||
|
||||
ALWAYS_INLINE bool HasPalette() const { return (mode < GPUTextureMode::Direct16Bit); }
|
||||
|
||||
ALWAYS_INLINE SourceKey& operator=(const SourceKey& k)
|
||||
{
|
||||
page = k.page;
|
||||
mode = k.mode;
|
||||
palette.bits = k.palette.bits;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool operator==(const SourceKey& k) const { return (std::memcmp(&k, this, sizeof(SourceKey)) == 0); }
|
||||
ALWAYS_INLINE bool operator!=(const SourceKey& k) const { return (std::memcmp(&k, this, sizeof(SourceKey)) != 0); }
|
||||
};
|
||||
static_assert(sizeof(SourceKey) == 4);
|
||||
|
||||
// TODO: Pool objects
|
||||
struct Source
|
||||
{
|
||||
SourceKey key;
|
||||
u32 num_page_refs;
|
||||
GPUTexture* texture;
|
||||
HashCacheEntry* from_hash_cache;
|
||||
GSVector4i texture_rect;
|
||||
GSVector4i palette_rect;
|
||||
HashType texture_hash;
|
||||
HashType palette_hash;
|
||||
GSVector4i active_uv_rect;
|
||||
PaletteRecordFlags palette_record_flags;
|
||||
|
||||
std::array<TListNode<Source>, MAX_PAGE_REFS_PER_SOURCE> page_refs;
|
||||
TListNode<Source> hash_cache_ref;
|
||||
};
|
||||
|
||||
bool Initialize();
|
||||
void UpdateSettings(const Settings& old_settings);
|
||||
bool DoState(StateWrapper& sw, bool skip);
|
||||
void Shutdown();
|
||||
|
||||
void Invalidate();
|
||||
|
||||
void AddWrittenRectangle(const GSVector4i rect, bool update_vram_writes = false);
|
||||
void AddDrawnRectangle(const GSVector4i rect, const GSVector4i clip_rect);
|
||||
|
||||
void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, bool check_mask,
|
||||
const GSVector4i src_bounds, const GSVector4i dst_bounds);
|
||||
void WriteVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask,
|
||||
const GSVector4i bounds);
|
||||
void UpdateVRAMTrackingState();
|
||||
|
||||
const Source* LookupSource(SourceKey key, const GSVector4i uv_rect, PaletteRecordFlags flags);
|
||||
|
||||
bool IsPageDrawn(u32 page_index);
|
||||
bool IsPageDrawn(u32 page_index, const GSVector4i rect);
|
||||
bool IsRectDrawn(const GSVector4i rect);
|
||||
bool AreSourcePagesDrawn(SourceKey key, const GSVector4i rect);
|
||||
|
||||
void InvalidatePageSources(u32 pn);
|
||||
void InvalidatePageSources(u32 pn, const GSVector4i rc);
|
||||
void DestroySource(Source* src);
|
||||
|
||||
void Compact();
|
||||
|
||||
void DecodeTexture(GPUTextureMode mode, const u16* page_ptr, const u16* palette, u32* dest, u32 dest_stride, u32 width,
|
||||
u32 height);
|
||||
HashType HashPartialPalette(GPUTexturePaletteReg palette, GPUTextureMode mode, u32 min, u32 max);
|
||||
HashType HashRect(const GSVector4i rc);
|
||||
|
||||
void SetGameID(std::string game_id);
|
||||
void ReloadTextureReplacements();
|
||||
|
||||
// VRAM Write Replacements
|
||||
const TextureReplacementImage* GetVRAMReplacement(u32 width, u32 height, const void* pixels);
|
||||
void DumpVRAMWrite(u32 width, u32 height, const void* pixels);
|
||||
bool ShouldDumpVRAMWrite(u32 width, u32 height);
|
||||
|
||||
} // namespace GPUTextureCache
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "gpu_sw.h"
|
||||
#include "gpu_hw_texture_cache.h"
|
||||
#include "settings.h"
|
||||
#include "system.h"
|
||||
|
||||
|
@ -70,7 +71,11 @@ bool GPU_SW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
|||
m_backend.Sync(true);
|
||||
|
||||
// ignore the host texture for software mode, since we want to save vram here
|
||||
return GPU::DoState(sw, nullptr, update_display);
|
||||
if (!GPU::DoState(sw, nullptr, update_display))
|
||||
return false;
|
||||
|
||||
// need to still call the TC, to toss any data in the state
|
||||
return GPUTextureCache::DoState(sw, true);
|
||||
}
|
||||
|
||||
void GPU_SW::Reset(bool clear_vram)
|
||||
|
|
|
@ -26,7 +26,15 @@ enum : u32
|
|||
GPU_MAX_DISPLAY_WIDTH = 720,
|
||||
GPU_MAX_DISPLAY_HEIGHT = 576,
|
||||
|
||||
DITHER_MATRIX_SIZE = 4
|
||||
DITHER_MATRIX_SIZE = 4,
|
||||
|
||||
VRAM_PAGE_WIDTH = 64,
|
||||
VRAM_PAGE_HEIGHT = 256,
|
||||
VRAM_PAGES_WIDE = VRAM_WIDTH / VRAM_PAGE_WIDTH,
|
||||
VRAM_PAGES_HIGH = VRAM_HEIGHT / VRAM_PAGE_HEIGHT,
|
||||
VRAM_PAGE_X_MASK = 0xf, // 16 pages wide
|
||||
VRAM_PAGE_Y_MASK = 0x10, // 2 pages high
|
||||
NUM_VRAM_PAGES = VRAM_PAGES_WIDE * VRAM_PAGES_HIGH,
|
||||
};
|
||||
|
||||
enum : s32
|
||||
|
@ -61,6 +69,11 @@ enum class GPUTextureMode : u8
|
|||
|
||||
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(GPUTextureMode);
|
||||
|
||||
ALWAYS_INLINE static constexpr bool TextureModeHasPalette(GPUTextureMode mode)
|
||||
{
|
||||
return (mode < GPUTextureMode::Direct16Bit);
|
||||
}
|
||||
|
||||
enum class GPUTransparencyMode : u8
|
||||
{
|
||||
HalfBackgroundPlusHalfForeground = 0,
|
||||
|
@ -169,7 +182,7 @@ static constexpr s32 TruncateGPUVertexPosition(s32 x)
|
|||
union GPUDrawModeReg
|
||||
{
|
||||
static constexpr u16 MASK = 0b1111111111111;
|
||||
static constexpr u16 TEXTURE_PAGE_MASK = UINT16_C(0b0000000000011111);
|
||||
static constexpr u16 TEXTURE_MODE_AND_PAGE_MASK = UINT16_C(0b0000000110011111);
|
||||
|
||||
// Polygon texpage commands only affect bits 0-8, 11
|
||||
static constexpr u16 POLYGON_TEXPAGE_MASK = 0b0000100111111111;
|
||||
|
@ -177,11 +190,9 @@ union GPUDrawModeReg
|
|||
// Bits 0..5 are returned in the GPU status register, latched at E1h/polygon draw time.
|
||||
static constexpr u32 GPUSTAT_MASK = 0b11111111111;
|
||||
|
||||
static constexpr std::array<u32, 4> texture_page_widths = {
|
||||
{TEXTURE_PAGE_WIDTH / 4, TEXTURE_PAGE_WIDTH / 2, TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_WIDTH}};
|
||||
|
||||
u16 bits;
|
||||
|
||||
BitField<u16, u8, 0, 5> texture_page;
|
||||
BitField<u16, u8, 0, 4> texture_page_x_base;
|
||||
BitField<u16, u8, 4, 1> texture_page_y_base;
|
||||
BitField<u16, GPUTransparencyMode, 5, 2> transparency_mode;
|
||||
|
@ -197,15 +208,6 @@ union GPUDrawModeReg
|
|||
|
||||
/// Returns true if the texture mode requires a palette.
|
||||
ALWAYS_INLINE bool IsUsingPalette() const { return (bits & (2 << 7)) == 0; }
|
||||
|
||||
/// Returns a rectangle comprising the texture page area.
|
||||
ALWAYS_INLINE_RELEASE GSVector4i GetTexturePageRectangle() const
|
||||
{
|
||||
const u32 base_x = GetTexturePageBaseX();
|
||||
const u32 base_y = GetTexturePageBaseY();
|
||||
return GSVector4i(base_x, base_y, base_x + texture_page_widths[static_cast<u8>(texture_mode.GetValue())],
|
||||
base_y + TEXTURE_PAGE_HEIGHT);
|
||||
}
|
||||
};
|
||||
|
||||
union GPUTexturePaletteReg
|
||||
|
@ -217,17 +219,8 @@ union GPUTexturePaletteReg
|
|||
BitField<u16, u16, 0, 6> x;
|
||||
BitField<u16, u16, 6, 9> y;
|
||||
|
||||
ALWAYS_INLINE u32 GetXBase() const { return static_cast<u32>(x) * 16u; }
|
||||
ALWAYS_INLINE u32 GetYBase() const { return static_cast<u32>(y); }
|
||||
|
||||
/// Returns a rectangle comprising the texture palette area.
|
||||
ALWAYS_INLINE_RELEASE GSVector4i GetRectangle(GPUTextureMode mode) const
|
||||
{
|
||||
static constexpr std::array<u32, 4> palette_widths = {{16, 256, 0, 0}};
|
||||
const u32 base_x = GetXBase();
|
||||
const u32 base_y = GetYBase();
|
||||
return GSVector4i(base_x, base_y, base_x + palette_widths[static_cast<u8>(mode)], base_y + 1);
|
||||
}
|
||||
ALWAYS_INLINE constexpr u32 GetXBase() const { return static_cast<u32>(x) * 16u; }
|
||||
ALWAYS_INLINE constexpr u32 GetYBase() const { return static_cast<u32>(y); }
|
||||
};
|
||||
|
||||
struct GPUTextureWindow
|
||||
|
@ -238,6 +231,119 @@ struct GPUTextureWindow
|
|||
u8 or_y;
|
||||
};
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 VRAMPageIndex(u32 px, u32 py)
|
||||
{
|
||||
return ((py * VRAM_PAGES_WIDE) + px);
|
||||
}
|
||||
ALWAYS_INLINE static constexpr GSVector4i VRAMPageRect(u32 px, u32 py)
|
||||
{
|
||||
return GSVector4i::cxpr(px * VRAM_PAGE_WIDTH, py * VRAM_PAGE_HEIGHT, (px + 1) * VRAM_PAGE_WIDTH,
|
||||
(py + 1) * VRAM_PAGE_HEIGHT);
|
||||
}
|
||||
ALWAYS_INLINE static constexpr GSVector4i VRAMPageRect(u32 pn)
|
||||
{
|
||||
// TODO: Put page rects in a LUT instead?
|
||||
return VRAMPageRect(pn % VRAM_PAGES_WIDE, pn / VRAM_PAGES_WIDE);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 VRAMCoordinateToPage(u32 x, u32 y)
|
||||
{
|
||||
return VRAMPageIndex(x / VRAM_PAGE_WIDTH, y / VRAM_PAGE_HEIGHT);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 VRAMPageStartX(u32 pn)
|
||||
{
|
||||
return (pn % VRAM_PAGES_WIDE) * VRAM_PAGE_WIDTH;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 VRAMPageStartY(u32 pn)
|
||||
{
|
||||
return (pn / VRAM_PAGES_WIDE) * VRAM_PAGE_HEIGHT;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u8 GetTextureModeShift(GPUTextureMode mode)
|
||||
{
|
||||
return ((mode < GPUTextureMode::Direct16Bit) ? (2 - static_cast<u8>(mode)) : 0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 ApplyTextureModeShift(GPUTextureMode mode, u32 vram_width)
|
||||
{
|
||||
return vram_width << GetTextureModeShift(mode);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static GSVector4i ApplyTextureModeShift(GPUTextureMode mode, const GSVector4i rect)
|
||||
{
|
||||
return rect.sll32(GetTextureModeShift(mode));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 TexturePageCountForMode(GPUTextureMode mode)
|
||||
{
|
||||
return ((mode < GPUTextureMode::Direct16Bit) ? (1 + static_cast<u8>(mode)) : 4);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 TexturePageWidthForMode(GPUTextureMode mode)
|
||||
{
|
||||
return TEXTURE_PAGE_WIDTH >> GetTextureModeShift(mode);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr bool TexturePageIsWrapping(GPUTextureMode mode, u32 pn)
|
||||
{
|
||||
return ((VRAMPageStartX(pn) + TexturePageWidthForMode(mode)) > VRAM_WIDTH);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 PalettePageCountForMode(GPUTextureMode mode)
|
||||
{
|
||||
return (mode == GPUTextureMode::Palette4Bit) ? 1 : 4;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr u32 PalettePageNumber(GPUTexturePaletteReg reg)
|
||||
{
|
||||
return VRAMCoordinateToPage(reg.GetXBase(), reg.GetYBase());
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static constexpr GSVector4i GetTextureRect(u32 pn, GPUTextureMode mode)
|
||||
{
|
||||
u32 left = VRAMPageStartX(pn);
|
||||
u32 top = VRAMPageStartY(pn);
|
||||
u32 right = left + TexturePageWidthForMode(mode);
|
||||
u32 bottom = top + VRAM_PAGE_HEIGHT;
|
||||
if (right > VRAM_WIDTH) [[unlikely]]
|
||||
{
|
||||
left = 0;
|
||||
right = VRAM_WIDTH;
|
||||
}
|
||||
if (bottom > VRAM_HEIGHT) [[unlikely]]
|
||||
{
|
||||
top = 0;
|
||||
bottom = VRAM_HEIGHT;
|
||||
}
|
||||
|
||||
return GSVector4i::cxpr(left, top, right, bottom);
|
||||
}
|
||||
|
||||
/// Returns the maximum index for a paletted texture.
|
||||
ALWAYS_INLINE static constexpr u32 GetPaletteWidth(GPUTextureMode mode)
|
||||
{
|
||||
return (mode == GPUTextureMode::Palette4Bit ? 16 : ((mode == GPUTextureMode::Palette8Bit) ? 256 : 0));
|
||||
}
|
||||
|
||||
/// Returns a rectangle comprising the texture palette area.
|
||||
ALWAYS_INLINE static constexpr GSVector4i GetPaletteRect(GPUTexturePaletteReg palette, GPUTextureMode mode,
|
||||
bool clamp_instead_of_wrapping = false)
|
||||
{
|
||||
const u32 width = GetPaletteWidth(mode);
|
||||
u32 left = palette.GetXBase();
|
||||
u32 top = palette.GetYBase();
|
||||
u32 right = left + width;
|
||||
u32 bottom = top + 1;
|
||||
if (right > VRAM_WIDTH) [[unlikely]]
|
||||
{
|
||||
right = VRAM_WIDTH;
|
||||
left = clamp_instead_of_wrapping ? left : 0;
|
||||
}
|
||||
return GSVector4i::cxpr(left, top, right, bottom);
|
||||
}
|
||||
|
||||
// 4x4 dither matrix.
|
||||
static constexpr s32 DITHER_MATRIX[DITHER_MATRIX_SIZE][DITHER_MATRIX_SIZE] = {{-4, +0, -3, +1}, // row 0
|
||||
{+2, -2, +3, -1}, // row 1
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
#include "cpu_pgxp.h"
|
||||
#include "fullscreen_ui.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_hw_texture_cache.h"
|
||||
#include "host.h"
|
||||
#include "imgui_overlays.h"
|
||||
#include "settings.h"
|
||||
#include "spu.h"
|
||||
#include "system.h"
|
||||
#include "texture_replacements.h"
|
||||
|
||||
#include "util/gpu_device.h"
|
||||
#include "util/input_manager.h"
|
||||
|
@ -22,8 +22,8 @@
|
|||
#include "common/file_system.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "IconsFontAwesome5.h"
|
||||
#include "IconsEmoji.h"
|
||||
#include "IconsFontAwesome5.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include <cmath>
|
||||
|
@ -441,7 +441,7 @@ DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics")
|
|||
{
|
||||
Host::AddKeyedOSDMessage("ReloadTextureReplacements",
|
||||
TRANSLATE_STR("OSDMessage", "Texture replacements reloaded."), 10.0f);
|
||||
TextureReplacements::Reload();
|
||||
GPUTextureCache::ReloadTextureReplacements();
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -554,7 +554,8 @@ DEFINE_HOTKEY("AudioCDAudioMute", TRANSLATE_NOOP("Hotkeys", "Audio"), TRANSLATE_
|
|||
{
|
||||
g_settings.cdrom_mute_cd_audio = !g_settings.cdrom_mute_cd_audio;
|
||||
Host::AddIconOSDMessage(
|
||||
"AudioControlHotkey", g_settings.cdrom_mute_cd_audio ? ICON_EMOJI_MUTED_SPEAKER : ICON_EMOJI_MEDIUM_VOLUME_SPEAKER,
|
||||
"AudioControlHotkey",
|
||||
g_settings.cdrom_mute_cd_audio ? ICON_EMOJI_MUTED_SPEAKER : ICON_EMOJI_MEDIUM_VOLUME_SPEAKER,
|
||||
g_settings.cdrom_mute_cd_audio ? TRANSLATE_STR("OSDMessage", "CD Audio Muted.") :
|
||||
TRANSLATE_STR("OSDMessage", "CD Audio Unmuted."),
|
||||
2.0f);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 72;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 73;
|
||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||
|
||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||
|
|
|
@ -228,6 +228,7 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
|
|||
si.GetStringValue("GPU", "ForceVideoTiming", GetForceVideoTimingName(DEFAULT_FORCE_VIDEO_TIMING_MODE)).c_str())
|
||||
.value_or(DEFAULT_FORCE_VIDEO_TIMING_MODE);
|
||||
gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false);
|
||||
gpu_texture_cache = si.GetBoolValue("GPU", "EnableTextureCache", false);
|
||||
display_24bit_chroma_smoothing = si.GetBoolValue("GPU", "ChromaSmoothing24Bit", false);
|
||||
gpu_pgxp_enable = si.GetBoolValue("GPU", "PGXPEnable", false);
|
||||
gpu_pgxp_culling = si.GetBoolValue("GPU", "PGXPCulling", true);
|
||||
|
@ -433,16 +434,43 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
|
|||
debugging.show_mdec_state = si.GetBoolValue("Debug", "ShowMDECState");
|
||||
debugging.show_dma_state = si.GetBoolValue("Debug", "ShowDMAState");
|
||||
|
||||
texture_replacements.enable_texture_replacements =
|
||||
si.GetBoolValue("TextureReplacements", "EnableTextureReplacements", false);
|
||||
texture_replacements.enable_vram_write_replacements =
|
||||
si.GetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false);
|
||||
texture_replacements.preload_textures = si.GetBoolValue("TextureReplacements", "PreloadTextures", false);
|
||||
texture_replacements.dump_textures = si.GetBoolValue("TextureReplacements", "DumpTextures", false);
|
||||
texture_replacements.dump_replaced_textures = si.GetBoolValue("TextureReplacements", "DumpReplacedTextures", true);
|
||||
texture_replacements.dump_vram_writes = si.GetBoolValue("TextureReplacements", "DumpVRAMWrites", false);
|
||||
texture_replacements.dump_vram_write_force_alpha_channel =
|
||||
|
||||
texture_replacements.config.dump_texture_pages = si.GetBoolValue("TextureReplacements", "DumpTexturePages", false);
|
||||
texture_replacements.config.dump_full_texture_pages =
|
||||
si.GetBoolValue("TextureReplacements", "DumpFullTexturePages", false);
|
||||
texture_replacements.config.dump_texture_force_alpha_channel =
|
||||
si.GetBoolValue("TextureReplacements", "DumpTextureForceAlphaChannel", false);
|
||||
texture_replacements.config.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);
|
||||
texture_replacements.config.dump_c16_textures = si.GetBoolValue("TextureReplacements", "DumpC16Textures", false);
|
||||
texture_replacements.config.reduce_palette_range = si.GetBoolValue("TextureReplacements", "ReducePaletteRange", true);
|
||||
texture_replacements.config.convert_copies_to_writes =
|
||||
si.GetBoolValue("TextureReplacements", "ConvertCopiesToWrites", false);
|
||||
texture_replacements.config.replacement_scale_linear_filter =
|
||||
si.GetBoolValue("TextureReplacements", "ReplacementScaleLinearFilter", true);
|
||||
|
||||
texture_replacements.config.max_vram_write_splits = si.GetUIntValue("TextureReplacements", "MaxVRAMWriteSplits", 0u);
|
||||
texture_replacements.config.max_vram_write_coalesce_width =
|
||||
si.GetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceWidth", 0u);
|
||||
texture_replacements.config.max_vram_write_coalesce_height =
|
||||
si.GetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceHeight", 0u);
|
||||
|
||||
texture_replacements.config.texture_dump_width_threshold =
|
||||
si.GetUIntValue("TextureReplacements", "DumpTextureWidthThreshold", 16);
|
||||
texture_replacements.config.texture_dump_height_threshold =
|
||||
si.GetUIntValue("TextureReplacements", "DumpTextureHeightThreshold", 16);
|
||||
texture_replacements.config.vram_write_dump_width_threshold =
|
||||
si.GetUIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold", 128);
|
||||
texture_replacements.config.vram_write_dump_height_threshold =
|
||||
si.GetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Android users are incredibly silly and don't understand that stretch is in the aspect ratio list...
|
||||
|
@ -526,6 +554,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
si.SetStringValue("GPU", "WireframeMode", GetGPUWireframeModeName(gpu_wireframe_mode));
|
||||
si.SetStringValue("GPU", "ForceVideoTiming", GetForceVideoTimingName(gpu_force_video_timing));
|
||||
si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack);
|
||||
si.SetBoolValue("GPU", "EnableTextureCache", gpu_texture_cache);
|
||||
si.SetBoolValue("GPU", "ChromaSmoothing24Bit", display_24bit_chroma_smoothing);
|
||||
si.SetBoolValue("GPU", "PGXPEnable", gpu_pgxp_enable);
|
||||
si.SetBoolValue("GPU", "PGXPCulling", gpu_pgxp_culling);
|
||||
|
@ -677,16 +706,41 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
si.SetBoolValue("Debug", "ShowDMAState", debugging.show_dma_state);
|
||||
}
|
||||
|
||||
si.SetBoolValue("TextureReplacements", "EnableTextureReplacements", texture_replacements.enable_texture_replacements);
|
||||
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", "DumpTextures", texture_replacements.dump_textures);
|
||||
si.SetBoolValue("TextureReplacements", "DumpReplacedTextures", texture_replacements.dump_replaced_textures);
|
||||
|
||||
si.SetBoolValue("TextureReplacements", "DumpTexturePages", texture_replacements.config.dump_texture_pages);
|
||||
si.SetBoolValue("TextureReplacements", "DumpFullTexturePages", texture_replacements.config.dump_full_texture_pages);
|
||||
si.SetBoolValue("TextureReplacements", "DumpTextureForceAlphaChannel",
|
||||
texture_replacements.config.dump_texture_force_alpha_channel);
|
||||
|
||||
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);
|
||||
texture_replacements.config.dump_vram_write_force_alpha_channel);
|
||||
si.SetBoolValue("TextureReplacements", "DumpC16Textures", texture_replacements.config.dump_c16_textures);
|
||||
si.SetBoolValue("TextureReplacements", "ReducePaletteRange", texture_replacements.config.reduce_palette_range);
|
||||
si.SetBoolValue("TextureReplacements", "ConvertCopiesToWrites", texture_replacements.config.convert_copies_to_writes);
|
||||
si.SetBoolValue("TextureReplacements", "ReplacementScaleLinearFilter",
|
||||
texture_replacements.config.replacement_scale_linear_filter);
|
||||
|
||||
si.SetUIntValue("TextureReplacements", "MaxVRAMWriteSplits", texture_replacements.config.max_vram_write_splits);
|
||||
si.SetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceWidth",
|
||||
texture_replacements.config.max_vram_write_coalesce_width);
|
||||
si.GetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceHeight",
|
||||
texture_replacements.config.max_vram_write_coalesce_height);
|
||||
|
||||
si.SetUIntValue("TextureReplacements", "DumpTextureWidthThreshold",
|
||||
texture_replacements.config.texture_dump_width_threshold);
|
||||
si.SetUIntValue("TextureReplacements", "DumpTextureHeightThreshold",
|
||||
texture_replacements.config.texture_dump_height_threshold);
|
||||
si.SetUIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold",
|
||||
texture_replacements.config.vram_write_dump_width_threshold);
|
||||
si.SetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold",
|
||||
texture_replacements.config.vram_write_dump_height_threshold);
|
||||
}
|
||||
|
||||
void Settings::Clear(SettingsInterface& si)
|
||||
|
@ -716,6 +770,146 @@ void Settings::Clear(SettingsInterface& si)
|
|||
si.ClearSection("TextureReplacements");
|
||||
}
|
||||
|
||||
bool Settings::TextureReplacementSettings::Configuration::operator==(const Configuration& rhs) const
|
||||
{
|
||||
return (dump_texture_pages == rhs.dump_texture_pages && dump_full_texture_pages == rhs.dump_full_texture_pages &&
|
||||
dump_texture_force_alpha_channel == rhs.dump_texture_force_alpha_channel &&
|
||||
dump_vram_write_force_alpha_channel == rhs.dump_vram_write_force_alpha_channel &&
|
||||
dump_c16_textures == rhs.dump_c16_textures && reduce_palette_range == rhs.reduce_palette_range &&
|
||||
convert_copies_to_writes == rhs.convert_copies_to_writes &&
|
||||
replacement_scale_linear_filter == rhs.replacement_scale_linear_filter &&
|
||||
max_vram_write_splits == rhs.max_vram_write_splits &&
|
||||
max_vram_write_coalesce_width == rhs.max_vram_write_coalesce_width &&
|
||||
max_vram_write_coalesce_height == rhs.max_vram_write_coalesce_height &&
|
||||
texture_dump_width_threshold == rhs.texture_dump_width_threshold &&
|
||||
texture_dump_height_threshold == rhs.texture_dump_height_threshold &&
|
||||
vram_write_dump_width_threshold == rhs.vram_write_dump_width_threshold &&
|
||||
vram_write_dump_height_threshold == rhs.vram_write_dump_height_threshold);
|
||||
}
|
||||
|
||||
bool Settings::TextureReplacementSettings::Configuration::operator!=(const Configuration& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
bool Settings::TextureReplacementSettings::operator==(const TextureReplacementSettings& rhs) const
|
||||
{
|
||||
return (enable_texture_replacements == rhs.enable_texture_replacements &&
|
||||
enable_vram_write_replacements == rhs.enable_vram_write_replacements &&
|
||||
preload_textures == rhs.preload_textures && dump_textures == rhs.dump_textures &&
|
||||
dump_replaced_textures == rhs.dump_replaced_textures && dump_vram_writes == rhs.dump_vram_writes &&
|
||||
config == rhs.config);
|
||||
}
|
||||
|
||||
bool Settings::TextureReplacementSettings::operator!=(const TextureReplacementSettings& rhs) const
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
std::string Settings::TextureReplacementSettings::Configuration::ExportToYAML(bool comment) const
|
||||
{
|
||||
static constexpr const char CONFIG_TEMPLATE[] = R"(# DuckStation Texture Replacement Configuration
|
||||
# This file allows you to set a per-game configuration for the dumping and
|
||||
# replacement system, avoiding the need to use the normal per-game settings
|
||||
# when moving files to a different computer. It also allows for the definition
|
||||
# of texture aliases, for reducing duplicate files.
|
||||
#
|
||||
# All options are commented out by default. If an option is commented, the user's
|
||||
# current setting will be used instead. If an option is defined in this file, it
|
||||
# will always take precedence over the user's choice.
|
||||
|
||||
# Enables texture page dumping mode.
|
||||
# Instead of tracking VRAM writes and attempting to identify the "real" size of
|
||||
# textures, create sub-rectangles from pages based on how they are drawn. In
|
||||
# most games, this will lead to significant duplication in dumps, and reduce
|
||||
# replacement reliability. However, some games are incompatible with write
|
||||
# tracking, and must use page mode.
|
||||
{}DumpTexturePages: {}
|
||||
|
||||
# Dumps full texture pages instead of sub-rectangles.
|
||||
# 256x256 pages will be dumped/replaced instead.
|
||||
{}DumpFullTexturePages: {}
|
||||
|
||||
# Enables the dumping of direct textures (i.e. C16 format).
|
||||
# Most games do not use direct textures, and when they do, it is usually for
|
||||
# post-processing or FMVs. Ignoring C16 textures typically reduces garbage/false
|
||||
# positive texture dumps, however, some games may require it.
|
||||
{}DumpC16Textures: {}
|
||||
|
||||
# Reduces the size of palettes (i.e. CLUTs) to only those indices that are used.
|
||||
# This can help reduce duplication and improve replacement reliability in games
|
||||
# that use 8-bit textures, but do not reserve or use the full 1x256 region in
|
||||
# video memory for storage of the palette. When replacing textures dumped with
|
||||
# this option enabled, CPU usage on the GPU thread does increase trivially,
|
||||
# however, generally it is worthwhile for the reliability improvement. Games
|
||||
# that require this option include Metal Gear Solid.
|
||||
{}ReducePaletteRange: {}
|
||||
|
||||
# Converts VRAM copies to VRAM writes, when a copy of performed into a previously
|
||||
# tracked VRAM write. This is required for some games that construct animated
|
||||
# textures by copying and replacing small portions of the texture with the parts
|
||||
# that are animated. Generally this option will cause duplication when dumping,
|
||||
# but it is required in some games, such as Final Fantasy VIII.
|
||||
{}ConvertCopiesToWrites: {}
|
||||
|
||||
# Determines the maximum number of times a VRAM write/upload can be split, before
|
||||
# it is discarded and no longer tracked. This is required for games that partially
|
||||
# overwrite texture data, such as Gran Turismo.
|
||||
{}MaxVRAMWriteSplits: {}
|
||||
|
||||
# Determines the maximum size of an incoming VRAM write that will be merged with
|
||||
# another write to the left/above of the incoming write. Needed for games that
|
||||
# upload textures one line at a time. These games will log "tracking VRAM write
|
||||
# of Nx1" repeatedly during loading. If the upload size is 1, then you can set
|
||||
# the corresponding maximum coalesce dimension to 1 to merge these uploads,
|
||||
# which should enable these textures to be dumped/replaced.
|
||||
{}MaxVRAMWriteCoalesceWidth: {}
|
||||
{}MaxVRAMWriteCoalesceHeight: {}
|
||||
|
||||
# Determines the minimum size of a texture that will be dumped. Textures with a
|
||||
# width/height smaller than this value will be ignored.
|
||||
{}DumpTextureWidthThreshold: {}
|
||||
{}DumpTextureHeightThreshold: {}
|
||||
|
||||
# Determines the minimum size of a VRAM write that will be dumped, in background
|
||||
# dumping mode. Uploads smaller than this size will be ignored.
|
||||
{}DumpVRAMWriteWidthThreshold: {}
|
||||
{}DumpVRAMWriteHeightThreshold: {}
|
||||
|
||||
# Enables the use of a bilinear filter when scaling replacement textures.
|
||||
# If more than one replacement texture in a 256x256 texture page has a different
|
||||
# scaling over the native resolution, or the texture page is not covered, a
|
||||
# bilinear filter will be used to resize/stretch the replacement texture, and/or
|
||||
# the original native data.
|
||||
{}ReplacementScaleLinearFilter: {}
|
||||
|
||||
# Use this section to define replacement aliases. One line per replacement
|
||||
# texture, with the key set to the source ID, and the value set to the filename
|
||||
# which should be loaded as a replacement. For example, without the newline,
|
||||
# or keep the multi-line separator.
|
||||
#Aliases:
|
||||
# Alias-Texture-Name: Path-To-Texture
|
||||
# texupload-P4-AAAAAAAAAAAAAAAA-BBBBBBBBBBBBBBBB-64x256-0-192-64x64-P0-14: |
|
||||
# texupload-P4-BBBBBBBBBBBBBBBB-BBBBBBBBBBBBBBBB-64x256-0-64-64x64-P0-13.png
|
||||
# texupload-P4-AAAAAAAAAAAAAAAA-BBBBBBBBBBBBBBBB-64x256-0-192-64x64-P0-14: mytexture.png
|
||||
)";
|
||||
|
||||
const std::string_view comment_str = comment ? "#" : "";
|
||||
return fmt::format(CONFIG_TEMPLATE, comment_str, dump_texture_pages, // DumpTexturePages
|
||||
comment_str, dump_full_texture_pages, // DumpFullTexturePages
|
||||
comment_str, dump_c16_textures, // DumpC16Textures
|
||||
comment_str, reduce_palette_range, // ReducePaletteRange
|
||||
comment_str, convert_copies_to_writes, // ConvertCopiesToWrites
|
||||
comment_str, max_vram_write_splits, // MaxVRAMWriteSplits
|
||||
comment_str, max_vram_write_coalesce_width, // MaxVRAMWriteCoalesceWidth
|
||||
comment_str, max_vram_write_coalesce_height, // MaxVRAMWriteCoalesceHeight
|
||||
comment_str, texture_dump_width_threshold, // DumpTextureWidthThreshold
|
||||
comment_str, texture_dump_height_threshold, // DumpTextureHeightThreshold
|
||||
comment_str, vram_write_dump_width_threshold, // DumpVRAMWriteWidthThreshold
|
||||
comment_str, vram_write_dump_height_threshold, // DumpVRAMWriteHeightThreshold
|
||||
comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter
|
||||
}
|
||||
|
||||
void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
||||
{
|
||||
if (g_settings.disable_all_enhancements)
|
||||
|
@ -2043,7 +2237,6 @@ bool EmuFolders::EnsureFoldersExist()
|
|||
result = FileSystem::EnsureDirectoryExists(Covers.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Dumps.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Path::Combine(Dumps, "audio").c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Path::Combine(Dumps, "textures").c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(GameIcons.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(InputProfiles.c_str(), false) && result;
|
||||
|
|
|
@ -117,6 +117,7 @@ struct Settings
|
|||
bool gpu_force_round_texcoords : 1 = false;
|
||||
bool gpu_accurate_blending : 1 = false;
|
||||
bool gpu_widescreen_hack : 1 = false;
|
||||
bool gpu_texture_cache : 1 = false;
|
||||
bool gpu_pgxp_enable : 1 = false;
|
||||
bool gpu_pgxp_culling : 1 = true;
|
||||
bool gpu_pgxp_texture_correction : 1 = true;
|
||||
|
@ -239,20 +240,46 @@ struct Settings
|
|||
// texture replacements
|
||||
struct TextureReplacementSettings
|
||||
{
|
||||
struct Configuration
|
||||
{
|
||||
constexpr Configuration() = default;
|
||||
|
||||
bool dump_texture_pages : 1 = false;
|
||||
bool dump_full_texture_pages : 1 = false;
|
||||
bool dump_texture_force_alpha_channel : 1 = false;
|
||||
bool dump_vram_write_force_alpha_channel : 1 = true;
|
||||
bool dump_c16_textures : 1 = false;
|
||||
bool reduce_palette_range : 1 = true;
|
||||
bool convert_copies_to_writes : 1 = false;
|
||||
bool replacement_scale_linear_filter = true;
|
||||
|
||||
u32 max_vram_write_splits = 0;
|
||||
u32 max_vram_write_coalesce_width = 0;
|
||||
u32 max_vram_write_coalesce_height = 0;
|
||||
u32 texture_dump_width_threshold = 16;
|
||||
u32 texture_dump_height_threshold = 16;
|
||||
|
||||
u32 vram_write_dump_width_threshold = 128;
|
||||
u32 vram_write_dump_height_threshold = 128;
|
||||
|
||||
bool operator==(const Configuration& rhs) const;
|
||||
bool operator!=(const Configuration& rhs) const;
|
||||
|
||||
std::string ExportToYAML(bool comment) const;
|
||||
};
|
||||
|
||||
bool enable_texture_replacements : 1 = false;
|
||||
bool enable_vram_write_replacements : 1 = false;
|
||||
bool preload_textures : 1 = false;
|
||||
|
||||
bool dump_textures : 1 = false;
|
||||
bool dump_replaced_textures : 1 = true;
|
||||
bool dump_vram_writes : 1 = false;
|
||||
bool dump_vram_write_force_alpha_channel : 1 = 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; }
|
||||
Configuration config;
|
||||
|
||||
ALWAYS_INLINE bool ShouldDumpVRAMWrite(u32 width, u32 height)
|
||||
{
|
||||
return dump_vram_writes && width >= dump_vram_write_width_threshold && height >= dump_vram_write_height_threshold;
|
||||
}
|
||||
bool operator==(const TextureReplacementSettings& rhs) const;
|
||||
bool operator!=(const TextureReplacementSettings& rhs) const;
|
||||
} texture_replacements;
|
||||
|
||||
bool bios_tty_logging : 1 = false;
|
||||
|
@ -345,8 +372,6 @@ struct Settings
|
|||
DEFAULT_DMA_HALT_TICKS = 100,
|
||||
DEFAULT_GPU_FIFO_SIZE = 16,
|
||||
DEFAULT_GPU_MAX_RUN_AHEAD = 128,
|
||||
DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD = 128,
|
||||
DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD = 128,
|
||||
};
|
||||
|
||||
void Load(SettingsInterface& si, SettingsInterface& controller_si);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "game_database.h"
|
||||
#include "game_list.h"
|
||||
#include "gpu.h"
|
||||
#include "gpu_hw_texture_cache.h"
|
||||
#include "gte.h"
|
||||
#include "host.h"
|
||||
#include "host_interface_progress_callback.h"
|
||||
|
@ -30,7 +31,6 @@
|
|||
#include "save_state_version.h"
|
||||
#include "sio.h"
|
||||
#include "spu.h"
|
||||
#include "texture_replacements.h"
|
||||
#include "timers.h"
|
||||
|
||||
#include "scmversion/scmversion.h"
|
||||
|
@ -1794,7 +1794,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
|||
|
||||
// Texture replacement preloading.
|
||||
// TODO: Move this and everything else below OnSystemStarted().
|
||||
TextureReplacements::SetGameID(s_running_game_serial);
|
||||
GPUTextureCache::SetGameID(s_running_game_serial);
|
||||
|
||||
// Good to go.
|
||||
s_state = State::Running;
|
||||
|
@ -1969,8 +1969,6 @@ void System::DestroySystem()
|
|||
|
||||
ClearMemorySaveStates();
|
||||
|
||||
TextureReplacements::Shutdown();
|
||||
|
||||
PCDrv::Shutdown();
|
||||
SIO::Shutdown();
|
||||
MDEC::Shutdown();
|
||||
|
@ -1984,6 +1982,7 @@ void System::DestroySystem()
|
|||
CPU::Shutdown();
|
||||
Bus::Shutdown();
|
||||
TimingEvents::Shutdown();
|
||||
GPUTextureCache::Shutdown();
|
||||
ClearRunningGame();
|
||||
|
||||
// Restore present-all-frames behavior.
|
||||
|
@ -4078,7 +4077,7 @@ void System::UpdateRunningGame(const std::string_view path, CDImage* image, bool
|
|||
}
|
||||
|
||||
if (!booting)
|
||||
TextureReplacements::SetGameID(s_running_game_serial);
|
||||
GPUTextureCache::SetGameID(s_running_game_serial);
|
||||
|
||||
if (booting)
|
||||
Achievements::ResetHardcoreMode(true);
|
||||
|
@ -4389,6 +4388,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
|
||||
g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale ||
|
||||
g_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode ||
|
||||
g_settings.gpu_texture_cache != old_settings.gpu_texture_cache ||
|
||||
g_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode ||
|
||||
g_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing ||
|
||||
g_settings.display_crop_mode != old_settings.display_crop_mode ||
|
||||
|
@ -4404,7 +4404,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
g_settings.display_line_start_offset != old_settings.display_line_start_offset ||
|
||||
g_settings.display_line_end_offset != old_settings.display_line_end_offset ||
|
||||
g_settings.rewind_enable != old_settings.rewind_enable ||
|
||||
g_settings.runahead_frames != old_settings.runahead_frames)
|
||||
g_settings.runahead_frames != old_settings.runahead_frames ||
|
||||
g_settings.texture_replacements.config != old_settings.texture_replacements.config)
|
||||
{
|
||||
g_gpu->UpdateSettings(old_settings);
|
||||
if (IsPaused())
|
||||
|
@ -4455,13 +4456,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
UpdateMemorySaveStateSettings();
|
||||
}
|
||||
|
||||
if (g_settings.texture_replacements.enable_vram_write_replacements !=
|
||||
old_settings.texture_replacements.enable_vram_write_replacements ||
|
||||
g_settings.texture_replacements.preload_textures != old_settings.texture_replacements.preload_textures)
|
||||
{
|
||||
TextureReplacements::Reload();
|
||||
}
|
||||
|
||||
if (g_settings.audio_backend != old_settings.audio_backend ||
|
||||
g_settings.emulation_speed != old_settings.emulation_speed ||
|
||||
g_settings.fast_forward_speed != old_settings.fast_forward_speed ||
|
||||
|
@ -4576,53 +4570,66 @@ void System::WarnAboutUnsafeSettings()
|
|||
LargeString messages;
|
||||
auto append = [&messages](const char* icon, std::string_view msg) { messages.append_format("{} {}\n", icon, msg); };
|
||||
|
||||
if (!g_settings.disable_all_enhancements && ImGuiManager::IsShowingOSDMessages())
|
||||
if (!g_settings.disable_all_enhancements)
|
||||
{
|
||||
if (g_settings.cpu_overclock_active)
|
||||
if (ImGuiManager::IsShowingOSDMessages())
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
SmallString::from_format(
|
||||
TRANSLATE_FS("System", "CPU clock speed is set to {}% ({} / {}). This may crash games."),
|
||||
g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator,
|
||||
g_settings.cpu_overclock_denominator));
|
||||
}
|
||||
if (g_settings.cdrom_read_speedup > 1)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
SmallString::from_format(
|
||||
TRANSLATE_FS("System", "CD-ROM read speedup set to {}x (effective speed {}x). This may crash games."),
|
||||
g_settings.cdrom_read_speedup, g_settings.cdrom_read_speedup * 2));
|
||||
}
|
||||
if (g_settings.cdrom_seek_speedup != 1)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
SmallString::from_format(TRANSLATE_FS("System", "CD-ROM seek speedup set to {}. This may crash games."),
|
||||
(g_settings.cdrom_seek_speedup == 0) ?
|
||||
TinyString(TRANSLATE_SV("System", "Instant")) :
|
||||
TinyString::from_format("{}x", g_settings.cdrom_seek_speedup)));
|
||||
}
|
||||
if (g_settings.gpu_force_video_timing != ForceVideoTimingMode::Disabled)
|
||||
{
|
||||
append(ICON_FA_TV, TRANSLATE_SV("System", "Force frame timings is enabled. Games may run at incorrect speeds."));
|
||||
}
|
||||
if (!g_settings.IsUsingSoftwareRenderer())
|
||||
{
|
||||
if (g_settings.gpu_multisamples != 1)
|
||||
if (g_settings.cpu_overclock_active)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "Multisample anti-aliasing is enabled, some games may not render correctly."));
|
||||
SmallString::from_format(
|
||||
TRANSLATE_FS("System", "CPU clock speed is set to {}% ({} / {}). This may crash games."),
|
||||
g_settings.GetCPUOverclockPercent(), g_settings.cpu_overclock_numerator,
|
||||
g_settings.cpu_overclock_denominator));
|
||||
}
|
||||
if (g_settings.gpu_resolution_scale > 1 && g_settings.gpu_force_round_texcoords)
|
||||
if (g_settings.cdrom_read_speedup > 1)
|
||||
{
|
||||
append(
|
||||
ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "Round upscaled texture coordinates is enabled. This may cause rendering errors."));
|
||||
append(ICON_EMOJI_WARNING,
|
||||
SmallString::from_format(
|
||||
TRANSLATE_FS("System", "CD-ROM read speedup set to {}x (effective speed {}x). This may crash games."),
|
||||
g_settings.cdrom_read_speedup, g_settings.cdrom_read_speedup * 2));
|
||||
}
|
||||
if (g_settings.cdrom_seek_speedup != 1)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
SmallString::from_format(TRANSLATE_FS("System", "CD-ROM seek speedup set to {}. This may crash games."),
|
||||
(g_settings.cdrom_seek_speedup == 0) ?
|
||||
TinyString(TRANSLATE_SV("System", "Instant")) :
|
||||
TinyString::from_format("{}x", g_settings.cdrom_seek_speedup)));
|
||||
}
|
||||
if (g_settings.gpu_force_video_timing != ForceVideoTimingMode::Disabled)
|
||||
{
|
||||
append(ICON_FA_TV,
|
||||
TRANSLATE_SV("System", "Force frame timings is enabled. Games may run at incorrect speeds."));
|
||||
}
|
||||
if (!g_settings.IsUsingSoftwareRenderer())
|
||||
{
|
||||
if (g_settings.gpu_multisamples != 1)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "Multisample anti-aliasing is enabled, some games may not render correctly."));
|
||||
}
|
||||
if (g_settings.gpu_resolution_scale > 1 && g_settings.gpu_force_round_texcoords)
|
||||
{
|
||||
append(
|
||||
ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "Round upscaled texture coordinates is enabled. This may cause rendering errors."));
|
||||
}
|
||||
}
|
||||
if (g_settings.enable_8mb_ram)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "8MB RAM is enabled, this may be incompatible with some games."));
|
||||
}
|
||||
}
|
||||
if (g_settings.enable_8mb_ram)
|
||||
|
||||
// Always display TC warning.
|
||||
if (g_settings.gpu_texture_cache)
|
||||
{
|
||||
append(ICON_EMOJI_WARNING,
|
||||
TRANSLATE_SV("System", "8MB RAM is enabled, this may be incompatible with some games."));
|
||||
append(
|
||||
ICON_FA_PAINT_ROLLER,
|
||||
TRANSLATE_SV("System",
|
||||
"Texture cache is enabled. This feature is experimental, some games may not render correctly."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,335 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "texture_replacements.h"
|
||||
#include "gpu_types.h"
|
||||
#include "host.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include "common/bitutils.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/hash_combine.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "xxhash.h"
|
||||
#if defined(CPU_ARCH_X86) || defined(CPU_ARCH_X64)
|
||||
#include "xxh_x86dispatch.h"
|
||||
#endif
|
||||
|
||||
#include <cinttypes>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
LOG_CHANNEL(TextureReplacements);
|
||||
|
||||
namespace TextureReplacements {
|
||||
namespace {
|
||||
struct VRAMReplacementHash
|
||||
{
|
||||
u64 low;
|
||||
u64 high;
|
||||
|
||||
TinyString ToString() const;
|
||||
bool ParseString(std::string_view sv);
|
||||
|
||||
bool operator<(const VRAMReplacementHash& rhs) const { return std::tie(low, high) < std::tie(rhs.low, rhs.high); }
|
||||
bool operator==(const VRAMReplacementHash& rhs) const { return low == rhs.low && high == rhs.high; }
|
||||
bool operator!=(const VRAMReplacementHash& rhs) const { return low != rhs.low || high != rhs.high; }
|
||||
};
|
||||
|
||||
struct VRAMReplacementHashMapHash
|
||||
{
|
||||
size_t operator()(const VRAMReplacementHash& hash) const;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
using VRAMWriteReplacementMap = std::unordered_map<VRAMReplacementHash, std::string, VRAMReplacementHashMapHash>;
|
||||
using TextureCache = std::unordered_map<std::string, ReplacementImage>;
|
||||
|
||||
static bool ParseReplacementFilename(const std::string& filename, VRAMReplacementHash* replacement_hash,
|
||||
ReplacmentType* replacement_type);
|
||||
|
||||
static std::string GetSourceDirectory();
|
||||
static std::string GetDumpDirectory();
|
||||
|
||||
static VRAMReplacementHash GetVRAMWriteHash(u32 width, u32 height, const void* pixels);
|
||||
static std::string GetVRAMWriteDumpFilename(u32 width, u32 height, const void* pixels);
|
||||
|
||||
static void FindTextures(const std::string& dir);
|
||||
|
||||
static const ReplacementImage* LoadTexture(const std::string& filename);
|
||||
static void PreloadTextures();
|
||||
static void PurgeUnreferencedTexturesFromCache();
|
||||
|
||||
static std::string s_game_id;
|
||||
|
||||
// TODO: Check the size, purge some when it gets too large.
|
||||
static TextureCache s_texture_cache;
|
||||
|
||||
static VRAMWriteReplacementMap s_vram_write_replacements;
|
||||
} // namespace TextureReplacements
|
||||
|
||||
size_t TextureReplacements::VRAMReplacementHashMapHash::operator()(const VRAMReplacementHash& hash) const
|
||||
{
|
||||
size_t hash_hash = std::hash<u64>{}(hash.low);
|
||||
hash_combine(hash_hash, hash.high);
|
||||
return hash_hash;
|
||||
}
|
||||
|
||||
TinyString TextureReplacements::VRAMReplacementHash::ToString() const
|
||||
{
|
||||
return TinyString::from_format("{:08X}{:08X}", high, low);
|
||||
}
|
||||
|
||||
bool TextureReplacements::VRAMReplacementHash::ParseString(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;
|
||||
}
|
||||
|
||||
void TextureReplacements::SetGameID(std::string game_id)
|
||||
{
|
||||
if (s_game_id == game_id)
|
||||
return;
|
||||
|
||||
s_game_id = game_id;
|
||||
Reload();
|
||||
}
|
||||
|
||||
const TextureReplacements::ReplacementImage* TextureReplacements::GetVRAMReplacement(u32 width, u32 height,
|
||||
const void* pixels)
|
||||
{
|
||||
const VRAMReplacementHash hash = GetVRAMWriteHash(width, height, pixels);
|
||||
|
||||
const auto it = s_vram_write_replacements.find(hash);
|
||||
if (it == s_vram_write_replacements.end())
|
||||
return nullptr;
|
||||
|
||||
return LoadTexture(it->second);
|
||||
}
|
||||
|
||||
void TextureReplacements::DumpVRAMWrite(u32 width, u32 height, const void* pixels)
|
||||
{
|
||||
const std::string filename = GetVRAMWriteDumpFilename(width, height, pixels);
|
||||
if (filename.empty())
|
||||
return;
|
||||
|
||||
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, VRAMRGBA5551ToRGBA8888(*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);
|
||||
}
|
||||
}
|
||||
|
||||
INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename));
|
||||
if (!image.SaveToFile(filename.c_str())) [[unlikely]]
|
||||
ERROR_LOG("Failed to dump {}x{} VRAM write to '{}'", width, height, filename);
|
||||
}
|
||||
|
||||
void TextureReplacements::Shutdown()
|
||||
{
|
||||
s_texture_cache.clear();
|
||||
s_vram_write_replacements.clear();
|
||||
s_game_id.clear();
|
||||
}
|
||||
|
||||
// TODO: Organize into PCSX2-style.
|
||||
std::string TextureReplacements::GetSourceDirectory()
|
||||
{
|
||||
return Path::Combine(EmuFolders::Textures, s_game_id);
|
||||
}
|
||||
|
||||
std::string TextureReplacements::GetDumpDirectory()
|
||||
{
|
||||
return Path::Combine(EmuFolders::Dumps, Path::Combine("textures", s_game_id));
|
||||
}
|
||||
|
||||
TextureReplacements::VRAMReplacementHash TextureReplacements::GetVRAMWriteHash(u32 width, u32 height,
|
||||
const void* pixels)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (s_game_id.empty())
|
||||
return {};
|
||||
|
||||
const VRAMReplacementHash hash = GetVRAMWriteHash(width, height, pixels);
|
||||
const std::string dump_directory(GetDumpDirectory());
|
||||
std::string filename(Path::Combine(dump_directory, fmt::format("vram-write-{}.png", hash.ToString())));
|
||||
|
||||
if (FileSystem::FileExists(filename.c_str()))
|
||||
return {};
|
||||
|
||||
if (!FileSystem::EnsureDirectoryExists(dump_directory.c_str(), false))
|
||||
return {};
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
void TextureReplacements::Reload()
|
||||
{
|
||||
s_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(s_texture_cache);
|
||||
s_texture_cache = {};
|
||||
|
||||
for (const auto& it : s_vram_write_replacements)
|
||||
{
|
||||
auto it2 = old_map.find(it.second);
|
||||
if (it2 != old_map.end())
|
||||
{
|
||||
s_texture_cache[it.second] = std::move(it2->second);
|
||||
old_map.erase(it2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TextureReplacements::ParseReplacementFilename(const std::string& filename, VRAMReplacementHash* replacement_hash,
|
||||
ReplacmentType* replacement_type)
|
||||
{
|
||||
const std::string_view file_title = Path::GetFileTitle(filename);
|
||||
if (!file_title.starts_with("vram-write-"))
|
||||
return false;
|
||||
|
||||
const std::string_view hashpart = file_title.substr(11);
|
||||
if (!replacement_hash->ParseString(hashpart))
|
||||
return false;
|
||||
|
||||
const std::string_view file_extension = Path::GetExtension(filename);
|
||||
bool valid_extension = false;
|
||||
for (const char* test_extension : {"png", "jpg", "webp"})
|
||||
{
|
||||
if (StringUtil::EqualNoCase(file_extension, test_extension))
|
||||
{
|
||||
valid_extension = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*replacement_type = ReplacmentType::VRAMWrite;
|
||||
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;
|
||||
|
||||
VRAMReplacementHash hash;
|
||||
ReplacmentType type;
|
||||
if (!ParseReplacementFilename(fd.FileName, &hash, &type))
|
||||
continue;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ReplacmentType::VRAMWrite:
|
||||
{
|
||||
auto it = s_vram_write_replacements.find(hash);
|
||||
if (it != s_vram_write_replacements.end())
|
||||
{
|
||||
WARNING_LOG("Duplicate VRAM write replacement: '{}' and '{}'", it->second, fd.FileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
s_vram_write_replacements.emplace(hash, std::move(fd.FileName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
INFO_LOG("Found {} replacement VRAM writes for '{}'", s_vram_write_replacements.size(), s_game_id);
|
||||
}
|
||||
|
||||
const TextureReplacements::ReplacementImage* TextureReplacements::LoadTexture(const std::string& filename)
|
||||
{
|
||||
auto it = s_texture_cache.find(filename);
|
||||
if (it != s_texture_cache.end())
|
||||
return &it->second;
|
||||
|
||||
RGBA8Image image;
|
||||
if (!image.LoadFromFile(filename.c_str()))
|
||||
{
|
||||
ERROR_LOG("Failed to load '{}'", Path::GetFileName(filename));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
INFO_LOG("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight());
|
||||
it = s_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>(s_vram_write_replacements.size());
|
||||
|
||||
#define UPDATE_PROGRESS() \
|
||||
if (last_update_time.GetTimeSeconds() >= UPDATE_INTERVAL) \
|
||||
{ \
|
||||
Host::DisplayLoadingScreen("Preloading replacement textures...", 0, static_cast<int>(total_textures), \
|
||||
static_cast<int>(num_textures_loaded)); \
|
||||
last_update_time.Reset(); \
|
||||
}
|
||||
|
||||
for (const auto& it : s_vram_write_replacements)
|
||||
{
|
||||
UPDATE_PROGRESS();
|
||||
|
||||
LoadTexture(it.second);
|
||||
num_textures_loaded++;
|
||||
}
|
||||
|
||||
#undef UPDATE_PROGRESS
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "util/image.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace TextureReplacements {
|
||||
|
||||
using ReplacementImage = RGBA8Image;
|
||||
|
||||
enum class ReplacmentType
|
||||
{
|
||||
VRAMWrite,
|
||||
};
|
||||
|
||||
void SetGameID(std::string game_id);
|
||||
|
||||
void Reload();
|
||||
|
||||
const ReplacementImage* GetVRAMReplacement(u32 width, u32 height, const void* pixels);
|
||||
void DumpVRAMWrite(u32 width, u32 height, const void* pixels);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
} // namespace TextureReplacements
|
|
@ -143,6 +143,7 @@ set(SRCS
|
|||
setupwizarddialog.cpp
|
||||
setupwizarddialog.h
|
||||
setupwizarddialog.ui
|
||||
texturereplacementsettingsdialog.ui
|
||||
)
|
||||
|
||||
set(TS_FILES
|
||||
|
|
|
@ -339,6 +339,9 @@
|
|||
<QtUi Include="selectdiscdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="texturereplacementsettingsdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<None Include="translations\duckstation-qt_es-es.ts" />
|
||||
<None Include="translations\duckstation-qt_tr.ts" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -284,6 +284,7 @@
|
|||
<QtUi Include="audiostretchsettingsdialog.ui" />
|
||||
<QtUi Include="controllerbindingwidget_justifier.ui" />
|
||||
<QtUi Include="selectdiscdialog.ui" />
|
||||
<QtUi Include="texturereplacementsettingsdialog.ui" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="duckstation-qt.rc" />
|
||||
|
|
|
@ -5,13 +5,21 @@
|
|||
#include "qtutils.h"
|
||||
#include "settingswindow.h"
|
||||
#include "settingwidgetbinder.h"
|
||||
#include "ui_texturereplacementsettingsdialog.h"
|
||||
|
||||
#include "core/game_database.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "util/ini_settings_interface.h"
|
||||
#include "util/media_capture.h"
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QInputDialog>
|
||||
#include <algorithm>
|
||||
|
||||
static QVariant GetMSAAModeValue(uint multisamples, bool ssaa)
|
||||
|
@ -232,26 +240,50 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
|
||||
// Texture Replacements Tab
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteReplacement, "TextureReplacements",
|
||||
"EnableVRAMWriteReplacements", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableTextureCache, "GPU", "EnableTextureCache", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useOldMDECRoutines, "Hacks", "UseOldMDECRoutines", false);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableTextureReplacements, "TextureReplacements",
|
||||
"EnableTextureReplacements", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preloadTextureReplacements, "TextureReplacements",
|
||||
"PreloadTextures", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useOldMDECRoutines, "Hacks", "UseOldMDECRoutines", false);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableTextureDumping, "TextureReplacements", "DumpTextures",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.dumpReplacedTextures, "TextureReplacements",
|
||||
"DumpReplacedTextures", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteReplacement, "TextureReplacements",
|
||||
"EnableVRAMWriteReplacements", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteDumping, "TextureReplacements", "DumpVRAMWrites",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.setVRAMWriteAlphaChannel, "TextureReplacements",
|
||||
"DumpVRAMWriteForceAlphaChannel", true);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.minDumpedVRAMWriteWidth, "TextureReplacements",
|
||||
"DumpVRAMWriteWidthThreshold",
|
||||
Settings::DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.minDumpedVRAMWriteHeight, "TextureReplacements",
|
||||
"DumpVRAMWriteHeightThreshold",
|
||||
Settings::DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD);
|
||||
|
||||
if (!m_dialog->isPerGameSettings())
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.texturesDirectory, m_ui.texturesDirectoryBrowse,
|
||||
tr("Select Textures Directory"), m_ui.texturesDirectoryOpen,
|
||||
m_ui.texturesDirectoryReset, "Folders", "Textures",
|
||||
Path::Combine(EmuFolders::DataRoot, "textures"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.tabTextureReplacementsLayout->removeWidget(m_ui.texturesDirectoryGroup);
|
||||
delete m_ui.texturesDirectoryGroup;
|
||||
m_ui.texturesDirectoryGroup = nullptr;
|
||||
m_ui.texturesDirectoryBrowse = nullptr;
|
||||
m_ui.texturesDirectoryOpen = nullptr;
|
||||
m_ui.texturesDirectoryReset = nullptr;
|
||||
m_ui.texturesDirectoryLabel = nullptr;
|
||||
m_ui.texturesDirectory = nullptr;
|
||||
}
|
||||
|
||||
connect(m_ui.enableTextureCache, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onEnableTextureCacheChanged);
|
||||
connect(m_ui.enableTextureReplacements, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged);
|
||||
connect(m_ui.vramWriteReplacement, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged);
|
||||
connect(m_ui.vramWriteDumping, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onEnableVRAMWriteDumpingChanged);
|
||||
connect(m_ui.textureReplacementOptions, &QPushButton::clicked, this,
|
||||
&GraphicsSettingsWidget::onTextureReplacementOptionsClicked);
|
||||
|
||||
// Debugging Tab
|
||||
|
||||
|
@ -274,8 +306,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
onMediaCaptureBackendChanged();
|
||||
onMediaCaptureAudioEnabledChanged();
|
||||
onMediaCaptureVideoEnabledChanged();
|
||||
onEnableTextureCacheChanged();
|
||||
onEnableAnyTextureReplacementsChanged();
|
||||
onEnableVRAMWriteDumpingChanged();
|
||||
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
|
||||
|
||||
// Rendering Tab
|
||||
|
@ -534,23 +566,26 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
"separate two pairs from each other.</b><br>For example: \"compression_level = 4 : joint_stereo = 1\""));
|
||||
|
||||
// Texture Replacements Tab
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.vramWriteReplacement, tr("Enable VRAM Write Replacement"), tr("Unchecked"),
|
||||
tr("Enables the replacement of background textures in supported games. <strong>This is "
|
||||
"not general texture replacement.</strong>"));
|
||||
dialog->registerWidgetHelp(m_ui.preloadTextureReplacements, tr("Preload Texture Replacements"), tr("Unchecked"),
|
||||
tr("Loads all replacement texture to RAM, reducing stuttering at runtime."));
|
||||
dialog->registerWidgetHelp(m_ui.enableTextureCache, tr("Enable Texture Cache"), tr("Unchecked"),
|
||||
tr("Enables caching of guest textures, required for texture replacement."));
|
||||
dialog->registerWidgetHelp(m_ui.useOldMDECRoutines, tr("Use Old MDEC Routines"), tr("Unchecked"),
|
||||
tr("Enables the older, less accurate MDEC decoding routines. May be required for old "
|
||||
"replacement backgrounds to match/load."));
|
||||
dialog->registerWidgetHelp(m_ui.setVRAMWriteAlphaChannel, tr("Set Alpha Channel"), tr("Checked"),
|
||||
tr("Clears the mask/transparency bit in VRAM write dumps."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.enableTextureReplacements, tr("Enable Texture Replacements"), tr("Unchecked"),
|
||||
tr("Enables loading of replacement textures. Not compatible with all games."));
|
||||
dialog->registerWidgetHelp(m_ui.preloadTextureReplacements, tr("Preload Texture Replacements"), tr("Unchecked"),
|
||||
tr("Loads all replacement texture to RAM, reducing stuttering at runtime."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.enableTextureDumping, tr("Enable Texture Dumping"), tr("Unchecked"),
|
||||
tr("Enables dumping of textures to image files, which can be replaced. Not compatible with all games."));
|
||||
dialog->registerWidgetHelp(m_ui.dumpReplacedTextures, tr("Dump Replaced Textures"), tr("Unchecked"),
|
||||
tr("Dumps textures that have replacements already loaded."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.vramWriteReplacement, tr("Enable VRAM Write Replacement"), tr("Unchecked"),
|
||||
tr("Enables the replacement of background textures in supported games."));
|
||||
dialog->registerWidgetHelp(m_ui.vramWriteDumping, tr("Enable VRAM Write Dumping"), tr("Unchecked"),
|
||||
tr("Writes backgrounds that can be replaced to the dump directory."));
|
||||
dialog->registerWidgetHelp(m_ui.minDumpedVRAMWriteWidth, tr("Dump Size Threshold"), tr("128px"),
|
||||
tr("Determines the threshold that triggers a VRAM write to be dumped."));
|
||||
dialog->registerWidgetHelp(m_ui.minDumpedVRAMWriteHeight, tr("Dump Size Threshold"), tr("128px"),
|
||||
tr("Determines the threshold that triggers a VRAM write to be dumped."));
|
||||
|
||||
// Debugging Tab
|
||||
|
||||
|
@ -1097,19 +1132,120 @@ void GraphicsSettingsWidget::onMediaCaptureAudioEnabledChanged()
|
|||
m_ui.audioCaptureArguments->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onEnableTextureCacheChanged()
|
||||
{
|
||||
const bool tc_enabled = m_dialog->getEffectiveBoolValue("GPU", "EnableTextureCache", false);
|
||||
m_ui.enableTextureReplacements->setEnabled(tc_enabled);
|
||||
m_ui.enableTextureDumping->setEnabled(tc_enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged()
|
||||
{
|
||||
const bool any_replacements_enabled =
|
||||
m_dialog->getEffectiveBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false);
|
||||
(m_dialog->getEffectiveBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false) ||
|
||||
(m_dialog->getEffectiveBoolValue("GPU", "EnableTextureCache", false) &&
|
||||
m_dialog->getEffectiveBoolValue("TextureReplacements", "EnableTextureReplacements", false)));
|
||||
m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onEnableVRAMWriteDumpingChanged()
|
||||
void GraphicsSettingsWidget::onTextureReplacementOptionsClicked()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("TextureReplacements", "DumpVRAMWrites", false);
|
||||
m_ui.setVRAMWriteAlphaChannel->setEnabled(enabled);
|
||||
m_ui.minDumpedVRAMWriteWidth->setEnabled(enabled);
|
||||
m_ui.minDumpedVRAMWriteHeight->setEnabled(enabled);
|
||||
m_ui.vramWriteDumpThresholdLabel->setEnabled(enabled);
|
||||
m_ui.vramWriteDumpThresholdSeparator->setEnabled(enabled);
|
||||
QDialog dlg(QtUtils::GetRootWidget(this));
|
||||
|
||||
Ui::TextureReplacementSettingsDialog dlgui;
|
||||
dlgui.setupUi(&dlg);
|
||||
dlgui.icon->setPixmap(QIcon::fromTheme(QStringLiteral("image-fill")).pixmap(32, 32));
|
||||
|
||||
constexpr Settings::TextureReplacementSettings::Configuration default_replacement_config;
|
||||
SettingsInterface* const sif = m_dialog->getSettingsInterface();
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.dumpTexturePages, "TextureReplacements", "DumpTexturePages",
|
||||
default_replacement_config.dump_texture_pages);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.dumpFullTexturePages, "TextureReplacements",
|
||||
"DumpFullTexturePages",
|
||||
default_replacement_config.dump_full_texture_pages);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.dumpC16Textures, "TextureReplacements", "DumpC16Textures",
|
||||
default_replacement_config.dump_c16_textures);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.reducePaletteRange, "TextureReplacements",
|
||||
"ReducePaletteRange", default_replacement_config.reduce_palette_range);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.convertCopiesToWrites, "TextureReplacements",
|
||||
"ConvertCopiesToWrites",
|
||||
default_replacement_config.convert_copies_to_writes);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.maxVRAMWriteSplits, "TextureReplacements",
|
||||
"MaxVRAMWriteSplits", default_replacement_config.max_vram_write_splits);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.maxVRAMWriteCoalesceWidth, "TextureReplacements",
|
||||
"MaxVRAMWriteCoalesceWidth",
|
||||
default_replacement_config.max_vram_write_coalesce_width);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.maxVRAMWriteCoalesceHeight, "TextureReplacements",
|
||||
"MaxVRAMWriteCoalesceHeight",
|
||||
default_replacement_config.max_vram_write_coalesce_height);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.minDumpedTextureWidth, "TextureReplacements",
|
||||
"DumpTextureWidthThreshold",
|
||||
default_replacement_config.texture_dump_width_threshold);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.minDumpedTextureHeight, "TextureReplacements",
|
||||
"DumpTextureHeightThreshold",
|
||||
default_replacement_config.texture_dump_height_threshold);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.setTextureDumpAlphaChannel, "TextureReplacements",
|
||||
"DumpTextureForceAlphaChannel",
|
||||
default_replacement_config.dump_texture_force_alpha_channel);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.minDumpedVRAMWriteWidth, "TextureReplacements",
|
||||
"DumpVRAMWriteWidthThreshold",
|
||||
default_replacement_config.vram_write_dump_width_threshold);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, dlgui.minDumpedVRAMWriteHeight, "TextureReplacements",
|
||||
"DumpVRAMWriteHeightThreshold",
|
||||
default_replacement_config.vram_write_dump_height_threshold);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, dlgui.setVRAMWriteAlphaChannel, "TextureReplacements",
|
||||
"DumpVRAMWriteForceAlphaChannel",
|
||||
default_replacement_config.dump_vram_write_force_alpha_channel);
|
||||
|
||||
dlgui.dumpFullTexturePages->setEnabled(
|
||||
m_dialog->getEffectiveBoolValue("TextureReplacements", "DumpTexturePages", false));
|
||||
connect(dlgui.dumpTexturePages, &QCheckBox::checkStateChanged, this, [this, full_cb = dlgui.dumpFullTexturePages]() {
|
||||
full_cb->setEnabled(m_dialog->getEffectiveBoolValue("TextureReplacements", "DumpTexturePages", false));
|
||||
});
|
||||
connect(dlgui.closeButton, &QPushButton::clicked, &dlg, &QDialog::accept);
|
||||
connect(dlgui.exportButton, &QPushButton::clicked, &dlg, [&dlg, &dlgui]() {
|
||||
Settings::TextureReplacementSettings::Configuration config;
|
||||
|
||||
config.dump_texture_pages = dlgui.dumpTexturePages->isChecked();
|
||||
config.dump_full_texture_pages = dlgui.dumpFullTexturePages->isChecked();
|
||||
config.dump_c16_textures = dlgui.dumpC16Textures->isChecked();
|
||||
config.reduce_palette_range = dlgui.reducePaletteRange->isChecked();
|
||||
config.convert_copies_to_writes = dlgui.convertCopiesToWrites->isChecked();
|
||||
config.max_vram_write_splits = dlgui.maxVRAMWriteSplits->value();
|
||||
config.max_vram_write_coalesce_width = dlgui.maxVRAMWriteCoalesceWidth->value();
|
||||
config.max_vram_write_coalesce_height = dlgui.maxVRAMWriteCoalesceHeight->value();
|
||||
config.texture_dump_width_threshold = dlgui.minDumpedTextureWidth->value();
|
||||
config.texture_dump_height_threshold = dlgui.minDumpedTextureHeight->value();
|
||||
config.dump_texture_force_alpha_channel = dlgui.setTextureDumpAlphaChannel->isChecked();
|
||||
config.vram_write_dump_width_threshold = dlgui.minDumpedVRAMWriteWidth->value();
|
||||
config.vram_write_dump_height_threshold = dlgui.minDumpedVRAMWriteHeight->value();
|
||||
config.dump_vram_write_force_alpha_channel = dlgui.setTextureDumpAlphaChannel->isChecked();
|
||||
|
||||
QInputDialog idlg(&dlg);
|
||||
idlg.resize(600, 400);
|
||||
idlg.setWindowTitle(tr("Texture Replacement Configuration"));
|
||||
idlg.setInputMode(QInputDialog::TextInput);
|
||||
idlg.setOption(QInputDialog::UsePlainTextEditForTextInput);
|
||||
idlg.setLabelText(tr("Texture Replacement Configuration (config.yaml)"));
|
||||
idlg.setTextValue(QString::fromStdString(config.ExportToYAML(false)));
|
||||
idlg.setOkButtonText(tr("Save"));
|
||||
if (idlg.exec())
|
||||
{
|
||||
const QString path = QFileDialog::getSaveFileName(&dlg, tr("Save Configuration"), QString(),
|
||||
tr("Configuration Files (config.yaml)"));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
Error error;
|
||||
if (!FileSystem::WriteStringToFile(QDir::toNativeSeparators(path).toUtf8().constData(),
|
||||
idlg.textValue().toStdString(), &error))
|
||||
{
|
||||
QMessageBox::critical(&dlg, tr("Write Failed"), QString::fromStdString(error.GetDescription()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dlg.exec();
|
||||
}
|
||||
|
|
|
@ -39,8 +39,9 @@ private Q_SLOTS:
|
|||
void onMediaCaptureVideoAutoResolutionChanged();
|
||||
void onMediaCaptureAudioEnabledChanged();
|
||||
|
||||
void onEnableTextureCacheChanged();
|
||||
void onEnableAnyTextureReplacementsChanged();
|
||||
void onEnableVRAMWriteDumpingChanged();
|
||||
void onTextureReplacementOptionsClicked();
|
||||
|
||||
private:
|
||||
static constexpr int TAB_INDEX_RENDERING = 0;
|
||||
|
|
|
@ -1054,9 +1054,9 @@
|
|||
</widget>
|
||||
<widget class="QWidget" name="tabTextureReplacements">
|
||||
<attribute name="title">
|
||||
<string>Texture Replacements</string>
|
||||
<string>Texture Replacement</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="tabTextureReplacementsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -1067,31 +1067,85 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<widget class="QGroupBox" name="groupBox_12">
|
||||
<property name="title">
|
||||
<string>General Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_9">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="enableTextureCache">
|
||||
<property name="text">
|
||||
<string>Enable Texture Cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>The texture cache is currently experimental, and may cause rendering errors in some games.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="useOldMDECRoutines">
|
||||
<property name="text">
|
||||
<string>Use Old MDEC Routines</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Texture Replacement</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_8">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<layout class="QGridLayout" name="gridLayout_6" columnstretch="1,1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="vramWriteReplacement">
|
||||
<widget class="QCheckBox" name="enableTextureReplacements">
|
||||
<property name="text">
|
||||
<string>Enable VRAM Write Replacement</string>
|
||||
<string>Enable Texture Replacements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="preloadTextureReplacements">
|
||||
<property name="text">
|
||||
<string>Preload Texture Replacements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="useOldMDECRoutines">
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableTextureDumping">
|
||||
<property name="text">
|
||||
<string>Enable Texture Dumping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="textureReplacementOptions">
|
||||
<property name="icon">
|
||||
<iconset theme="settings-3-line"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="dumpReplacedTextures">
|
||||
<property name="text">
|
||||
<string>Use Old MDEC Routines</string>
|
||||
<string>Dump Replaced Textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -1103,70 +1157,66 @@
|
|||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>VRAM Write Dumping</string>
|
||||
<string>VRAM Write (Background) Replacement</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_9">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="vramWriteReplacement">
|
||||
<property name="text">
|
||||
<string>Enable VRAM Write Replacement</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="vramWriteDumping">
|
||||
<property name="text">
|
||||
<string>Enable VRAM Write Dumping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="setVRAMWriteAlphaChannel">
|
||||
<property name="text">
|
||||
<string>Set Alpha Channel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdLabel">
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="texturesDirectoryGroup">
|
||||
<property name="title">
|
||||
<string>Textures Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_10">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="texturesDirectoryOpen">
|
||||
<property name="text">
|
||||
<string>Dump Size Threshold:</string>
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="texturesDirectoryLabel">
|
||||
<property name="text">
|
||||
<string>Directory to load replacement textures from, and save dumps to.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0,1">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedVRAMWriteWidth">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdSeparator">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedVRAMWriteHeight">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QPushButton" name="texturesDirectoryBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="texturesDirectory"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="texturesDirectoryReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
|
@ -1814,6 +1814,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
|
|||
m_ui.menuCheats->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionCPUDebugger->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionMemoryScanner->setDisabled(cheevos_challenge_mode);
|
||||
m_ui.actionReloadTextureReplacements->setDisabled(starting || !running);
|
||||
m_ui.actionDumpRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpVRAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
m_ui.actionDumpSPURAM->setDisabled(starting || !running || cheevos_challenge_mode);
|
||||
|
@ -2095,6 +2096,8 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::openCPUDebugger);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
|
||||
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||
connect(m_ui.actionOpenTextureDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenTextureDirectoryTriggered);
|
||||
connect(m_ui.actionReloadTextureReplacements, &QAction::triggered, g_emu_thread, &EmuThread::reloadTextureReplacements);
|
||||
connect(m_ui.actionMergeDiscSets, &QAction::triggered, m_game_list_widget, &GameListWidget::setMergeDiscSets);
|
||||
connect(m_ui.actionShowGameIcons, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowGameIcons);
|
||||
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
||||
|
@ -2815,6 +2818,15 @@ void MainWindow::onToolsOpenDataDirectoryTriggered()
|
|||
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot)));
|
||||
}
|
||||
|
||||
void MainWindow::onToolsOpenTextureDirectoryTriggered()
|
||||
{
|
||||
QString dir = QString::fromStdString(EmuFolders::Textures);
|
||||
if (s_system_valid && !s_current_game_serial.isEmpty())
|
||||
dir = QStringLiteral("%1" FS_OSPATH_SEPARATOR_STR "%2").arg(dir).arg(s_current_game_serial);
|
||||
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(dir));
|
||||
}
|
||||
|
||||
void MainWindow::onSettingsTriggeredFromToolbar()
|
||||
{
|
||||
if (s_system_valid)
|
||||
|
|
|
@ -178,6 +178,7 @@ private Q_SLOTS:
|
|||
void onToolsCoverDownloaderTriggered();
|
||||
void onToolsMediaCaptureToggled(bool checked);
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
void onToolsOpenTextureDirectoryTriggered();
|
||||
void onSettingsTriggeredFromToolbar();
|
||||
|
||||
void onGameListRefreshComplete();
|
||||
|
|
|
@ -208,6 +208,8 @@
|
|||
<property name="title">
|
||||
<string>&Tools</string>
|
||||
</property>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMemoryCardEditor"/>
|
||||
<addaction name="actionCoverDownloader"/>
|
||||
<addaction name="separator"/>
|
||||
|
@ -215,7 +217,8 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionMediaCapture"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
<addaction name="actionOpenTextureDirectory"/>
|
||||
<addaction name="actionReloadTextureReplacements"/>
|
||||
</widget>
|
||||
<addaction name="menuSystem"/>
|
||||
<addaction name="menuSettings"/>
|
||||
|
@ -917,6 +920,16 @@
|
|||
<string>Media Ca&pture</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenTextureDirectory">
|
||||
<property name="text">
|
||||
<string>Open Texture Directory...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReloadTextureReplacements">
|
||||
<property name="text">
|
||||
<string>Reload Texture Replacements</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/duckstation-qt.qrc"/>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "core/game_list.h"
|
||||
#include "core/gdb_server.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/gpu_hw_texture_cache.h"
|
||||
#include "core/host.h"
|
||||
#include "core/imgui_overlays.h"
|
||||
#include "core/memory_card.h"
|
||||
|
@ -1351,6 +1352,18 @@ void EmuThread::clearInputBindStateFromSource(InputBindingKey key)
|
|||
InputManager::ClearBindStateFromSource(key);
|
||||
}
|
||||
|
||||
void EmuThread::reloadTextureReplacements()
|
||||
{
|
||||
if (!isOnThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "reloadTextureReplacements", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (System::IsValid())
|
||||
GPUTextureCache::ReloadTextureReplacements();
|
||||
}
|
||||
|
||||
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
||||
{
|
||||
callback();
|
||||
|
|
|
@ -199,6 +199,7 @@ public Q_SLOTS:
|
|||
void reloadPostProcessingShaders();
|
||||
void updatePostProcessingSettings();
|
||||
void clearInputBindStateFromSource(InputBindingKey key);
|
||||
void reloadTextureReplacements();
|
||||
|
||||
private Q_SLOTS:
|
||||
void stopInThread();
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TextureReplacementSettingsDialog</class>
|
||||
<widget class="QDialog" name="TextureReplacementSettingsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>646</width>
|
||||
<height>587</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Texture Replacement Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="icon">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:700;">Texture Replacement Settings</span><br/>These settings fine-tune the behavior of the texture replacement system. You can also export a game-specific configuration file. Each of the options is explained in the configuration file, and at <a href="https://github.com/stenzek/duckstation/wiki/Texture-Replacement"><span style=" text-decoration: underline; color:#0078d4;">https://github.com/stenzek/duckstation/wiki/Texture-Replacement</span></a>.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::TextFormat::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Texture Dumping Mode</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="reducePaletteRange">
|
||||
<property name="text">
|
||||
<string>Reduce Palette Range</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="dumpTexturePages">
|
||||
<property name="text">
|
||||
<string>Dump Texture Pages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="dumpC16Textures">
|
||||
<property name="text">
|
||||
<string>Dump C16 Textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="dumpFullTexturePages">
|
||||
<property name="text">
|
||||
<string>Dump Full Texture Pages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>The texture dumping system can either operate in page mode, or write-tracking mode. Replacements can be loaded from either dump method.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Write Tracking Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Maximum Write Coalesce Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8" stretch="1,0,1">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maxVRAMWriteCoalesceWidth">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="maxVRAMWriteCoalesceSeparator">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="maxVRAMWriteCoalesceHeight">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>512</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Maximum Write Splits:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="maxVRAMWriteSplits">
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>32</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="convertCopiesToWrites">
|
||||
<property name="text">
|
||||
<string>Convert Copies To Writes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Texture Dumping Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdLabel_2">
|
||||
<property name="text">
|
||||
<string>Dump Size Threshold:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0,1,0">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedTextureWidth">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdSeparator_2">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedTextureHeight">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="setTextureDumpAlphaChannel">
|
||||
<property name="text">
|
||||
<string>Set Alpha Channel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Determines the minimum size of a texture that will be dumped. Textures with a size smaller than this value will be ignored.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Background Dumping Options</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdLabel">
|
||||
<property name="text">
|
||||
<string>Dump Size Threshold:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0,1,0">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedVRAMWriteWidth">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="vramWriteDumpThresholdSeparator">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="minDumpedVRAMWriteHeight">
|
||||
<property name="suffix">
|
||||
<string>px</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>512</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="setVRAMWriteAlphaChannel">
|
||||
<property name="text">
|
||||
<string>Set Alpha Channel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Determines the minimum size of a VRAM write that will be dumped, in background dumping mode. Uploads smaller than this size will be ignored.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>198</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="exportButton">
|
||||
<property name="text">
|
||||
<string>Export...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in New Issue