GPU: Implement texture dumping and replacement

This commit is contained in:
Connor McLaughlin 2021-06-05 22:19:21 +10:00
parent b80e146ec7
commit 9d3ed39432
27 changed files with 3538 additions and 152 deletions

View File

@ -95,6 +95,8 @@ add_library(core
spu.h
system.cpp
system.h
texture_dumper.cpp
texture_dumper.h
texture_replacements.cpp
texture_replacements.h
timers.cpp

View File

@ -74,6 +74,7 @@
<ClCompile Include="spu.cpp" />
<ClCompile Include="system.cpp" />
<ClCompile Include="texture_replacements.cpp" />
<ClCompile Include="texture_dumper.cpp" />
<ClCompile Include="timers.cpp" />
<ClCompile Include="timing_event.cpp" />
</ItemGroup>
@ -150,6 +151,7 @@
<ClInclude Include="spu.h" />
<ClInclude Include="system.h" />
<ClInclude Include="texture_replacements.h" />
<ClInclude Include="texture_dumper.h" />
<ClInclude Include="timers.h" />
<ClInclude Include="timing_event.h" />
<ClInclude Include="types.h" />

View File

@ -59,6 +59,7 @@
<ClCompile Include="gpu_hw_d3d12.cpp" />
<ClCompile Include="host.cpp" />
<ClCompile Include="game_database.cpp" />
<ClCompile Include="texture_dumper.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -124,5 +125,6 @@
<ClInclude Include="host_settings.h" />
<ClInclude Include="achievements.h" />
<ClInclude Include="game_database.h" />
<ClInclude Include="texture_dumper.h" />
</ItemGroup>
</Project>
</Project>

View File

@ -5,6 +5,7 @@
#include "interrupt_controller.h"
#include "system.h"
#include "texture_replacements.h"
#include "texture_dumper.h"
Log_SetChannel(GPU);
#define CHECK_COMMAND_SIZE(num_words) \
@ -462,7 +463,12 @@ bool GPU::HandleFillRectangleCommand()
Log_DebugPrintf("Fill VRAM rectangle offset=(%u,%u), size=(%u,%u)", dst_x, dst_y, width, height);
if (width > 0 && height > 0)
{
if (g_settings.texture_replacements.dump_textures)
TextureDumper::AddClear(dst_x, dst_y, width, height);
FillVRAM(dst_x, dst_y, width, height, color);
}
m_stats.num_vram_fills++;
AddCommandTicks(46 + ((width / 8) + 9) * height);
@ -513,10 +519,10 @@ void GPU::FinishVRAMWrite()
m_blit_buffer.data(), true);
}
if (g_settings.texture_replacements.ShouldDumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height))
if (g_settings.texture_replacements.dump_textures)
{
g_texture_replacements.DumpVRAMWrite(m_vram_transfer.width, m_vram_transfer.height,
reinterpret_cast<const u16*>(m_blit_buffer.data()));
TextureDumper::AddVRAMWrite(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height,
reinterpret_cast<const u16*>(m_blit_buffer.data()));
}
UpdateVRAM(m_vram_transfer.x, m_vram_transfer.y, m_vram_transfer.width, m_vram_transfer.height,

View File

@ -9,6 +9,8 @@
#include "pgxp.h"
#include "settings.h"
#include "system.h"
#include "texture_dumper.h"
#include "texture_replacements.h"
#include "util/state_wrapper.h"
#include <cmath>
#include <sstream>
@ -63,6 +65,7 @@ bool GPU_HW::Initialize()
m_texture_filtering = g_settings.gpu_texture_filter;
m_using_uv_limits = ShouldUseUVLimits();
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
m_texture_replacements = g_settings.texture_replacements.enable_texture_replacements;
m_downsample_mode = GetDownsampleMode(m_resolution_scale);
if (m_multisamples != g_settings.gpu_multisamples)
@ -90,6 +93,12 @@ bool GPU_HW::Initialize()
"OSDMessage", "Adaptive downsampling is not supported with the current renderer, using box filter instead."),
20.0f);
}
if (m_texture_replacements && !SetupTextureReplacementTexture())
{
Host::AddOSDMessage(
Host::TranslateStdString("OSDMessage", "Failed to setup texture replacements, original textures will be used."),
20.0f);
}
m_pgxp_depth_buffer = g_settings.UsingPGXPDepthBuffer();
@ -148,7 +157,8 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed)
m_true_color != g_settings.gpu_true_color || m_per_sample_shading != per_sample_shading ||
m_scaled_dithering != g_settings.gpu_scaled_dithering || m_texture_filtering != g_settings.gpu_texture_filter ||
m_using_uv_limits != use_uv_limits || m_chroma_smoothing != g_settings.gpu_24bit_chroma_smoothing ||
m_downsample_mode != downsample_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer());
m_downsample_mode != downsample_mode || m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer()) ||
m_texture_replacements != g_settings.texture_replacements.enable_texture_replacements;
if (m_resolution_scale != resolution_scale)
{
@ -183,6 +193,7 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed)
m_texture_filtering = g_settings.gpu_texture_filter;
m_using_uv_limits = use_uv_limits;
m_chroma_smoothing = g_settings.gpu_24bit_chroma_smoothing;
m_texture_replacements = g_settings.texture_replacements.enable_texture_replacements;
m_downsample_mode = downsample_mode;
if (!m_supports_dual_source_blend && TextureFilterRequiresDualSourceBlend(m_texture_filtering))
@ -196,6 +207,16 @@ void GPU_HW::UpdateHWSettings(bool* framebuffer_changed, bool* shaders_changed)
ClearDepthBuffer();
}
if (!SetupTextureReplacementTexture() && m_texture_replacements)
{
Host::AddOSDMessage(
Host::TranslateStdString("OSDMessage", "Failed to setup texture replacements, original textures will be used."),
20.0f);
m_texture_replacements = false;
*shaders_changed = true;
}
UpdateSoftwareRenderer(true);
PrintSettingsToLog();
@ -627,8 +648,20 @@ void GPU_HW::LoadVertices()
if (rc.quad_polygon && m_resolution_scale > 1)
HandleFlippedQuadTextureCoordinates(vertices.data());
if (m_using_uv_limits && textured)
ComputePolygonUVLimits(vertices.data(), num_vertices);
if (textured)
{
if (m_using_uv_limits)
ComputePolygonUVLimits(vertices.data(), num_vertices);
if (g_settings.texture_replacements.dump_textures)
{
if (!m_using_uv_limits)
ComputePolygonUVLimits(vertices.data(), num_vertices);
TextureDumper::AddDraw(m_draw_mode.mode_reg.bits, m_draw_mode.palette_reg, vertices[0].uv_limits & 0xFF,
(vertices[0].uv_limits >> 8) & 0xFF, (vertices[0].uv_limits >> 16) & 0xFF,
(vertices[0].uv_limits >> 24), rc.transparency_enable);
}
}
if (!IsDrawingAreaIsValid())
return;
@ -765,6 +798,35 @@ void GPU_HW::LoadVertices()
if (!IsDrawingAreaIsValid())
return;
if (rc.texture_enable && g_settings.texture_replacements.dump_textures)
{
#if 0
const s32 offset_x =
(pos_x < static_cast<s32>(m_drawing_area.left)) ? (static_cast<s32>(m_drawing_area.left) - pos_x) : 0;
const s32 offset_y =
(pos_y < static_cast<s32>(m_drawing_area.top)) ? (static_cast<s32>(m_drawing_area.top) - pos_y) : 0;
const s32 end_x = pos_x + rectangle_width;
const s32 end_y = pos_y + rectangle_height;
const s32 reduce_x =
(end_x > static_cast<s32>(m_drawing_area.right)) ? (end_x - static_cast<s32>(m_drawing_area.right)) : 0;
const s32 reduce_y =
(end_y > static_cast<s32>(m_drawing_area.bottom)) ? (end_y - static_cast<s32>(m_drawing_area.bottom)) : 0;
if ((offset_x + reduce_x) <= rectangle_width && (offset_y + reduce_y) <= rectangle_height)
{
TextureDumper::AddDraw(m_draw_mode.mode_reg.bits, m_draw_mode.palette_reg,
static_cast<s32>(orig_tex_left) + offset_x, static_cast<s32>(orig_tex_top) + offset_y,
static_cast<s32>(orig_tex_left) + offset_x + (rectangle_width - offset_x - reduce_x),
static_cast<s32>(orig_tex_top) + offset_y + (rectangle_height - offset_y - reduce_y),
rc.transparency_enable);
}
#else
TextureDumper::AddDraw(m_draw_mode.mode_reg.bits, m_draw_mode.palette_reg, orig_tex_left, orig_tex_top,
orig_tex_left + rectangle_width, orig_tex_top + rectangle_height,
rc.transparency_enable);
#endif
}
// we can split the rectangle up into potentially 8 quads
SetBatchDepthBuffer(false);
DebugAssert(GetBatchVertexSpace() >= MAX_VERTICES_FOR_RECTANGLE);

View File

