GPU: Add hardware texture cache

This commit is contained in:
Stenzek 2023-12-31 19:40:10 +10:00
parent 96ece5de1c
commit 49b8bf8da0
No known key found for this signature in database
26 changed files with 4530 additions and 401 deletions

View File

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

View File

@ -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" />
@ -131,6 +132,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" />

View File

@ -69,6 +69,7 @@
<ClCompile Include="gdb_server.cpp" />
<ClCompile Include="gpu_sw_rasterizer.cpp" />
<ClCompile Include="gpu_sw_rasterizer_avx2.cpp" />
<ClCompile Include="gpu_hw_texture_cache.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -143,6 +144,7 @@
<ClInclude Include="pine_server.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" />

View File

@ -1719,8 +1719,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)

View File

@ -532,7 +532,7 @@ 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 (TextureReplacements::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()));

View File

@ -193,6 +193,8 @@ GPU_HW::GPU_HW() : GPU()
GPU_HW::~GPU_HW()
{
GPUTextureCache::Shutdown();
if (m_sw_renderer)
{
m_sw_renderer->Shutdown();
@ -261,6 +263,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();
@ -281,13 +285,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);
@ -365,6 +383,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);
}
@ -374,10 +393,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()
@ -471,6 +492,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());
@ -524,6 +547,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))
@ -717,6 +757,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)
@ -724,13 +767,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);
}
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);
}
void GPU_HW::SetTexPageChangedOnOverlap(const GSVector4i update_rect)
@ -738,9 +790,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();
}
@ -878,6 +930,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;
}
@ -982,7 +1036,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 =
(active_texture_modes * 5 * 9 * 2 * (1 + BoolToUInt32(!true_color)) *
(1 + BoolToUInt32(!m_force_progressive_scan)) * (1 + BoolToUInt32(needs_rov_depth)));
@ -1009,7 +1063,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);
@ -1018,13 +1072,13 @@ bool GPU_HW::CompilePipelines(Error* error)
for (u8 textured = 0; textured < 2; textured++)
{
for (u8 palette = 0; palette < (textured ? 2 : 1); palette++)
for (u8 palette = 0; palette < (textured ? 3 : 1); palette++)
{
for (u8 sprite = 0; sprite < (textured ? 2 : 1); sprite++)
{
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)))
{
@ -1191,6 +1245,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));
@ -1204,7 +1260,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 :
@ -1834,19 +1892,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());
// TOOD: Totally not optimized.
if (texture)
g_gpu_device->SetTextureSampler(0, texture->texture, g_gpu_device->GetNearestSampler());
else if (texture_mode != static_cast<u8>(BatchTextureMode::Disabled))
g_gpu_device->SetTextureSampler(0, 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)]);
@ -2197,7 +2262,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());
}
@ -2618,7 +2683,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)));
@ -2842,7 +2907,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)
{
@ -2869,6 +2934,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));
@ -2903,6 +2996,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);
@ -2999,8 +3097,9 @@ ALWAYS_INLINE float GPU_HW::GetCurrentNormalizedVertexDepth() const
void GPU_HW::UpdateSoftwareRenderer(bool copy_vram_from_hw)
{
// TODO: SW-for-readbacks is currently incompatible with the texture cache, due to threading races.
const bool current_enabled = (m_sw_renderer != nullptr);
const bool new_enabled = g_settings.gpu_use_software_renderer_for_readbacks;
const bool new_enabled = !m_use_texture_cache && g_settings.gpu_use_software_renderer_for_readbacks;
if (current_enabled == new_enabled)
return;
@ -3078,7 +3177,21 @@ 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);
if (m_sw_renderer)
m_sw_renderer->Sync(true);
else
GPU::FillVRAM(x, y, width, height, color);
}
else
{
AddUnclampedDrawnRectangle(bounds);
}
GL_INS_FMT("Dirty draw area after: {}", m_vram_dirty_draw_rect);
@ -3124,6 +3237,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);
@ -3175,7 +3290,21 @@ 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)
{
if (m_sw_renderer)
m_sw_renderer->Sync(true);
GPU::UpdateVRAM(x, y, width, height, data, set_mask, check_mask);
GPUTextureCache::TrackVRAMWrite(bounds);
}
else if (m_sw_renderer)
{
const u32 num_words = width * height;
GPUBackendUpdateVRAMCommand* cmd = m_sw_renderer->NewUpdateVRAMCommand(num_words);
@ -3190,10 +3319,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
@ -3281,7 +3406,32 @@ 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.");
if (m_sw_renderer)
m_sw_renderer->Sync(true);
GPUTextureCache::AddWrittenRectangle(dst_bounds);
// GPUTextureCache::AddCopiedRectanglePart1(dst_bounds); // needed for FF8 because it animates textures by copying
GPU::CopyVRAM(src_x, src_y, dst_x, dst_y, width, height);
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);
// GPUTextureCache::AddCopiedRectanglePart2(dst_bounds);
return;
}
else if (m_sw_renderer)
{
GPUBackendCopyVRAMCommand* cmd = m_sw_renderer->NewCopyVRAMCommand();
FillBackendCommandParameters(cmd);
@ -3294,16 +3444,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)
@ -3341,6 +3483,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();
@ -3360,7 +3503,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);
}
@ -3396,77 +3540,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();
@ -3510,6 +3689,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())
{
@ -3575,10 +3755,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)
{
@ -3587,21 +3778,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);
}
}
@ -3621,6 +3815,8 @@ void GPU_HW::UpdateDisplay()
GL_SCOPE("UpdateDisplay()");
GPUTextureCache::Compact();
if (g_settings.debugging.show_vram)
{
if (IsUsingMultisampling())

View File

@ -4,6 +4,7 @@
#pragma once
#include "gpu.h"
#include "gpu_hw_texture_cache.h"
#include "texture_replacements.h"
#include "util/gpu_device.h"
@ -38,6 +39,7 @@ public:
Palette4Bit,
Palette8Bit,
Direct16Bit,
PageTexture,
Disabled,
SpritePalette4Bit,
@ -52,6 +54,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 +90,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 +125,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;
};
@ -136,11 +147,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;
@ -165,7 +171,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;
@ -182,6 +189,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);
@ -286,6 +294,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;
@ -296,8 +307,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;