@ -33,6 +33,8 @@ public:
GPU_HW();
virtual ~GPU_HW();
ALWAYS_INLINE bool IsTextureReplacementEnabled() const { return m_texture_replacements; }
const Threading::Thread* GetSWThread() const override;
virtual bool Initialize() override;
@ -43,6 +45,10 @@ public:
std::tuple<u32, u32> GetEffectiveDisplayResolution(bool scaled = true) override final;
std::tuple<u32, u32> GetFullDisplayResolution(bool scaled = true) override final;
virtual void UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride) = 0;
virtual void InvalidateTextureReplacements() = 0;
protected:
enum : u32
{
@ -205,6 +211,7 @@ protected:
virtual void UnmapBatchVertexPointer(u32 used_vertices) = 0;
virtual void UploadUniformBuffer(const void* uniforms, u32 uniforms_size) = 0;
virtual void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) = 0;
virtual bool SetupTextureReplacementTexture() = 0;
u32 CalculateResolutionScale() const;
GPUDownsampleMode GetDownsampleMode(u32 resolution_scale) const;
@ -215,6 +222,11 @@ protected:
return (m_downsample_mode != GPUDownsampleMode::Disabled && !m_GPUSTAT.display_area_color_depth_24);
}
ALWAYS_INLINE bool IsFullVRAMUpload(u32 x, u32 y, u32 width, u32 height)
{
return (x == 0 && y == 0 && width == VRAM_WIDTH && height == VRAM_HEIGHT);
}
void SetFullVRAMDirtyRectangle()
{
m_vram_dirty_rect.Set(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
@ -377,6 +389,7 @@ protected:
BitField<u8, bool, 3, 1> m_per_sample_shading;
BitField<u8, bool, 4, 1> m_scaled_dithering;
BitField<u8, bool, 5, 1> m_chroma_smoothing;
BitField<u8, bool, 6, 1> m_texture_replacements;
u8 bits = 0;
};

View File

@ -159,8 +159,20 @@ void GPU_HW_D3D11::RestoreGraphicsAPIState()
m_context->IASetInputLayout(m_batch_input_layout.Get());
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_context->GSSetShader(nullptr, nullptr, 0);
m_context->PSSetShaderResources(0, 1, m_vram_read_texture.GetD3DSRVArray());
m_context->PSSetSamplers(0, 1, m_point_sampler_state.GetAddressOf());
if (!g_settings.texture_replacements.enable_texture_replacements)
{
m_context->PSSetShaderResources(0, 1, m_vram_read_texture.GetD3DSRVArray());
m_context->PSSetSamplers(0, 1, m_point_sampler_state.GetAddressOf());
}
else
{
ID3D11ShaderResourceView* srvs[2] = {m_vram_read_texture.GetD3DSRV(), m_texture_replacement_texture.GetD3DSRV()};
ID3D11SamplerState* samplers[2] = {m_point_sampler_state.Get(), m_point_sampler_state.Get()};
m_context->PSSetShaderResources(0, countof(srvs), srvs);
m_context->PSSetSamplers(0, countof(samplers), samplers);
}
m_context->OMSetRenderTargets(1, m_vram_texture.GetD3DRTVArray(), m_vram_depth_view.Get());
m_context->RSSetState(m_cull_none_rasterizer_state.Get());
SetViewport(0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight());
@ -175,6 +187,9 @@ void GPU_HW_D3D11::UpdateSettings()
bool framebuffer_changed, shaders_changed;
UpdateHWSettings(&framebuffer_changed, &shaders_changed);
if (!SetupTextureReplacementTexture())
shaders_changed = true;
if (framebuffer_changed)
{
RestoreGraphicsAPIState();
@ -202,6 +217,71 @@ void GPU_HW_D3D11::UpdateSettings()
}
}
bool GPU_HW_D3D11::SetupTextureReplacementTexture()
{
const bool is_enabled = static_cast<bool>(m_texture_replacement_texture);
if (is_enabled == m_texture_replacements)
{
if (!is_enabled ||
(m_texture_replacement_texture.GetWidth() == g_texture_replacements.GetScaledReplacementTextureWidth() &&
m_texture_replacement_texture.GetHeight() == g_texture_replacements.GetScaledReplacementTextureHeight()))
{
// no changes
return true;
}
}
m_texture_replacement_texture.Destroy();
if (m_texture_replacements)
{
const u32 width = g_texture_replacements.GetScaledReplacementTextureWidth();
const u32 height = g_texture_replacements.GetScaledReplacementTextureHeight();
if (!m_texture_replacement_texture.Create(m_device.Get(), width, height,
TextureReplacements::TEXTURE_REPLACEMENT_PAGE_COUNT, 1, 1,
REPLACEMENT_TEXTURE_FORMAT, D3D11_BIND_SHADER_RESOURCE))
{
m_texture_replacement_texture.Destroy();
return false;
}
g_texture_replacements.ReuploadReplacementTextures();
}
return true;
}
void GPU_HW_D3D11::UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride)
{
if (!m_texture_replacement_texture)
return;
#if 0
if (!m_texture_replacement_staging_texture.Map(m_context.Get(), true))
return;
m_texture_replacement_staging_texture.WritePixels(0, 0, data_width, data_height, data_stride,
reinterpret_cast<const u32*>(data));
m_texture_replacement_staging_texture.Unmap(m_context.Get());
const CD3D11_BOX src_box(0, 0, 0, data_width, data_height, 1);
m_context->CopySubresourceRegion(m_texture_replacement_texture, D3D11CalcSubresource(0, page_index, 1), page_x,
page_y, 0, m_texture_replacement_staging_texture.GetD3DTexture(), 0, &src_box);
#else
const CD3D11_BOX dst_box(page_x, page_y, 0, page_x + data_width, page_y + data_height, 1);
m_context->UpdateSubresource(m_texture_replacement_texture, D3D11CalcSubresource(0, page_index, 1), &dst_box, data,
data_stride, data_stride * data_height);
#endif
}
void GPU_HW_D3D11::InvalidateTextureReplacements()
{
m_texture_replacement_texture.Destroy();
if (!SetupTextureReplacementTexture())
Panic("Failed to reallocate texture replacement textures");
}
void GPU_HW_D3D11::MapBatchVertexPointer(u32 required_vertices)
{
DebugAssert(!m_batch_start_vertex_ptr);
@ -567,7 +647,7 @@ bool GPU_HW_D3D11::CompileShaders()
{
const std::string ps = shadergen.GenerateBatchFragmentShader(
static_cast<BatchRenderMode>(render_mode), static_cast<GPUTextureMode>(texture_mode),
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing));
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing), m_texture_replacements);
m_batch_pixel_shaders[render_mode][texture_mode][dithering][interlacing] =
shader_cache.GetPixelShader(m_device.Get(), ps);
@ -991,14 +1071,20 @@ void GPU_HW_D3D11::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* d
const Common::Rectangle<u32> bounds = GetVRAMTransferBounds(x, y, width, height);
GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask);
if (!check_mask)
if (!check_mask && !IsFullVRAMUpload(x, y, width, height))
{
const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
width * m_resolution_scale, height * m_resolution_scale))
if (g_settings.texture_replacements.enable_vram_write_replacements)
{
return;
const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
width * m_resolution_scale, height * m_resolution_scale))
{
return;
}
}
if (m_texture_replacements)
g_texture_replacements.UploadReplacementTextures(x, y, width, height, data);
}
const u32 num_pixels = width * height;

View File

@ -29,6 +29,10 @@ public:
void RestoreGraphicsAPIState() override;
void UpdateSettings() override;
void UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride) override;
void InvalidateTextureReplacements() override;
protected:
void ClearDisplay() override;
void UpdateDisplay() override;
@ -44,6 +48,7 @@ protected:
void UnmapBatchVertexPointer(u32 used_vertices) override;
void UploadUniformBuffer(const void* data, u32 data_size) override;
void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) override;
bool SetupTextureReplacementTexture() override;
private:
enum : u32
@ -52,6 +57,8 @@ private:
MAX_UNIFORM_BUFFER_SIZE = 64
};
static constexpr DXGI_FORMAT REPLACEMENT_TEXTURE_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
void SetCapabilities();
bool CreateFramebuffer();
void ClearFramebuffer();
@ -128,6 +135,7 @@ private:
std::array<std::array<ComPtr<ID3D11PixelShader>, 3>, 2> m_display_pixel_shaders; // [depth_24][interlaced]
D3D11::Texture m_vram_replacement_texture;
D3D11::Texture m_texture_replacement_texture;
// downsampling
ComPtr<ID3D11PixelShader> m_downsample_first_pass_pixel_shader;

View File

@ -12,6 +12,7 @@
#include "gpu_hw_shadergen.h"
#include "host_display.h"
#include "system.h"
#include "texture_replacements.h"
Log_SetChannel(GPU_HW_D3D12);
GPU_HW_D3D12::GPU_HW_D3D12() = default;
@ -117,11 +118,21 @@ void GPU_HW_D3D12::RestoreGraphicsAPIState()
cmdlist->IASetVertexBuffers(0, 1, &vbv);
cmdlist->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmdlist->SetGraphicsRootSignature(m_batch_root_signature.Get());
if (m_texture_replacements)
{
cmdlist->SetGraphicsRootSignature(m_texture_replacement_root_signature.Get());
cmdlist->SetGraphicsRootDescriptorTable(1, m_texture_replacement_srv_handle.gpu_handle);
cmdlist->SetGraphicsRootDescriptorTable(2, m_texture_replacement_sampler_handle.gpu_handle);
}
else
{
cmdlist->SetGraphicsRootSignature(m_batch_root_signature.Get());
cmdlist->SetGraphicsRootDescriptorTable(1, m_vram_read_texture.GetSRVDescriptor().gpu_handle);
cmdlist->SetGraphicsRootDescriptorTable(2, m_point_sampler.gpu_handle);
}
cmdlist->SetGraphicsRootConstantBufferView(0,
m_uniform_stream_buffer.GetGPUPointer() + m_current_uniform_buffer_offset);
cmdlist->SetGraphicsRootDescriptorTable(1, m_vram_read_texture.GetSRVDescriptor().gpu_handle);
cmdlist->SetGraphicsRootDescriptorTable(2, m_point_sampler.gpu_handle);
D3D12::SetViewport(cmdlist, 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight());
@ -167,6 +178,154 @@ void GPU_HW_D3D12::UpdateSettings()
}
}
bool GPU_HW_D3D12::SetupTextureReplacementTexture()
{
const bool is_enabled = static_cast<bool>(m_texture_replacement_texture);
if (is_enabled == m_texture_replacements)
{
if (!is_enabled ||
(m_texture_replacement_texture.GetWidth() == g_texture_replacements.GetScaledReplacementTextureWidth() &&
m_texture_replacement_texture.GetHeight() == g_texture_replacements.GetScaledReplacementTextureHeight()))
{
// no changes
return true;
}
}
g_d3d12_context->ExecuteCommandList(true);
m_texture_replacement_texture.Destroy(false);
if (m_texture_replacements)
{
#if 1
Panic("Fixme");
#else
const u32 width = g_texture_replacements.GetScaledReplacementTextureWidth();
const u32 height = g_texture_replacements.GetScaledReplacementTextureHeight();
if (!m_texture_replacement_texture.Create(width, height, TextureReplacements::TEXTURE_REPLACEMENT_PAGE_COUNT, 1, 1,
REPLACEMENT_TEXTURE_FORMAT, REPLACEMENT_TEXTURE_FORMAT,
REPLACEMENT_TEXTURE_FORMAT, DXGI_FORMAT_UNKNOWN,
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET))
{
return false;
}
D3D12::SetObjectName(m_texture_replacement_texture, "Texture replacement texture");
InvalidateTextureReplacements();
if (!m_texture_replacement_srv_handle)
{
g_d3d12_context->GetDescriptorHeapManager().Allocate(&m_texture_replacement_srv_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
}
if (!m_texture_replacement_sampler_handle)
{
g_d3d12_context->GetSamplerHeapManager().Allocate(&m_texture_replacement_sampler_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
}
UpdateTextureReplacementSRV();
#endif
}
else
{
if (m_texture_replacement_srv_handle)
{
g_d3d12_context->GetDescriptorHeapManager().Free(&m_texture_replacement_srv_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
}
if (m_texture_replacement_sampler_handle)
{
g_d3d12_context->GetSamplerHeapManager().Free(&m_texture_replacement_sampler_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
}
}
if (m_texture_replacements)
g_texture_replacements.ReuploadReplacementTextures();
return true;
}
void GPU_HW_D3D12::UpdateTextureReplacementSRV()
{
#if 0
if (!m_texture_replacement_texture || !m_vram_read_texture)
return;
ID3D12Device* dev = g_d3d12_context->GetDevice();
D3D12_SHADER_RESOURCE_VIEW_DESC vram_desc = {m_vram_read_texture.GetFormat(), D3D12_SRV_DIMENSION_TEXTURE2D,
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
vram_desc.Texture2D.MipLevels = m_vram_read_texture.GetLevels();
dev->CreateShaderResourceView(m_vram_read_texture, &vram_desc, m_texture_replacement_srv_handle);
D3D12_SHADER_RESOURCE_VIEW_DESC replacement_desc = {m_texture_replacement_texture.GetFormat(),
D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
replacement_desc.Texture2DArray.MipLevels = m_texture_replacement_texture.GetLevels();
replacement_desc.Texture2DArray.ArraySize = m_texture_replacement_texture.GetLayers();
dev->CreateShaderResourceView(
m_texture_replacement_texture, &replacement_desc,
g_d3d12_context->GetDescriptorHeapManager().OffsetCPUHandle(m_texture_replacement_srv_handle, 1));
D3D12_SAMPLER_DESC sd;
D3D12::SetDefaultSampler(&sd);
sd.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sd.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sd.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
dev->CreateSampler(&sd, m_texture_replacement_sampler_handle);
sd.Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
dev->CreateSampler(
&sd, g_d3d12_context->GetDescriptorHeapManager().OffsetCPUHandle(m_texture_replacement_sampler_handle, 1));
#endif
}
void GPU_HW_D3D12::UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride)
{
#if 0
if (!CreateTextureReplacementStreamBuffer())
return;
const u32 copy_pitch = Common::AlignUpPow2<u32>(data_width * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 required_size = copy_pitch * data_height;
if (!m_texture_replacment_stream_buffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT))
{
Log_PerfPrint("Executing command buffer while waiting for texture replacement buffer space");
g_d3d12_context->ExecuteCommandList(false);
RestoreGraphicsAPIState();
if (!m_texture_replacment_stream_buffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT))
{
Log_ErrorPrintf("Failed to allocate %u bytes from texture replacement streaming buffer", required_size);
return;
}
}
// buffer -> texture
const u32 sb_offset = m_texture_replacment_stream_buffer.GetCurrentOffset();
D3D12::Texture::CopyToUploadBuffer(data, data_stride, data_height,
m_texture_replacment_stream_buffer.GetCurrentHostPointer(), copy_pitch);
m_texture_replacment_stream_buffer.CommitMemory(required_size);
m_texture_replacement_texture.CopyFromBuffer(page_index, page_x, page_y, data_width, data_height, copy_pitch,
m_texture_replacment_stream_buffer.GetBuffer(), sb_offset);
m_texture_replacement_texture.TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
#endif
}
void GPU_HW_D3D12::InvalidateTextureReplacements()
{
if (!m_texture_replacement_texture)
return;
static constexpr std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 0.0f};
m_texture_replacement_texture.TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
g_d3d12_context->GetCommandList()->ClearRenderTargetView(m_texture_replacement_texture.GetRTVOrDSVDescriptor(),
clear_color.data(), 0, nullptr);
m_texture_replacement_texture.TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
void GPU_HW_D3D12::MapBatchVertexPointer(u32 required_vertices)
{
DebugAssert(!m_batch_start_vertex_ptr);
@ -254,8 +413,14 @@ void GPU_HW_D3D12::DestroyResources()
DestroyFramebuffer();
DestroyPipelines();
m_texture_replacement_texture.Destroy(false);
g_d3d12_context->GetSamplerHeapManager().Free(&m_texture_replacement_sampler_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
g_d3d12_context->GetSamplerHeapManager().Free(&m_point_sampler);
g_d3d12_context->GetSamplerHeapManager().Free(&m_linear_sampler);
g_d3d12_context->GetDescriptorHeapManager().Free(&m_texture_replacement_srv_handle,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT);
g_d3d12_context->GetDescriptorHeapManager().Free(&m_texture_stream_buffer_srv);
m_vertex_stream_buffer.Destroy(false);
@ -263,6 +428,7 @@ void GPU_HW_D3D12::DestroyResources()
m_texture_stream_buffer.Destroy(false);
m_single_sampler_root_signature.Reset();
m_texture_replacement_root_signature.Reset();
m_batch_root_signature.Reset();
}
@ -277,6 +443,14 @@ bool GPU_HW_D3D12::CreateRootSignatures()
if (!m_batch_root_signature)
return false;
rsbuilder.SetInputAssemblerFlag();
rsbuilder.AddCBVParameter(0, D3D12_SHADER_VISIBILITY_ALL);
rsbuilder.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 2, D3D12_SHADER_VISIBILITY_PIXEL);
rsbuilder.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 2, D3D12_SHADER_VISIBILITY_PIXEL);
m_texture_replacement_root_signature = rsbuilder.Create();
if (!m_texture_replacement_root_signature)
return false;
rsbuilder.Add32BitConstants(0, MAX_PUSH_CONSTANTS_SIZE / sizeof(u32), D3D12_SHADER_VISIBILITY_ALL);
rsbuilder.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 1, D3D12_SHADER_VISIBILITY_PIXEL);
rsbuilder.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, 1, D3D12_SHADER_VISIBILITY_PIXEL);
@ -341,6 +515,8 @@ bool GPU_HW_D3D12::CreateFramebuffer()
D3D12::SetObjectName(m_display_texture, "VRAM Display Texture");
D3D12::SetObjectName(m_vram_read_texture, "VRAM Readback Texture");
UpdateTextureReplacementSRV();
m_vram_texture.TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
m_vram_depth_texture.TransitionToState(D3D12_RESOURCE_STATE_DEPTH_WRITE);
m_vram_read_texture.TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
@ -446,7 +622,7 @@ bool GPU_HW_D3D12::CompilePipelines()
{
const std::string fs = shadergen.GenerateBatchFragmentShader(
static_cast<BatchRenderMode>(render_mode), static_cast<GPUTextureMode>(texture_mode),
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing));
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing), m_texture_replacements);
batch_fragment_shaders[render_mode][texture_mode][dithering][interlacing] = shader_cache.GetPixelShader(fs);
if (!batch_fragment_shaders[render_mode][texture_mode][dithering][interlacing])
@ -475,7 +651,8 @@ bool GPU_HW_D3D12::CompilePipelines()
{
const bool textured = (static_cast<GPUTextureMode>(texture_mode) != GPUTextureMode::Disabled);
gpbuilder.SetRootSignature(m_batch_root_signature.Get());
gpbuilder.SetRootSignature(m_texture_replacements ? m_texture_replacement_root_signature.Get() :
m_batch_root_signature.Get());
gpbuilder.SetRenderTarget(0, m_vram_texture.GetFormat());
gpbuilder.SetDepthStencilFormat(m_vram_depth_texture.GetFormat());
@ -1035,7 +1212,7 @@ void GPU_HW_D3D12::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* d
const Common::Rectangle<u32> bounds = GetVRAMTransferBounds(x, y, width, height);
GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask);
if (!check_mask)
if (!check_mask && !IsFullVRAMUpload(x, y, width, height))
{
const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
@ -1043,6 +1220,9 @@ void GPU_HW_D3D12::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* d
{
return;
}
if (m_texture_replacements)
g_texture_replacements.UploadReplacementTextures(x, y, width, height, data);
}
const u32 data_size = width * height * sizeof(u16);
@ -1175,6 +1355,9 @@ void GPU_HW_D3D12::UpdateDepthBufferFromMaskBit()
m_vram_texture.TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
if (m_texture_replacements)
cmdlist->SetGraphicsRootSignature(m_batch_root_signature.Get());
cmdlist->OMSetRenderTargets(0, nullptr, FALSE, &m_vram_depth_texture.GetRTVOrDSVDescriptor().cpu_handle);
cmdlist->SetGraphicsRootDescriptorTable(1, m_vram_texture.GetSRVDescriptor());
cmdlist->SetPipelineState(m_vram_update_depth_pipeline.Get());

View File

@ -27,6 +27,10 @@ public:
void RestoreGraphicsAPIState() override;
void UpdateSettings() override;
void UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride) override;
void InvalidateTextureReplacements() override;
protected:
void ClearDisplay() override;
void UpdateDisplay() override;
@ -42,13 +46,18 @@ protected:
void UnmapBatchVertexPointer(u32 used_vertices) override;
void UploadUniformBuffer(const void* data, u32 data_size) override;
void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) override;
bool SetupTextureReplacementTexture() override;
private:
enum : u32
{
MAX_PUSH_CONSTANTS_SIZE = 64,
TEXTURE_REPLACEMENT_BUFFER_SIZE = 64 * 1024 * 1024,
TEXTURE_REPLACEMENT_BATCH_TEXTURE_COUNT = 2,
};
static constexpr DXGI_FORMAT REPLACEMENT_TEXTURE_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
void SetCapabilities();
void DestroyResources();
@ -68,8 +77,10 @@ private:
bool CreateTextureReplacementStreamBuffer();
bool BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
void UpdateTextureReplacementSRV();
ComPtr<ID3D12RootSignature> m_batch_root_signature;
ComPtr<ID3D12RootSignature> m_texture_replacement_root_signature;
ComPtr<ID3D12RootSignature> m_single_sampler_root_signature;
D3D12::Texture m_vram_texture;
@ -107,5 +118,8 @@ private:
ComPtr<ID3D12PipelineState> m_copy_pipeline;
D3D12::Texture m_vram_write_replacement_texture;
D3D12::Texture m_texture_replacement_texture;
D3D12::StreamBuffer m_texture_replacment_stream_buffer;
D3D12::DescriptorHandle m_texture_replacement_srv_handle;
D3D12::DescriptorHandle m_texture_replacement_sampler_handle;
};

View File

@ -232,6 +232,14 @@ void GPU_HW_OpenGL::RestoreGraphicsAPIState()
glBindVertexArray(m_vao_id);
m_uniform_stream_buffer->Bind();
m_vram_read_texture.Bind();
if (m_texture_replacement_texture.IsValid())
{
glActiveTexture(GL_TEXTURE1);
m_texture_replacement_texture.Bind();
glActiveTexture(GL_TEXTURE0);
}
SetBlendMode();
m_current_depth_test = 0;
SetDepthFunc();
@ -267,6 +275,79 @@ void GPU_HW_OpenGL::UpdateSettings()
}
}
bool GPU_HW_OpenGL::SetupTextureReplacementTexture()
{
const bool is_enabled = m_texture_replacement_texture.IsValid();
if (is_enabled == m_texture_replacements)
{
if (!is_enabled ||
(m_texture_replacement_texture.GetWidth() == g_texture_replacements.GetScaledReplacementTextureWidth() &&
m_texture_replacement_texture.GetHeight() == g_texture_replacements.GetScaledReplacementTextureHeight()))
{
// no changes
return true;
}
}
m_texture_replacement_texture.Destroy();
if (m_texture_replacements)
{
const u32 width = g_texture_replacements.GetScaledReplacementTextureWidth();
const u32 height = g_texture_replacements.GetScaledReplacementTextureHeight();
if (!m_texture_replacement_texture.Create(width, height, TextureReplacements::TEXTURE_REPLACEMENT_PAGE_COUNT, 1, 1,
GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false, true))
{
m_texture_replacement_texture.Destroy();
return false;
}
g_texture_replacements.ReuploadReplacementTextures();
}
return true;
}
void GPU_HW_OpenGL::UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride)
{
if (!m_texture_replacement_texture.IsValid())
return;
// TODO: Get rid of this crap.
std::vector<u32> temp;
{
temp.resize(data_width * data_height);
const u8* source_ptr = static_cast<const u8*>(data) + (data_stride * (data_height - 1));
u8* dest_ptr = reinterpret_cast<u8*>(temp.data());
const u32 copy_size = data_width * sizeof(u32);
for (u32 row = 0; row < data_height; row++)
{
std::memcpy(dest_ptr, source_ptr, copy_size);
dest_ptr += copy_size;
source_ptr -= data_stride;
}
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, data_width);
m_texture_replacement_texture.Bind();
m_texture_replacement_texture.ReplaceSubImage(page_index, 0, page_x,
m_texture_replacement_texture.GetHeight() - page_y - data_height,
data_width, data_height, GL_RGBA, GL_UNSIGNED_BYTE, temp.data());
m_vram_read_texture.Bind();
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
void GPU_HW_OpenGL::InvalidateTextureReplacements()
{
m_texture_replacement_texture.Destroy();
if (!SetupTextureReplacementTexture())
Panic("Failed to re-create replacement textures for invalidation");
}
void GPU_HW_OpenGL::MapBatchVertexPointer(u32 required_vertices)
{
DebugAssert(!m_batch_start_vertex_ptr);
@ -534,7 +615,7 @@ bool GPU_HW_OpenGL::CompilePrograms()
const std::string batch_vs = shadergen.GenerateBatchVertexShader(textured);
const std::string fs = shadergen.GenerateBatchFragmentShader(
static_cast<BatchRenderMode>(render_mode), static_cast<GPUTextureMode>(texture_mode),
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing));
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing), m_texture_replacements);
const auto link_callback = [this, textured, use_binding_layout](GL::Program& prog) {
if (!use_binding_layout)
@ -1066,7 +1147,7 @@ void GPU_HW_OpenGL::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
const Common::Rectangle<u32> bounds = GetVRAMTransferBounds(x, y, width, height);
GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask);
if (!check_mask)
if (!check_mask && !IsFullVRAMUpload(x, y, width, height))
{
const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
@ -1074,6 +1155,9 @@ void GPU_HW_OpenGL::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
{
return;
}
if (m_texture_replacements)
g_texture_replacements.UploadReplacementTextures(x, y, width, height, data);
}
const u32 num_pixels = width * height;

View File