View File

@ -60,13 +60,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);
@ -74,7 +75,22 @@ std::string GPU_HW_ShaderGen::GenerateBatchVertexShader(bool textured, bool pale
WriteCommonFunctions(ss);
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)
{
@ -132,16 +148,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 *= float(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
@ -151,7 +169,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)
@ -712,6 +730,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 &&
@ -730,6 +749,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);
@ -810,6 +830,8 @@ uint2 FloatToIntegerCoords(float2 coords)
return uint2((RESOLUTION_SCALE == 1u || FORCE_ROUND_TEXCOORDS != 0) ? roundEven(coords) : floor(coords));
}
#if !PAGE_TEXTURE
float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
{
#if PALETTE
@ -863,11 +885,43 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
#endif
}
#else
float4 SampleFromPageTexture(float2 coords)
{
// Cached textures.
#if FORCE_ROUND_TEXCOORDS
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);
@ -913,7 +967,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;
@ -1712,3 +1776,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();
}

View File

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

View File

@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "gpu_types.h"
#include "texture_replacements.h"
class GPUTexture;
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;
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);
void AddCopiedRectanglePart1(const GSVector4i rect); // TODO: Rename this shit
void AddCopiedRectanglePart2(const GSVector4i rect);
void AddDrawnRectangle(const GSVector4i rect);
void TrackVRAMWrite(const GSVector4i rect);
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);
} // namespace GPUTextureCache

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_sw.h"
#include "gpu_hw_texture_cache.h"
#include "system.h"
#include "util/gpu_device.h"
@ -69,7 +70,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)

View File

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

View File

@ -6,7 +6,7 @@
#include "common/types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 71;
static constexpr u32 SAVE_STATE_VERSION = 72;
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View File