@ -26,6 +26,10 @@ public:
void RestoreGraphicsAPIState() override;
void UpdateSettings() override;
void UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride) override;
void InvalidateTextureReplacements();
protected:
void ClearDisplay() override;
void UpdateDisplay() override;
@ -41,6 +45,7 @@ protected:
void UnmapBatchVertexPointer(u32 used_vertices) override;
void UploadUniformBuffer(const void* data, u32 data_size) override;
void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) override;
bool SetupTextureReplacementTexture() override;
private:
struct GLStats
@ -84,6 +89,7 @@ private:
GL::Texture m_vram_encoding_texture;
GL::Texture m_display_texture;
GL::Texture m_vram_write_replacement_texture;
GL::Texture m_texture_replacement_texture;
std::unique_ptr<GL::StreamBuffer> m_vertex_stream_buffer;
GLuint m_vram_fbo_id = 0;

View File

@ -665,7 +665,8 @@ void FilteredSampleFromVRAM(uint4 texpage, float2 coords, float4 uv_limits,
}
std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMode transparency,
GPUTextureMode texture_mode, bool dithering, bool interlacing)
GPUTextureMode texture_mode, bool dithering, bool interlacing,
bool texture_replacement)
{
const GPUTextureMode actual_texture_mode = texture_mode & ~GPUTextureMode::RawTextureBit;
const bool raw_texture = (texture_mode & GPUTextureMode::RawTextureBit) == GPUTextureMode::RawTextureBit;
@ -694,11 +695,15 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(GPU_HW::BatchRenderMod
DefineMacro(ss, "UV_LIMITS", m_uv_limits);
DefineMacro(ss, "USE_DUAL_SOURCE", use_dual_source);
DefineMacro(ss, "PGXP_DEPTH", m_pgxp_depth);
DefineMacro(ss, "TEXTURE_REPLACEMENT", texture_replacement);
WriteCommonFunctions(ss);
WriteBatchUniformBuffer(ss);
DeclareTexture(ss, "samp0", 0);
if (texture_replacement)
DeclareTextureArray(ss, "samp1", 1);
if (m_glsl)
ss << "CONSTANT int[16] s_dither_values = int[16]( ";
else
@ -793,7 +798,32 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
#endif
}
#if TEXTURE_REPLACEMENT
float4 SampleReplacementTexture(uint4 texpage, float2 coords)
{
uint2 replace_icoord = ApplyUpscaledTextureWindow(FloatToIntegerCoords(coords));
float2 replace_ncoord = float2(replace_icoord) / float2(256u * RESOLUTION_SCALE, 256u * RESOLUTION_SCALE);
uint replace_layer = ((texpage.y / (256u * RESOLUTION_SCALE)) * 16) + (texpage.x / (64u * RESOLUTION_SCALE));
#if PALETTE_8_BIT
uint extra_pages = (uint(coords.x) / (128u * RESOLUTION_SCALE));
replace_layer += extra_pages;
replace_ncoord.x -= float(extra_pages) * (128.0f / 256.0f);
#elif !PALETTE
uint extra_pages = (uint(coords.x) / (64u * RESOLUTION_SCALE));
replace_layer += extra_pages;
replace_ncoord.x -= float(extra_pages) * (64.0f / 256.0f);
#endif
#if API_OPENGL || API_OPENGL_ES
replace_ncoord.y = 1.0 - replace_ncoord.y;
#endif
return SAMPLE_TEXTURE_ARRAY(samp1, replace_ncoord, float(replace_layer));
}
#endif
#endif // TEXTURED
)";
if (textured)
@ -834,14 +864,6 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
#endif
#if TEXTURED
// We can't currently use upscaled coordinate for palettes because of how they're packed.
// Not that it would be any benefit anyway, render-to-texture effects don't use palettes.
float2 coords = v_tex0;
#if PALETTE
coords /= float2(RESOLUTION_SCALE, RESOLUTION_SCALE);
#endif
#if UV_LIMITS
float4 uv_limits = v_uv_limits;
#if !PALETTE
@ -853,20 +875,41 @@ float4 SampleFromVRAM(uint4 texpage, float2 coords)
#endif
float4 texcol;
#if TEXTURE_FILTERING
FilteredSampleFromVRAM(v_texpage, coords, uv_limits, texcol, ialpha);
if (ialpha < 0.5)
discard;
#else
#if UV_LIMITS
texcol = SampleFromVRAM(v_texpage, clamp(coords, uv_limits.xy, uv_limits.zw));
#else
texcol = SampleFromVRAM(v_texpage, coords);
#endif
if (VECTOR_EQ(texcol, TRANSPARENT_PIXEL_COLOR))
discard;
ialpha = 1.0;
#if TEXTURE_REPLACEMENT
// TODO: Do we want to apply UV limits here?
texcol = SampleReplacementTexture(v_texpage, v_tex0);
if (texcol.a >= 0.5)
{
ialpha = (texcol.a * 2.0) - 1.0;
}
else
{
#endif
// We can't currently use upscaled coordinate for palettes because of how they're packed.
// Not that it would be any benefit anyway, render-to-texture effects don't use palettes.
float2 coords = v_tex0;
#if PALETTE
coords /= float2(RESOLUTION_SCALE, RESOLUTION_SCALE);
#endif
#if TEXTURE_FILTERING
FilteredSampleFromVRAM(v_texpage, coords, uv_limits, texcol, ialpha);
if (ialpha < 0.5)
discard;
#else
#if UV_LIMITS
texcol = SampleFromVRAM(v_texpage, clamp(coords, uv_limits.xy, uv_limits.zw));
#else
texcol = SampleFromVRAM(v_texpage, coords);
#endif
if (VECTOR_EQ(texcol, TRANSPARENT_PIXEL_COLOR))
discard;
ialpha = 1.0;
#endif
#if TEXTURE_REPLACEMENT
}
#endif
semitransparent = (texcol.a >= 0.5);

View File

@ -12,7 +12,7 @@ public:
std::string GenerateBatchVertexShader(bool textured);
std::string GenerateBatchFragmentShader(GPU_HW::BatchRenderMode transparency, GPUTextureMode texture_mode,
bool dithering, bool interlacing);
bool dithering, bool interlacing, bool texture_replacement);
std::string GenerateDisplayFragmentShader(bool depth_24bit, GPU_HW::InterlacedRenderMode interlace_mode,
bool smooth_chroma);
std::string GenerateVRAMReadFragmentShader();

View File

@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "common/vulkan/builders.h"
#include "common/vulkan/context.h"
@ -197,8 +198,10 @@ void GPU_HW_Vulkan::RestoreGraphicsAPIState()
VkDeviceSize vertex_buffer_offset = 0;
vkCmdBindVertexBuffers(cmdbuf, 0, 1, m_vertex_stream_buffer.GetBufferPointer(), &vertex_buffer_offset);
Vulkan::Util::SetViewport(cmdbuf, 0, 0, m_vram_texture.GetWidth(), m_vram_texture.GetHeight());
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_batch_pipeline_layout, 0, 1,
&m_batch_descriptor_set, 1, &m_current_uniform_buffer_offset);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_texture_replacements ? m_texture_replacement_batch_pipeline_layout :
m_batch_pipeline_layout,
0, 1, &m_batch_descriptor_set, 1, &m_current_uniform_buffer_offset);
SetScissorFromDrawingArea();
}
@ -241,6 +244,83 @@ void GPU_HW_Vulkan::UpdateSettings()
}
}
bool GPU_HW_Vulkan::SetupTextureReplacementTexture()
{
const bool is_enabled = m_texture_replacement_texture.IsValid();
if (is_enabled == m_texture_replacements)
{
if (!is_enabled ||
(m_texture_replacement_texture.GetWidth() == g_texture_replacements.GetScaledReplacementTextureWidth() &&
m_texture_replacement_texture.GetHeight() == g_texture_replacements.GetScaledReplacementTextureHeight()))
{
// no changes
return true;
}
}
g_vulkan_context->ExecuteCommandBuffer(true);
m_texture_replacement_texture.Destroy(false);
if (m_texture_replacements)
{
const u32 width = g_texture_replacements.GetScaledReplacementTextureWidth();
const u32 height = g_texture_replacements.GetScaledReplacementTextureHeight();
if (!m_texture_replacement_texture.Create(width, height, 1, TextureReplacements::TEXTURE_REPLACEMENT_PAGE_COUNT,
VK_FORMAT_R8G8B8A8_UNORM, VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_VIEW_TYPE_2D_ARRAY, VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT))
{
return false;
}
const VkClearColorValue cv = {};
const VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0,
m_texture_replacement_texture.GetLayers()};
m_texture_replacement_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdClearColorImage(g_vulkan_context->GetCurrentCommandBuffer(), m_texture_replacement_texture.GetImage(),
m_texture_replacement_texture.GetLayout(), &cv, 1, &range);
m_texture_replacement_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
if (m_batch_descriptor_set != VK_NULL_HANDLE && !CreateBatchDesciptorSet(m_texture_replacements))
{
if (!CreateBatchDesciptorSet(false))
Panic("Failed to create non-texture-replacement descriptor set");
return false;
}
if (m_texture_replacements)
g_texture_replacements.ReuploadReplacementTextures();
return true;
}
void GPU_HW_Vulkan::UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride)
{
// buffer -> texture
EndRenderPass();
m_texture_replacement_texture.Update(page_x, page_y, data_width, data_height, 0, page_index, data, data_stride);
}
void GPU_HW_Vulkan::InvalidateTextureReplacements()
{
if (!m_texture_replacement_texture.IsValid())
return;
const VkClearColorValue cv = {};
const VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, m_texture_replacement_texture.GetLayers()};
m_texture_replacement_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkCmdClearColorImage(g_vulkan_context->GetCurrentCommandBuffer(), m_texture_replacement_texture.GetImage(),
m_texture_replacement_texture.GetLayout(), &cv, 1, &range);
m_texture_replacement_texture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
void GPU_HW_Vulkan::MapBatchVertexPointer(u32 required_vertices)
{
DebugAssert(!m_batch_start_vertex_ptr);
@ -287,7 +367,9 @@ void GPU_HW_Vulkan::UploadUniformBuffer(const void* data, u32 data_size)
m_uniform_stream_buffer.CommitMemory(data_size);
vkCmdBindDescriptorSets(g_vulkan_context->GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS,
m_batch_pipeline_layout, 0, 1, &m_batch_descriptor_set, 1, &m_current_uniform_buffer_offset);
m_texture_replacements ? m_texture_replacement_batch_pipeline_layout :
m_batch_pipeline_layout,
0, 1, &m_batch_descriptor_set, 1, &m_current_uniform_buffer_offset);
}
void GPU_HW_Vulkan::SetCapabilities()
@ -369,9 +451,11 @@ void GPU_HW_Vulkan::DestroyResources()
Vulkan::Util::SafeDestroyPipelineLayout(m_vram_write_pipeline_layout);
Vulkan::Util::SafeDestroyPipelineLayout(m_single_sampler_pipeline_layout);
Vulkan::Util::SafeDestroyPipelineLayout(m_no_samplers_pipeline_layout);
Vulkan::Util::SafeDestroyPipelineLayout(m_texture_replacement_batch_pipeline_layout);
Vulkan::Util::SafeDestroyPipelineLayout(m_batch_pipeline_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_vram_write_descriptor_set_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_single_sampler_descriptor_set_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_texture_replacement_batch_descriptor_set_layout);
Vulkan::Util::SafeDestroyDescriptorSetLayout(m_batch_descriptor_set_layout);
Vulkan::Util::SafeDestroySampler(m_point_sampler);
Vulkan::Util::SafeDestroySampler(m_linear_sampler);
@ -438,6 +522,17 @@ bool GPU_HW_Vulkan::CreatePipelineLayouts()
Vulkan::Util::SetObjectName(g_vulkan_context->GetDevice(), m_batch_descriptor_set_layout,
"Batch Descriptor Set Layout");
dslbuilder.AddBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
dslbuilder.AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
dslbuilder.AddBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
m_texture_replacement_batch_descriptor_set_layout = dslbuilder.Create(device);
if (m_texture_replacement_batch_descriptor_set_layout == VK_NULL_HANDLE)
return false;
Vulkan::Util::SetObjectName(g_vulkan_context->GetDevice(), m_texture_replacement_batch_descriptor_set_layout,
"Batch Replacement Descriptor Set Layout");
// textures start at 1
dslbuilder.AddBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
m_single_sampler_descriptor_set_layout = dslbuilder.Create(device);
@ -463,6 +558,11 @@ bool GPU_HW_Vulkan::CreatePipelineLayouts()
return false;
Vulkan::Util::SetObjectName(g_vulkan_context->GetDevice(), m_batch_pipeline_layout, "Batch Pipeline Layout");
plbuilder.AddDescriptorSet(m_texture_replacement_batch_descriptor_set_layout);
m_texture_replacement_batch_pipeline_layout = plbuilder.Create(device);
if (m_texture_replacement_batch_pipeline_layout == VK_NULL_HANDLE)
return false;
plbuilder.AddDescriptorSet(m_single_sampler_descriptor_set_layout);
plbuilder.AddPushConstants(VK_SHADER_STAGE_FRAGMENT_BIT, 0, MAX_PUSH_CONSTANTS_SIZE);
m_single_sampler_pipeline_layout = plbuilder.Create(device);
@ -663,22 +763,20 @@ bool GPU_HW_Vulkan::CreateFramebuffer()
m_vram_depth_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
m_vram_read_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (!CreateBatchDesciptorSet(m_texture_replacements))
return false;
Vulkan::DescriptorSetUpdateBuilder dsubuilder;
m_batch_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_batch_descriptor_set_layout);
m_vram_copy_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout);
m_vram_read_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout);
m_display_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(m_single_sampler_descriptor_set_layout);
if (m_batch_descriptor_set == VK_NULL_HANDLE || m_vram_copy_descriptor_set == VK_NULL_HANDLE ||
m_vram_read_descriptor_set == VK_NULL_HANDLE || m_display_descriptor_set == VK_NULL_HANDLE)
if (m_vram_copy_descriptor_set == VK_NULL_HANDLE || m_vram_read_descriptor_set == VK_NULL_HANDLE ||
m_display_descriptor_set == VK_NULL_HANDLE)
{
return false;
}
dsubuilder.AddBufferDescriptorWrite(m_batch_descriptor_set, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
m_uniform_stream_buffer.GetBuffer(), 0, sizeof(BatchUBOData));
dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_batch_descriptor_set, 1, m_vram_read_texture.GetView(),
m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_vram_copy_descriptor_set, 1, m_vram_read_texture.GetView(),
m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_vram_read_descriptor_set, 1, m_vram_texture.GetView(),
@ -790,6 +888,33 @@ bool GPU_HW_Vulkan::CreateFramebuffer()
return true;
}
bool GPU_HW_Vulkan::CreateBatchDesciptorSet(bool texture_replacements)
{
if (m_batch_descriptor_set != VK_NULL_HANDLE)
g_vulkan_context->FreeGlobalDescriptorSet(m_batch_descriptor_set);
m_batch_descriptor_set = g_vulkan_context->AllocateGlobalDescriptorSet(
texture_replacements ? m_texture_replacement_batch_descriptor_set_layout : m_batch_descriptor_set_layout);
if (m_batch_descriptor_set == VK_NULL_HANDLE)
return false;
Vulkan::DescriptorSetUpdateBuilder dsubuilder;
dsubuilder.AddBufferDescriptorWrite(m_batch_descriptor_set, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
m_uniform_stream_buffer.GetBuffer(), 0, sizeof(BatchUBOData));
dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_batch_descriptor_set, 1, m_vram_read_texture.GetView(),
m_point_sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
if (texture_replacements)
{
Assert(m_texture_replacement_texture.IsValid());
dsubuilder.AddCombinedImageSamplerDescriptorWrite(m_batch_descriptor_set, 2,
m_texture_replacement_texture.GetView(), m_point_sampler,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
dsubuilder.Update(g_vulkan_context->GetDevice());
return true;
}
void GPU_HW_Vulkan::ClearFramebuffer()
{
VkCommandBuffer cmdbuf = g_vulkan_context->GetCurrentCommandBuffer();
@ -958,7 +1083,7 @@ bool GPU_HW_Vulkan::CompilePipelines()
{
const std::string fs = shadergen.GenerateBatchFragmentShader(
static_cast<BatchRenderMode>(render_mode), static_cast<GPUTextureMode>(texture_mode),
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing));
ConvertToBoolUnchecked(dithering), ConvertToBoolUnchecked(interlacing), m_texture_replacements);
VkShaderModule shader = g_vulkan_shader_cache->GetFragmentShader(fs);
if (shader == VK_NULL_HANDLE)
@ -990,7 +1115,8 @@ bool GPU_HW_Vulkan::CompilePipelines()
VK_COMPARE_OP_ALWAYS, VK_COMPARE_OP_GREATER_OR_EQUAL, VK_COMPARE_OP_LESS_OR_EQUAL};
const bool textured = (static_cast<GPUTextureMode>(texture_mode) != GPUTextureMode::Disabled);
gpbuilder.SetPipelineLayout(m_batch_pipeline_layout);
gpbuilder.SetPipelineLayout(m_texture_replacements ? m_texture_replacement_batch_pipeline_layout :
m_batch_pipeline_layout);
gpbuilder.SetRenderPass(m_vram_render_pass, 0);
gpbuilder.AddVertexBuffer(0, sizeof(BatchVertex), VK_VERTEX_INPUT_RATE_VERTEX);
@ -1618,7 +1744,7 @@ void GPU_HW_Vulkan::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
const Common::Rectangle<u32> bounds = GetVRAMTransferBounds(x, y, width, height);
GPU_HW::UpdateVRAM(bounds.left, bounds.top, bounds.GetWidth(), bounds.GetHeight(), data, set_mask, check_mask);
if (!check_mask)
if (!check_mask && !IsFullVRAMUpload(x, y, width, height))
{
const TextureReplacementTexture* rtex = g_texture_replacements.GetVRAMWriteReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
@ -1626,6 +1752,9 @@ void GPU_HW_Vulkan::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
{
return;
}
if (m_texture_replacements)
g_texture_replacements.UploadReplacementTextures(x, y, width, height, data);
}
const u32 data_size = width * height * sizeof(u16);

View File

@ -24,6 +24,10 @@ public:
void RestoreGraphicsAPIState() override;
void UpdateSettings() override;
void UploadTextureReplacement(u32 page_index, u32 page_x, u32 page_y, u32 data_width, u32 data_height,
const void* data, u32 data_stride) override;
void InvalidateTextureReplacements() override;
protected:
void ClearDisplay() override;
void UpdateDisplay() override;
@ -39,6 +43,7 @@ protected:
void UnmapBatchVertexPointer(u32 used_vertices) override;
void UploadUniformBuffer(const void* data, u32 data_size) override;
void DrawBatchVertices(BatchRenderMode render_mode, u32 base_vertex, u32 num_vertices) override;
bool SetupTextureReplacementTexture() override;
private:
enum : u32
@ -59,6 +64,7 @@ private:
bool CreateSamplers();
bool CreateFramebuffer();
bool CreateBatchDesciptorSet(bool texture_replacements);
void ClearFramebuffer();
void DestroyFramebuffer();
@ -84,10 +90,12 @@ private:
VkRenderPass m_vram_readback_render_pass = VK_NULL_HANDLE;
VkDescriptorSetLayout m_batch_descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout m_texture_replacement_batch_descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout m_single_sampler_descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout m_vram_write_descriptor_set_layout = VK_NULL_HANDLE;
VkPipelineLayout m_batch_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_texture_replacement_batch_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_no_samplers_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_single_sampler_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_vram_write_pipeline_layout = VK_NULL_HANDLE;
@ -139,6 +147,7 @@ private:
// texture replacements
Vulkan::Texture m_vram_write_replacement_texture;
Vulkan::Texture m_texture_replacement_texture;
// downsampling
Vulkan::Texture m_downsample_texture;

1240
src/core/host_interface.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -369,14 +369,37 @@ void Settings::Load(SettingsInterface& si)
texture_replacements.enable_vram_write_replacements =
si.GetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false);
texture_replacements.enable_texture_replacements =
si.GetBoolValue("TextureReplacements", "EnableTextureReplacements", false);
texture_replacements.preload_textures = si.GetBoolValue("TextureReplacements", "PreloadTextures", false);
texture_replacements.replacement_texture_scale =
static_cast<u32>(si.GetIntValue("TextureReplacements", "TextureReplacementScale", 0));
if (texture_replacements.replacement_texture_scale == 0)
texture_replacements.replacement_texture_scale = gpu_resolution_scale;
texture_replacements.dump_vram_writes = si.GetBoolValue("TextureReplacements", "DumpVRAMWrites", false);
texture_replacements.dump_vram_write_force_alpha_channel =
si.GetBoolValue("TextureReplacements", "DumpVRAMWriteForceAlphaChannel", true);
texture_replacements.dump_vram_write_width_threshold =
si.GetIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold", 128);
si.GetIntValue("TextureReplacements", "DumpVRAMWriteWidthThreshold", DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD);
texture_replacements.dump_vram_write_height_threshold =
si.GetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
si.GetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD);
texture_replacements.dump_textures_by_vram_write =
si.GetBoolValue("TextureReplacements", "DumpTexturesByVRAMWrite", false);
texture_replacements.dump_textures_by_palette =
si.GetBoolValue("TextureReplacements", "DumpTexturesByPalette", false);
texture_replacements.dump_textures_force_alpha_channel =
si.GetBoolValue("TextureReplacements", "DumpTexturesForceAlphaChannel", false);
texture_replacements.dump_textures_max_merge_width =
si.GetIntValue("TextureReplacements", "DumpTexturesMaxMergeWidth", DEFAULT_TEXTURE_DUMP_MAX_MERGE_WIDTH);
texture_replacements.dump_textures_max_merge_height =
si.GetIntValue("TextureReplacements", "DumpTexturesMaxMergeHeight", DEFAULT_TEXTURE_DUMP_MAX_MERGE_HEIGHT);
texture_replacements.dump_textures_max_mergee_width =
si.GetIntValue("TextureReplacements", "DumpTexturesMaxMergeeWidth", DEFAULT_TEXTURE_DUMP_MAX_MERGEE_WIDTH);
texture_replacements.dump_textures_max_mergee_height =
si.GetIntValue("TextureReplacements", "DumpTexturesMaxMergeeHeight", DEFAULT_TEXTURE_DUMP_MAX_MERGEE_HEIGHT);
texture_replacements.UpdateTextureDumpingEnabled();
}
void Settings::Save(SettingsInterface& si) const
@ -552,7 +575,10 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("TextureReplacements", "EnableVRAMWriteReplacements",
texture_replacements.enable_vram_write_replacements);
si.SetBoolValue("TextureReplacements", "EnableTextureReplacements", texture_replacements.enable_texture_replacements);
si.SetBoolValue("TextureReplacements", "PreloadTextures", texture_replacements.preload_textures);
si.SetIntValue("TextureReplacements", "TextureReplacementScale", texture_replacements.replacement_texture_scale);
si.SetBoolValue("TextureReplacements", "DumpVRAMWrites", texture_replacements.dump_vram_writes);
si.SetBoolValue("TextureReplacements", "DumpVRAMWriteForceAlphaChannel",
texture_replacements.dump_vram_write_force_alpha_channel);
@ -560,6 +586,19 @@ void Settings::Save(SettingsInterface& si) const
texture_replacements.dump_vram_write_width_threshold);
si.SetIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold",
texture_replacements.dump_vram_write_height_threshold);
si.SetBoolValue("TextureReplacements", "DumpTexturesByVRAMWrite", texture_replacements.dump_textures_by_vram_write);
si.SetBoolValue("TextureReplacements", "DumpTexturesByPalette", texture_replacements.dump_textures_by_palette);
si.SetBoolValue("TextureReplacements", "DumpTexturesForceAlphaChannel",
texture_replacements.dump_textures_force_alpha_channel);
si.SetIntValue("TextureReplacements", "DumpTexturesMaxMergeWidth",
texture_replacements.dump_textures_max_merge_width);
si.SetIntValue("TextureReplacements", "DumpTexturesMaxMergeHeight",
texture_replacements.dump_textures_max_merge_height);
si.SetIntValue("TextureReplacements", "DumpTexturesMaxMergeeWidth",
texture_replacements.dump_textures_max_mergee_width);
si.SetIntValue("TextureReplacements", "DumpTexturesMaxMergeeHeight",
texture_replacements.dump_textures_max_mergee_height);
}
void Settings::FixIncompatibleSettings(bool display_osd_messages)
@ -632,6 +671,13 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
}
#endif
if (g_settings.IsUsingSoftwareRenderer() && g_settings.texture_replacements.IsAnyDumpingEnabled())
{
Log_WarningPrintf("Disabling texture dumping because of software renderer");
g_settings.texture_replacements.dump_vram_writes = false;
g_settings.texture_replacements.dump_textures = false;
}
// if challenge mode is enabled, disable things like rewind since they use save states
if (Achievements::ChallengeModeActive())
{

View File

@ -201,18 +201,35 @@ struct Settings
struct TextureReplacementSettings
{
bool enable_vram_write_replacements = false;
bool enable_texture_replacements = false;
bool preload_textures = false;
bool dump_vram_writes = false;
bool dump_vram_write_force_alpha_channel = true;
u32 dump_vram_write_width_threshold = 128;
u32 dump_vram_write_height_threshold = 128;
ALWAYS_INLINE bool AnyReplacementsEnabled() const { return enable_vram_write_replacements; }
bool dump_textures = false;
bool dump_textures_by_vram_write = false;
bool dump_textures_by_palette = false;
bool dump_textures_force_alpha_channel = false;
ALWAYS_INLINE bool ShouldDumpVRAMWrite(u32 width, u32 height)
u32 replacement_texture_scale = 0;
u32 dump_vram_write_width_threshold = DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD;
u32 dump_vram_write_height_threshold = DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD;
u32 dump_textures_max_merge_width = DEFAULT_TEXTURE_DUMP_MAX_MERGE_WIDTH;
u32 dump_textures_max_merge_height = DEFAULT_TEXTURE_DUMP_MAX_MERGE_HEIGHT;
u32 dump_textures_max_mergee_width = DEFAULT_TEXTURE_DUMP_MAX_MERGEE_WIDTH;
u32 dump_textures_max_mergee_height = DEFAULT_TEXTURE_DUMP_MAX_MERGEE_HEIGHT;
ALWAYS_INLINE bool AnyReplacementsEnabled() const
{
return dump_vram_writes && width >= dump_vram_write_width_threshold && height >= dump_vram_write_height_threshold;
return enable_vram_write_replacements || enable_texture_replacements;
}
ALWAYS_INLINE bool IsAnyDumpingEnabled() const { return dump_vram_writes || dump_textures; }
ALWAYS_INLINE void UpdateTextureDumpingEnabled()
{
dump_textures = dump_textures_by_vram_write || dump_textures_by_palette;
}
} texture_replacements;
@ -310,6 +327,10 @@ struct Settings
DEFAULT_GPU_MAX_RUN_AHEAD = 128,
DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD = 128,
DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD = 128,
DEFAULT_TEXTURE_DUMP_MAX_MERGE_WIDTH = 256,
DEFAULT_TEXTURE_DUMP_MAX_MERGE_HEIGHT = 256,
DEFAULT_TEXTURE_DUMP_MAX_MERGEE_WIDTH = 256,
DEFAULT_TEXTURE_DUMP_MAX_MERGEE_HEIGHT = 1,
};
void Load(SettingsInterface& si);