@ -233,6 +233,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);
@ -437,16 +438,38 @@ 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_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.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...
@ -534,6 +557,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);
@ -684,16 +708,36 @@ 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", "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", "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)
@ -723,6 +767,39 @@ 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 &&
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_vram_writes == rhs.dump_vram_writes && config == rhs.config);
}
bool Settings::TextureReplacementSettings::operator!=(const TextureReplacementSettings& rhs) const
{
return !operator==(rhs);
}
void Settings::FixIncompatibleSettings(bool display_osd_messages)
{
if (g_settings.disable_all_enhancements)
@ -2046,7 +2123,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;

View File

@ -120,6 +120,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;
@ -242,20 +243,41 @@ 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 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;
};
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_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;
@ -347,8 +369,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);

View File

@ -1959,8 +1959,6 @@ void System::DestroySystem()
ClearMemorySaveStates();
TextureReplacements::Shutdown();
PCDrv::Shutdown();
SIO::Shutdown();
MDEC::Shutdown();
@ -1974,6 +1972,7 @@ void System::DestroySystem()
CPU::Shutdown();
Bus::Shutdown();
TimingEvents::Shutdown();
TextureReplacements::Shutdown();
ClearRunningGame();
// Restore present-all-frames behavior.
@ -4367,6 +4366,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 ||
@ -4382,7 +4382,10 @@ 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.dump_textures != old_settings.texture_replacements.dump_textures ||
g_settings.texture_replacements.enable_texture_replacements !=
old_settings.texture_replacements.enable_texture_replacements)
{
g_gpu->UpdateSettings(old_settings);
if (IsPaused())
@ -4435,10 +4438,14 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
if (g_settings.texture_replacements.enable_vram_write_replacements !=
old_settings.texture_replacements.enable_vram_write_replacements ||
g_settings.texture_replacements.enable_texture_replacements !=
old_settings.texture_replacements.enable_texture_replacements ||
g_settings.texture_replacements.preload_textures != old_settings.texture_replacements.preload_textures)
{
TextureReplacements::Reload();
}
if (g_settings.texture_replacements.config != old_settings.texture_replacements.config)
TextureReplacements::UpdateConfiguration();
if (g_settings.audio_backend != old_settings.audio_backend ||
g_settings.increase_timer_resolution != old_settings.increase_timer_resolution ||
@ -4563,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."));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,27 +3,67 @@
#pragma once
#include "types.h"
#include "gpu_types.h"
#include "settings.h"
#include "util/image.h"
#include "common/gsvector.h"
#include <string>
#include <vector>
class RGBA8Image;
namespace GPUTextureCache {
enum class PaletteRecordFlags : u32;
}
namespace TextureReplacements {
using ReplacementImage = RGBA8Image;
enum class ReplacmentType
enum class ReplacementType : u8
{
VRAMWrite,
VRAMReplacement,
TextureFromVRAMWrite,
TextureFromPage,
};
using ReplacementImage = RGBA8Image;
using TextureSourceHash = u64;
using TexturePaletteHash = u64;
struct ReplacementSubImage
{
GSVector4i dst_rect;
GSVector4i src_rect;
const ReplacementImage& image;
float scale_x;
float scale_y;
bool invert_alpha;
};
const Settings::TextureReplacementSettings::Configuration& GetConfig();
std::string ExportConfiguration(const Settings::TextureReplacementSettings::Configuration& config, bool comment = false);
void SetGameID(std::string game_id);
void UpdateConfiguration();
void Reload();
const ReplacementImage* GetVRAMReplacement(u32 width, u32 height, const void* pixels);
void DumpVRAMWrite(u32 width, u32 height, const void* pixels);
bool ShouldDumpVRAMWrite(u32 width, u32 height);
void DumpTexture(ReplacementType type, u32 offset_x, u32 offset_y, u32 src_width, u32 src_height, GPUTextureMode mode,
TextureSourceHash src_hash, TexturePaletteHash pal_hash, u32 pal_min, u32 pal_max, const u16* palette,
const GSVector4i rect, GPUTextureCache::PaletteRecordFlags flags);
bool HasVRAMWriteTextureReplacements();
void GetVRAMWriteTextureReplacements(std::vector<ReplacementSubImage>& replacements, TextureSourceHash vram_write_hash,
TextureSourceHash palette_hash, GPUTextureMode mode, GPUTexturePaletteReg palette,
const GSVector2i& offset_to_page);
bool HasTexturePageTextureReplacements();
void GetTexturePageTextureReplacements(std::vector<ReplacementSubImage>& replacements, u32 start_page_number,
TextureSourceHash page_hash, TextureSourceHash palette_hash, GPUTextureMode mode,
GPUTexturePaletteReg palette);
void Shutdown();

View File

@ -143,6 +143,7 @@ set(SRCS
setupwizarddialog.cpp
setupwizarddialog.h
setupwizarddialog.ui
texturereplacementsettingsdialog.ui
)
set(TS_FILES

View File

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

View File

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

View File

@ -5,13 +5,22 @@
#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 "core/texture_replacements.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)
@ -233,26 +242,29 @@ 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.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);
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
@ -273,8 +285,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
onMediaCaptureBackendChanged();
onMediaCaptureAudioEnabledChanged();
onMediaCaptureVideoEnabledChanged();
onEnableTextureCacheChanged();
onEnableAnyTextureReplacementsChanged();
onEnableVRAMWriteDumpingChanged();
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
// Rendering Tab
@ -545,14 +557,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
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.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
@ -1096,19 +1102,115 @@ 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.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));
dlgui.dumpFullTexturePagesLabel->setEnabled(dlgui.dumpFullTexturePages->isEnabled());
connect(dlgui.dumpTexturePages, &QCheckBox::checkStateChanged, this,
[this, full_cb = dlgui.dumpFullTexturePages, full_label = dlgui.dumpFullTexturePagesLabel]() {
full_cb->setEnabled(m_dialog->getEffectiveBoolValue("TextureReplacements", "DumpTexturePages", false));
full_label->setEnabled(full_cb->isEnabled());
});
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.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(TextureReplacements::ExportConfiguration(config, 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();
}

View File

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

View File

@ -1074,34 +1074,94 @@
<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_4">
<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="0" column="1">
<widget class="QCheckBox" name="enableTextureDumping">
<property name="text">
<string>Use Old MDEC Routines</string>
<string>Enable Texture Dumping</string>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4"/>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="textureReplacementOptions">
<property name="text">
<string>Options...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
@ -1110,66 +1170,22 @@
<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="vramWriteDumping">
<widget class="QCheckBox" name="vramWriteReplacement">
<property name="text">
<string>Enable VRAM Write Dumping</string>
<string>Enable VRAM Write Replacement</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="setVRAMWriteAlphaChannel">
<widget class="QCheckBox" name="vramWriteDumping">
<property name="text">
<string>Set Alpha Channel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="vramWriteDumpThresholdLabel">
<property name="text">
<string>Dump Size Threshold:</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>
<string>Enable VRAM Write Dumping</string>
</property>
</widget>
</item>

View File

@ -0,0 +1,359 @@
<?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>861</width>
<height>726</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>32</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Texture Replacement Settings&lt;/span&gt;&lt;br/&gt;These settings fine-tune the behavior of the texture replacement system. You can also export a game-specific configuration file.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="dumpTexturePages">
<property name="text">
<string>Dump Texture Pages</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Instead of tracking VRAM writes and attempting to identify the &quot;real&quot; 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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="dumpC16Textures">
<property name="text">
<string>Dump C16 Textures</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="reducePaletteRange">
<property name="text">
<string>Reduce Palette Range</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="convertCopiesToWrites">
<property name="text">
<string>Convert Copies To Writes</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Max Write Splits:</string>
</property>
</widget>
</item>
<item row="10" 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="11" column="0" colspan="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="vramWriteDumpThresholdLabel_2">
<property name="text">
<string>Dump Size Threshold:</string>
</property>
</widget>
</item>
<item row="12" 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="13" 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>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="dumpFullTexturePagesLabel">
<property name="text">
<string>Dumps full texture pages instead of sub-rectangles. 256x256 pages will be dumped/replaced instead.</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="dumpFullTexturePages">
<property name="text">
<string>Dump Full Texture Pages</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Background Dumping</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>