View File

@ -207,8 +207,10 @@ void ShaderGen::WriteHeader(std::stringstream& ss)
ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) textureOffset(name, coords, offset)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) textureLod(name, coords, level)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) textureLod(name, coords, level, offset)\n";
ss << "#define SAMPLE_TEXTURE_ARRAY(name, coords, layer) texture(name, float3((coords), (layer)))\n";
ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n";
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) texelFetch(name, coords, int(sample))\n";
ss << "#define LOAD_TEXTURE_ARRAY(name, coords, layer, mip) texelFetch(name, int3((coords), (layer)), (mip))\n";
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n";
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
ss << "#define BEGIN_ARRAY(type, size) type[size](\n";
@ -253,8 +255,10 @@ void ShaderGen::WriteHeader(std::stringstream& ss)
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) name.SampleLevel(name##_ss, coords, level)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) name.SampleLevel(name##_ss, coords, level, "
"offset)\n";
ss << "#define SAMPLE_TEXTURE_ARRAY(name, coords, layer) name.Sample(name##_ss, float3((coords), (layer)))\n";
ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n";
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) name.Load(coords, sample)\n";
ss << "#define LOAD_TEXTURE_ARRAY(name, coords, layer, mip) name.Load(int4((coords), (layer), (mip)))\n";
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n";
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
ss << "#define BEGIN_ARRAY(type, size) {\n";
@ -315,6 +319,24 @@ void ShaderGen::DeclareTexture(std::stringstream& ss, const char* name, u32 inde
}
}
void ShaderGen::DeclareTextureArray(std::stringstream& ss, const char* name, u32 index)
{
if (m_glsl)
{
if (IsVulkan())
ss << "layout(set = 0, binding = " << (index + 1u) << ") ";
else if (m_use_glsl_binding_layout)
ss << "layout(binding = " << index << ") ";
ss << "uniform sampler2DArray " << name << ";\n";
}
else
{
ss << "Texture2DArray " << name << " : register(t" << index << ");\n";
ss << "SamplerState " << name << "_ss : register(s" << index << ");\n";
}
}
void ShaderGen::DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned)
{
if (m_glsl)

View File

@ -34,6 +34,7 @@ protected:
void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
bool push_constant_on_vulkan);
void DeclareTexture(std::stringstream& ss, const char* name, u32 index, bool multisampled = false);
void DeclareTextureArray(std::stringstream& ss, const char* name, u32 index);
void DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned);
void DeclareVertexEntryPoint(std::stringstream& ss, const std::initializer_list<const char*>& attributes,
u32 num_color_outputs, u32 num_texcoord_outputs,

View File

@ -36,6 +36,7 @@
#include "save_state_version.h"
#include "sio.h"
#include "spu.h"
#include "texture_dumper.h"
#include "texture_replacements.h"
#include "timers.h"
#include "util/audio_stream.h"
@ -1372,6 +1373,9 @@ bool System::Initialize(bool force_software_renderer)
UpdateThrottlePeriod();
UpdateMemorySaveStateSettings();
g_texture_replacements.Reload();
return true;
}
@ -1386,6 +1390,7 @@ void System::DestroySystem()
ClearMemorySaveStates();
TextureDumper::Shutdown();
g_texture_replacements.Shutdown();
g_sio.Shutdown();
@ -1660,6 +1665,15 @@ bool System::DoState(StateWrapper& sw, HostDisplayTexture** host_texture, bool u
}
}
// TODO: What do we want to do with runahead here?
if (sw.IsReading())
{
g_texture_replacements.OnSystemReset();
if (g_settings.texture_replacements.IsAnyDumpingEnabled())
TextureDumper::ClearState();
}
return !sw.HasError();
}
@ -1688,13 +1702,19 @@ void System::InternalReset()
s_frame_number = 1;
s_internal_frame_number = 0;
TimingEvents::Reset();
ResetPerformanceCounters();
g_texture_replacements.OnSystemReset();
if (g_settings.texture_replacements.IsAnyDumpingEnabled())
TextureDumper::ClearState();
#ifdef WITH_CHEEVOS
Achievements::ResetRuntime();
#endif
g_gpu->ResetGraphicsAPIState();
ResetPerformanceCounters();
}
std::string System::GetMediaPathFromSaveState(const char* path)
@ -2910,7 +2930,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
}
}
g_texture_replacements.SetGameID(s_running_game_code);
g_texture_replacements.Reload();
s_cheat_list.reset();
if (g_settings.auto_load_cheats && !Achievements::ChallengeModeActive())
@ -3165,7 +3185,12 @@ 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.enable_texture_replacements !=
old_settings.texture_replacements.enable_texture_replacements ||
(g_settings.texture_replacements.enable_texture_replacements &&
g_settings.texture_replacements.replacement_texture_scale !=
old_settings.texture_replacements.replacement_texture_scale))
{
g_gpu->UpdateSettings();
Host::InvalidateDisplay();
@ -3223,11 +3248,19 @@ 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)
{
g_texture_replacements.Reload();
}
if (g_settings.texture_replacements.IsAnyDumpingEnabled() !=
old_settings.texture_replacements.IsAnyDumpingEnabled())
{
TextureDumper::ClearState();
}
g_dma.SetMaxSliceTicks(g_settings.dma_max_slice_ticks);
g_dma.SetHaltTicks(g_settings.dma_halt_ticks);

1064
src/core/texture_dumper.cpp Normal file

File diff suppressed because it is too large Load Diff

21
src/core/texture_dumper.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "common/hash_combine.h"
#include "common/image.h"
#include "common/rectangle.h"
#include "gpu_types.h"
#include "types.h"
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
namespace TextureDumper {
std::string GetDumpDirectory();
void ClearState();
void Shutdown();
void AddClear(u32 x, u32 y, u32 width, u32 height);
void AddVRAMWrite(u32 x, u32 y, u32 width, u32 height, const void* pixels);
void AddDraw(u16 draw_mode, u16 palette, u32 min_uv_x, u32 min_uv_y, u32 max_uv_x, u32 max_uv_y, bool transparent);
} // namespace TextureDumper

View File

@ -6,8 +6,10 @@
#include "common/string_util.h"
#include "common/timer.h"
#include "fmt/format.h"
#include "gpu_hw.h"
#include "host.h"
#include "settings.h"
#include "system.h"
#include "xxhash.h"
#if defined(CPU_X86) || defined(CPU_X64)
#include "xxh_x86dispatch.h"
@ -17,25 +19,24 @@ Log_SetChannel(TextureReplacements);
TextureReplacements g_texture_replacements;
static constexpr u32 VRAMRGBA5551ToRGBA8888(u16 color)
static GPU_HW* GetHWGPU()
{
u8 r = Truncate8(color & 31);
u8 g = Truncate8((color >> 5) & 31);
u8 b = Truncate8((color >> 10) & 31);
u8 a = Truncate8((color >> 15) & 1);
return static_cast<GPU_HW*>(g_gpu.get());
}
// 00012345 -> 1234545
b = (b << 3) | (b & 0b111);
g = (g << 3) | (g & 0b111);
r = (r << 3) | (r & 0b111);
a = a ? 255 : 0;
u32 TextureReplacements::GetScaledReplacementTextureWidth()
{
return TEXTURE_REPLACEMENT_PAGE_WIDTH * g_settings.texture_replacements.replacement_texture_scale;
}
return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24);
u32 TextureReplacements::GetScaledReplacementTextureHeight()
{
return TEXTURE_REPLACEMENT_PAGE_HEIGHT * g_settings.texture_replacements.replacement_texture_scale;
}
std::string TextureReplacementHash::ToString() const
{
return StringUtil::StdStringFromFormat("%" PRIx64 "%" PRIx64, high, low);
return StringUtil::StdStringFromFormat("%016" PRIx64 "%016" PRIx64, high, low);
}
bool TextureReplacementHash::ParseString(const std::string_view& sv)
@ -57,13 +58,25 @@ TextureReplacements::TextureReplacements() = default;
TextureReplacements::~TextureReplacements() = default;
void TextureReplacements::SetGameID(std::string game_id)
void TextureReplacements::InvalidateReplacementTextures()
{
if (m_game_id == game_id)
return;
Assert(g_settings.texture_replacements.enable_texture_replacements);
m_game_id = game_id;
Reload();
// TODO: Clear cached textures.
if (GetHWGPU()->IsTextureReplacementEnabled())
GetHWGPU()->InvalidateTextureReplacements();
}
void TextureReplacements::ReuploadReplacementTextures()
{
Assert(g_settings.texture_replacements.enable_texture_replacements);
}
void TextureReplacements::OnSystemReset()
{
if (g_settings.texture_replacements.enable_texture_replacements)
InvalidateReplacementTextures();
}
const TextureReplacementTexture* TextureReplacements::GetVRAMWriteReplacement(u32 width, u32 height, const void* pixels)
@ -77,84 +90,213 @@ const TextureReplacementTexture* TextureReplacements::GetVRAMWriteReplacement(u3
return LoadTexture(it->second);
}
void TextureReplacements::DumpVRAMWrite(u32 width, u32 height, const void* pixels)
static constexpr std::array<u32, 4> s_texture_mode_shifts = {{2, 1, 0}};
static constexpr std::array<u32, 4> s_texture_mode_revshifts = {{0, 1, 2}};
static constexpr std::array<u32, 4> s_texture_mode_masks = {{3, 1, 0}};
void TextureReplacements::TransformTextureCoordinates(GPUTextureMode mode, u32 vram_x, u32 vram_y, u32 vram_width,
u32 vram_height, u32* out_vram_width, u32* out_vram_height,
u32* page_index, u32* page_offset_x, u32* page_offset_y,
u32* page_width, u32* page_height)
{
std::string filename = GetVRAMWriteDumpFilename(width, height, pixels);
if (filename.empty())
const u32 shift = s_texture_mode_shifts[static_cast<u32>(mode)];
const u32 revshift = s_texture_mode_revshifts[static_cast<u32>(mode)];
const u32 page_x = vram_x / TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH;
const u32 page_y = vram_y / TEXTURE_REPLACEMENT_PAGE_VRAM_HEIGHT;
*page_index = GetTextureReplacementPageIndex(page_x, page_y);
*page_offset_x = (vram_x % TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH) << shift;
*page_offset_y = (vram_y % TEXTURE_REPLACEMENT_PAGE_VRAM_HEIGHT);
*page_width = std::min<u32>(vram_width << shift, (TEXTURE_REPLACEMENT_PAGE_WIDTH >> revshift) - *page_offset_x);
*page_height = std::min<u32>(vram_height, TEXTURE_REPLACEMENT_PAGE_HEIGHT - *page_offset_y);
*out_vram_width = *page_width >> shift;
*out_vram_height = *page_height;
}
void TextureReplacements::UntransformTextureCoordinates(GPUTextureMode mode, u32 texture_width, u32 texture_height,
u32 vram_x, u32 vram_y, u32* out_vram_width,
u32* out_vram_height, u32* page_index, u32* page_offset_x,
u32* page_offset_y, u32* page_width, u32* page_height)
{
const u32 shift = s_texture_mode_shifts[static_cast<u32>(mode)];
const u32 revshift = s_texture_mode_revshifts[static_cast<u32>(mode)];
const u32 page_x = vram_x / TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH;
const u32 page_y = vram_y / TEXTURE_REPLACEMENT_PAGE_VRAM_HEIGHT;
*page_index = GetTextureReplacementPageIndex(page_x, page_y);
*page_offset_x = (vram_x % TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH) << shift;
*page_offset_y = (vram_y % TEXTURE_REPLACEMENT_PAGE_VRAM_HEIGHT);
*page_width = std::min<u32>(texture_width, (TEXTURE_REPLACEMENT_PAGE_WIDTH >> revshift) - *page_offset_x);
*page_height = std::min<u32>(texture_height, TEXTURE_REPLACEMENT_PAGE_HEIGHT - *page_offset_y);
*out_vram_width = (*page_width + s_texture_mode_masks[static_cast<u32>(mode)]) >> shift;
*out_vram_height = *page_height;
}
void TextureReplacements::InvalidateReplacementTexture(GPUTextureMode mode, u32 vram_x, u32 vram_y, u32 width,
u32 height)
{
const u32 scale = g_settings.texture_replacements.replacement_texture_scale;
while (height > 0)
{
u32 current_x = vram_x;
u32 remaining_width = width;
u32 consume_height = height;
while (remaining_width > 0)
{
u32 consume_width, page_index, page_offset_x, page_offset_y, page_width, page_height;
TransformTextureCoordinates(mode, current_x, vram_y, remaining_width, height, &consume_width, &consume_height,
&page_index, &page_offset_x, &page_offset_y, &page_width, &page_height);
const u32 dummy_data_size = (page_width * scale) * (page_height * scale);
if (m_texture_replacement_invalidate_buffer.size() < dummy_data_size)
m_texture_replacement_invalidate_buffer.resize(dummy_data_size);
GetHWGPU()->UploadTextureReplacement(page_index, page_offset_x * scale, page_offset_y * scale, page_width * scale,
page_height * scale, m_texture_replacement_invalidate_buffer.data(),
page_width * scale * sizeof(u32));
current_x += consume_width;
remaining_width -= consume_width;
}
vram_y += consume_height;
height -= consume_height;
}
}
void TextureReplacements::UploadReplacementTexture(const TextureReplacementTexture* texture,
const ReplacementEntry& entry, u32 vram_x, u32 vram_y)
{
const u32 scale = g_settings.texture_replacements.replacement_texture_scale;
const u32 unscaled_width = (entry.width << s_texture_mode_shifts[static_cast<u32>(entry.mode)]);
const u32 scaled_width = unscaled_width * scale;
const u32 scaled_height = entry.height * scale;
if (texture->GetWidth() != scaled_width || texture->GetHeight() != scaled_height)
{
Log_VerbosePrintf("Resizing replacement texture from %ux%u to %ux%u", texture->GetWidth(), texture->GetHeight(),
scaled_width, scaled_height);
TextureReplacementTexture resized_texture;
resized_texture.ResizeFrom(texture, scaled_width, scaled_height);
UploadReplacementTexture(&resized_texture, entry, vram_x, vram_y);
return;
Common::RGBA8Image image;
image.SetSize(width, height);
const u16* src_pixels = reinterpret_cast<const u16*>(pixels);
for (u32 y = 0; y < height; y++)
{
for (u32 x = 0; x < width; x++)
{
image.SetPixel(x, y, VRAMRGBA5551ToRGBA8888(*src_pixels));
src_pixels++;
}
}
if (g_settings.texture_replacements.dump_vram_write_force_alpha_channel)
// this is the tricky part
u32 current_y = vram_y + entry.offset_y;
u32 remaining_height = entry.height;
u32 texture_y = 0;
while (remaining_height > 0)
{
for (u32 y = 0; y < height; y++)
u32 current_x = vram_x + entry.offset_x;
u32 remaining_width = unscaled_width;
u32 consume_height = remaining_height;
u32 texture_x = 0;
while (remaining_width > 0)
{
for (u32 x = 0; x < width; x++)
image.SetPixel(x, y, image.GetPixel(x, y) | 0xFF000000u);
u32 consume_width, page_index, page_offset_x, page_offset_y, page_width, page_height;
UntransformTextureCoordinates(entry.mode, remaining_width, remaining_height, current_x, current_y, &consume_width,
&consume_height, &page_index, &page_offset_x, &page_offset_y, &page_width,
&page_height);
const u32 upload_width = std::min(page_width, remaining_width);
const u32 upload_height = std::min(page_height, remaining_height);
const u32 scaled_upload_width = upload_width * scale;
const u32 scaled_upload_height = upload_height * scale;
const u32 scaled_page_offset_x = page_offset_x * scale;
const u32 scaled_page_offset_y = page_offset_y * scale;
Log_InfoPrintf("Uploading %ux%u to replacement page %u @ %u,%u", scaled_upload_width, scaled_upload_height,
page_index, scaled_page_offset_x, scaled_page_offset_y);
GetHWGPU()->UploadTextureReplacement(page_index, scaled_page_offset_x, scaled_page_offset_y, scaled_upload_width,
scaled_upload_height, texture->GetRowPixels(texture_y) + texture_x,
texture->GetPitch());
texture_x += scaled_upload_width;
remaining_width -= upload_width;
current_x += consume_width;
consume_height = upload_height;
}
remaining_height -= consume_height;
texture_y += consume_height;
vram_y += consume_height;
}
}
void TextureReplacements::UploadReplacementTextures(u32 vram_x, u32 vram_y, u32 width, u32 height, const void* pixels)
{
const TextureReplacementHash hash = GetVRAMWriteHash(width, height, pixels);
const auto [lower, upper] = m_texture_replacements.equal_range(hash);
if (lower == upper)
{
InvalidateReplacementTexture(GPUTextureMode::Palette4Bit, vram_x, vram_y, width, height);
return;
}
Log_InfoPrintf("Dumping %ux%u VRAM write to '%s'", width, height, filename.c_str());
if (!image.SaveToFile(filename.c_str()))
Log_ErrorPrintf("Failed to dump %ux%u VRAM write to '%s'", width, height, filename.c_str());
for (auto iter = lower; iter != upper; ++iter)
{
const ReplacementEntry& entry = iter->second;
const TextureReplacementTexture* texture = LoadTexture(entry.filename);
if (!texture)
{
InvalidateReplacementTexture(entry.mode, vram_x + entry.offset_x, vram_y + entry.offset_y, entry.width,
entry.height);
continue;
}
UploadReplacementTexture(texture, entry, vram_x, vram_y);
}
}
void TextureReplacements::Shutdown()
{
m_texture_cache.clear();
m_vram_write_replacements.clear();
m_game_id.clear();
m_texture_replacements.clear();
decltype(m_texture_replacement_invalidate_buffer)().swap(m_texture_replacement_invalidate_buffer);
}
std::string TextureReplacements::GetSourceDirectory() const
{
return Path::Combine(EmuFolders::Textures, m_game_id);
const std::string& code = System::GetRunningCode();
if (code.empty())
return EmuFolders::Textures;
else
return Path::Combine(EmuFolders::Textures, code);
}
std::string TextureReplacements::GetDumpDirectory() const
{
return Path::Combine(EmuFolders::Dumps, Path::Combine("textures", m_game_id));
}
TextureReplacementHash TextureReplacements::GetVRAMWriteHash(u32 width, u32 height, const void* pixels) const
TextureReplacementHash TextureReplacements::GetVRAMWriteHash(u32 width, u32 height, const void* pixels)
{
XXH128_hash_t hash = XXH3_128bits(pixels, width * height * sizeof(u16));
return {hash.low64, hash.high64};
}
std::string TextureReplacements::GetVRAMWriteDumpFilename(u32 width, u32 height, const void* pixels) const
{
if (m_game_id.empty())
return {};
const TextureReplacementHash hash = GetVRAMWriteHash(width, height, pixels);
const std::string dump_directory(GetDumpDirectory());
std::string filename(Path::Combine(dump_directory, fmt::format("vram-write-{}.png", hash.ToString())));
if (FileSystem::FileExists(filename.c_str()))
return {};
if (!FileSystem::EnsureDirectoryExists(dump_directory.c_str(), false))
return {};
return filename;
}
void TextureReplacements::Reload()
{
m_vram_write_replacements.clear();
m_texture_replacements.clear();
if (!g_gpu || !g_gpu->IsHardwareRenderer())
{
m_texture_cache.clear();
return;
}
if (g_settings.texture_replacements.AnyReplacementsEnabled())
FindTextures(GetSourceDirectory());
@ -163,6 +305,9 @@ void TextureReplacements::Reload()
PreloadTextures();
PurgeUnreferencedTexturesFromCache();
if (g_settings.texture_replacements.enable_texture_replacements)
ReuploadReplacementTextures();
}
void TextureReplacements::PurgeUnreferencedTexturesFromCache()
@ -177,13 +322,24 @@ void TextureReplacements::PurgeUnreferencedTexturesFromCache()
old_map.erase(it2);
}
}
for (const auto& it : m_texture_replacements)
{
auto it2 = old_map.find(it.second.filename);
if (it2 != old_map.end())
{
m_texture_cache[it.second.filename] = std::move(it2->second);
old_map.erase(it2);
}
}
}
bool TextureReplacements::ParseReplacementFilename(const std::string& filename,
TextureReplacementHash* replacement_hash,
ReplacmentType* replacement_type)
ReplacmentType* replacement_type, GPUTextureMode* out_mode,
u16* out_offset_x, u16* out_offset_y, u16* out_width,
u16* out_height)
{
const char* extension = std::strrchr(filename.c_str(), '.');
const char* title = std::strrchr(filename.c_str(), '/');
#ifdef _WIN32
const char* title2 = std::strrchr(filename.c_str(), '\\');
@ -191,24 +347,72 @@ bool TextureReplacements::ParseReplacementFilename(const std::string& filename,
title = title2;
#endif
if (!title || !extension)
if (!title)
return false;
title++;
const char* hashpart;
const char* extension;
if (StringUtil::Strncasecmp(title, "vram-write-", 11) == 0)
{
hashpart = title + 11;
if (std::strlen(hashpart) < 32)
return false;
if (!replacement_hash->ParseString(std::string_view(hashpart, 32)))
return false;
*replacement_type = ReplacmentType::VRAMWrite;
*out_mode = GPUTextureMode::Direct16Bit;
*out_offset_x = 0;
*out_offset_y = 0;
*out_width = 0;
*out_height = 0;
extension = hashpart + 32;
}
else if (StringUtil::Strncasecmp(title, "texture-", 8) == 0)
{
// TODO: Make this much better...
hashpart = title + 8;
if (std::strlen(hashpart) < 42)
return false;
if (!replacement_hash->ParseString(std::string_view(hashpart, 32)))
return false;
const char* datapart = hashpart + 33;
unsigned file_mode, file_offset_x, file_offset_y, file_width, file_height;
if (std::sscanf(datapart, "%u-%u-%u-%u-%u", &file_mode, &file_offset_x, &file_offset_y, &file_width,
&file_height) != 5)
{
return false;
}
if (file_mode > static_cast<unsigned>(GPUTextureMode::Reserved_Direct16Bit) || file_offset_x >= VRAM_WIDTH ||
(file_offset_x + file_width) > VRAM_WIDTH || file_offset_y >= VRAM_HEIGHT ||
(file_offset_y + file_height) > VRAM_HEIGHT)
{
return false;
}
*replacement_type = ReplacmentType::Texture;
*out_mode = static_cast<GPUTextureMode>(file_mode);
*out_offset_x = static_cast<u16>(file_offset_x);
*out_offset_y = static_cast<u16>(file_offset_y);
*out_width = static_cast<u16>(file_width);
*out_height = static_cast<u16>(file_height);
extension = std::strchr(datapart, '.');
}
else
{
return false;
}
if (!replacement_hash->ParseString(std::string_view(hashpart, static_cast<size_t>(extension - hashpart))))
if (!extension || *extension == '\0')
return false;
extension++;
@ -228,8 +432,12 @@ bool TextureReplacements::ParseReplacementFilename(const std::string& filename,
void TextureReplacements::FindTextures(const std::string& dir)
{
const bool recursive = !System::GetRunningCode().empty();
FileSystem::FindResultsArray files;
FileSystem::FindFiles(dir.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE, &files);
FileSystem::FindFiles(dir.c_str(), "*",
recursive ? (FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE) : (FILESYSTEM_FIND_FILES),
&files);
for (FILESYSTEM_FIND_DATA& fd : files)
{
@ -238,13 +446,22 @@ void TextureReplacements::FindTextures(const std::string& dir)
TextureReplacementHash hash;
ReplacmentType type;
if (!ParseReplacementFilename(fd.FileName, &hash, &type))
GPUTextureMode texture_mode;
u16 texture_offset_x, texture_offset_y;
u16 texture_width, texture_height;
if (!ParseReplacementFilename(fd.FileName, &hash, &type, &texture_mode, &texture_offset_x, &texture_offset_y,
&texture_width, &texture_height))
{
continue;
}
switch (type)
{
case ReplacmentType::VRAMWrite:
{
if (!g_settings.texture_replacements.enable_vram_write_replacements)
continue;
auto it = m_vram_write_replacements.find(hash);
if (it != m_vram_write_replacements.end())
{
@ -255,10 +472,30 @@ void TextureReplacements::FindTextures(const std::string& dir)
m_vram_write_replacements.emplace(hash, std::move(fd.FileName));
}
break;
case ReplacmentType::Texture:
{
if (!g_settings.texture_replacements.enable_texture_replacements)
continue;
ReplacementEntry entry;
entry.filename = std::move(fd.FileName);
entry.mode = texture_mode;
entry.offset_x = texture_offset_x;
entry.offset_y = texture_offset_y;
entry.width = texture_width;
entry.height = texture_height;
m_texture_replacements.emplace(hash, std::move(entry));
}
break;
}
}
Log_InfoPrintf("Found %zu replacement VRAM writes for '%s'", m_vram_write_replacements.size(), m_game_id.c_str());
if (g_settings.texture_replacements.enable_vram_write_replacements)
Log_InfoPrintf("Found %zu replacement VRAM writes", m_vram_write_replacements.size());
if (g_settings.texture_replacements.enable_texture_replacements)
Log_InfoPrintf("Found %zu replacement textures", m_texture_replacements.size());
}
const TextureReplacementTexture* TextureReplacements::LoadTexture(const std::string& filename)

View File

@ -1,6 +1,7 @@
#pragma once
#include "common/hash_combine.h"
#include "common/image.h"
#include "gpu_types.h"
#include "types.h"
#include <string>
#include <tuple>
@ -40,19 +41,49 @@ class TextureReplacements
public:
enum class ReplacmentType
{
VRAMWrite
VRAMWrite,
Texture
};
enum : u32
{
TEXTURE_REPLACEMENT_PAGE_WIDTH = 256, // in page space
TEXTURE_REPLACEMENT_PAGE_HEIGHT = 256, // in page space
TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH = 64, // in vram space
TEXTURE_REPLACEMENT_PAGE_VRAM_HEIGHT = 256, // in vram space
TEXTURE_REPLACEMENT_X_PAGES = (VRAM_WIDTH / TEXTURE_REPLACEMENT_PAGE_VRAM_WIDTH),
TEXTURE_REPLACEMENT_Y_PAGES = (VRAM_HEIGHT / TEXTURE_REPLACEMENT_PAGE_HEIGHT),
TEXTURE_REPLACEMENT_PAGE_COUNT = (TEXTURE_REPLACEMENT_X_PAGES * TEXTURE_REPLACEMENT_Y_PAGES),
};
TextureReplacements();
~TextureReplacements();
const std::string GetGameID() const { return m_game_id; }
void SetGameID(std::string game_id);
static constexpr u32 GetTextureReplacementXPage(u32 vram_x) { return (vram_x * 4) / TEXTURE_REPLACEMENT_PAGE_WIDTH; }
static constexpr u32 GetTextureReplacementXPageOffset(u32 vram_x)
{
return (vram_x * 4) % TEXTURE_REPLACEMENT_PAGE_WIDTH;
}
static constexpr u32 GetTextureReplacementYPage(u32 vram_y) { return vram_y / TEXTURE_REPLACEMENT_PAGE_HEIGHT; }
static constexpr u32 GetTextureReplacementYPageOffset(u32 vram_y) { return vram_y % TEXTURE_REPLACEMENT_PAGE_HEIGHT; }
static constexpr u32 GetTextureReplacementPageIndex(u32 page_x, u32 page_y)
{
return (page_y * TEXTURE_REPLACEMENT_X_PAGES) + page_x;
}
u32 GetScaledReplacementTextureWidth();
u32 GetScaledReplacementTextureHeight();
void ReuploadReplacementTextures();
void OnSystemReset();
void Reload();
static TextureReplacementHash GetVRAMWriteHash(u32 width, u32 height, const void* pixels);
const TextureReplacementTexture* GetVRAMWriteReplacement(u32 width, u32 height, const void* pixels);
void DumpVRAMWrite(u32 width, u32 height, const void* pixels);
void UploadReplacementTextures(u32 vram_x, u32 vram_y, u32 width, u32 height, const void* pixels);
void Shutdown();
@ -62,29 +93,50 @@ private:
size_t operator()(const TextureReplacementHash& hash);
};
struct ReplacementEntry
{
std::string filename;
u16 offset_x, offset_y;
u16 width, height;
GPUTextureMode mode;
};
using VRAMWriteReplacementMap = std::unordered_map<TextureReplacementHash, std::string>;
using TextureReplacementMap = std::unordered_multimap<TextureReplacementHash, ReplacementEntry>;
using TextureCache = std::unordered_map<std::string, TextureReplacementTexture>;
static bool ParseReplacementFilename(const std::string& filename, TextureReplacementHash* replacement_hash,
ReplacmentType* replacement_type);
ReplacmentType* replacement_type, GPUTextureMode* out_mode, u16* out_offset_x,
u16* out_offset_y, u16* out_width, u16* out_height);
std::string GetSourceDirectory() const;
std::string GetDumpDirectory() const;
TextureReplacementHash GetVRAMWriteHash(u32 width, u32 height, const void* pixels) const;
std::string GetVRAMWriteDumpFilename(u32 width, u32 height, const void* pixels) const;
void FindTextures(const std::string& dir);
const TextureReplacementTexture* LoadTexture(const std::string& filename);
void PreloadTextures();
void PurgeUnreferencedTexturesFromCache();
std::string m_game_id;
void TransformTextureCoordinates(GPUTextureMode mode, u32 vram_x, u32 vram_y, u32 vram_width, u32 vram_height,
u32* out_vram_width, u32* out_vram_height, u32* page_index, u32* page_offset_x,
u32* page_offset_y, u32* page_width, u32* page_height);
void UntransformTextureCoordinates(GPUTextureMode mode, u32 texture_width, u32 texture_height, u32 vram_x, u32 vram_y,
u32* out_vram_width, u32* out_vram_height, u32* page_index, u32* page_offset_x,
u32* page_offset_y, u32* page_width, u32* page_height);
void InvalidateReplacementTextures();
void InvalidateReplacementTexture(GPUTextureMode mode, u32 vram_x, u32 vram_y, u32 width, u32 height);
void UploadReplacementTexture(const TextureReplacementTexture* texture, const ReplacementEntry& entry, u32 vram_x,
u32 vram_y);
TextureCache m_texture_cache;
VRAMWriteReplacementMap m_vram_write_replacements;
TextureReplacementMap m_texture_replacements;
std::vector<u32> m_texture_replacement_invalidate_buffer;
};
extern TextureReplacements g_texture_replacements;

View File

@ -3801,7 +3801,7 @@ void FullscreenUI::DrawAdvancedSettingsPage()
MenuHeading("Texture Dumping");
DrawToggleSetting(bsi, "Dump Replaceable VRAM Writes", "Writes textures which can be replaced to the dump directory.",
DrawToggleSetting(bsi, "Dump Backgrounds", "Writes textures which can be replaced to the dump directory.",
"TextureReplacements", "DumpVRAMWrites", false);
DrawToggleSetting(bsi, "Set VRAM Write Dump Alpha Channel", "Clears the mask/transparency bit in VRAM write dumps.",
"TextureReplacements", "DumpVRAMWriteForceAlphaChannel", true);