GS/HW: Fix various issues with texture replacements

- Replacement textures now show in HC usage to give a clearer picture
   of VRAM usage.
 - Fixed crashes when loading compressed and mipmapped DDS textures.
 - Fixed compressed mipmapped textures in Direct3D 12.
 - Fixed GPU crashes in D3D11/D3D12/Vulkan when compressed textures went
   down the last 1x1 mipmap level.
This commit is contained in:
Connor McLaughlin 2022-11-26 15:40:41 +10:00 committed by refractionpcsx2
parent bb2016889a
commit daebb5753a
11 changed files with 50 additions and 33 deletions

View File

@ -369,14 +369,14 @@ namespace Vulkan
} }
void Texture::UpdateFromBuffer(VkCommandBuffer cmdbuf, u32 level, u32 layer, u32 x, u32 y, u32 width, u32 height, void Texture::UpdateFromBuffer(VkCommandBuffer cmdbuf, u32 level, u32 layer, u32 x, u32 y, u32 width, u32 height,
u32 row_length, VkBuffer buffer, u32 buffer_offset) u32 buffer_height, u32 row_length, VkBuffer buffer, u32 buffer_offset)
{ {
const VkImageLayout old_layout = m_layout; const VkImageLayout old_layout = m_layout;
if (old_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) if (old_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
TransitionSubresourcesToLayout( TransitionSubresourcesToLayout(
cmdbuf, level, 1, layer, 1, old_layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); cmdbuf, level, 1, layer, 1, old_layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
const VkBufferImageCopy bic = {static_cast<VkDeviceSize>(buffer_offset), row_length, height, const VkBufferImageCopy bic = {static_cast<VkDeviceSize>(buffer_offset), row_length, buffer_height,
{VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1u}, {static_cast<int32_t>(x), static_cast<int32_t>(y), 0}, {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1u}, {static_cast<int32_t>(x), static_cast<int32_t>(y), 0},
{width, height, 1u}}; {width, height, 1u}};

View File

@ -72,7 +72,7 @@ namespace Vulkan
VkFramebuffer CreateFramebuffer(VkRenderPass render_pass); VkFramebuffer CreateFramebuffer(VkRenderPass render_pass);
void UpdateFromBuffer(VkCommandBuffer cmdbuf, u32 level, u32 layer, u32 x, u32 y, u32 width, u32 height, void UpdateFromBuffer(VkCommandBuffer cmdbuf, u32 level, u32 layer, u32 x, u32 y, u32 width, u32 height,
u32 row_length, VkBuffer buffer, u32 buffer_offset); u32 buffer_height, u32 row_length, VkBuffer buffer, u32 buffer_offset);
private: private:
u32 m_width = 0; u32 m_width = 0;

View File

@ -222,7 +222,7 @@ static bool UploadBufferToTexture(
StringUtil::StrideMemCpy(buf.GetCurrentHostPointer(), upload_stride, data, data_stride, row_size, height); StringUtil::StrideMemCpy(buf.GetCurrentHostPointer(), upload_stride, data, data_stride, row_size, height);
buf.CommitMemory(upload_size); buf.CommitMemory(upload_size);
texture->UpdateFromBuffer(cmdbuf, 0, 0, 0, 0, width, height, upload_stride / texel_size, buf.GetBuffer(), buf_offset); texture->UpdateFromBuffer(cmdbuf, 0, 0, 0, 0, width, height, height, upload_stride / texel_size, buf.GetBuffer(), buf_offset);
return true; return true;
} }

View File

@ -428,7 +428,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
std::memcpy(ai.pMappedData, pixels, upload_size); std::memcpy(ai.pMappedData, pixels, upload_size);
vmaFlushAllocation(g_vulkan_context->GetAllocator(), allocation, 0, upload_size); vmaFlushAllocation(g_vulkan_context->GetAllocator(), allocation, 0, upload_size);
bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
bd->FontTexture.UpdateFromBuffer(g_vulkan_context->GetCurrentInitCommandBuffer(), 0, 0, 0, 0, width, height, width, buffer, 0); bd->FontTexture.UpdateFromBuffer(g_vulkan_context->GetCurrentInitCommandBuffer(), 0, 0, 0, 0, width, height, height, width, buffer, 0);
bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy. // Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy.

View File

@ -18,6 +18,7 @@
#include "GSTexture11.h" #include "GSTexture11.h"
#include "GS/GSPng.h" #include "GS/GSPng.h"
#include "GS/GSPerfMon.h" #include "GS/GSPerfMon.h"
#include "common/Align.h"
GSTexture11::GSTexture11(wil::com_ptr_nothrow<ID3D11Texture2D> texture, const D3D11_TEXTURE2D_DESC& desc, GSTexture11::GSTexture11(wil::com_ptr_nothrow<ID3D11Texture2D> texture, const D3D11_TEXTURE2D_DESC& desc,
GSTexture::Type type, GSTexture::Format format) GSTexture::Type type, GSTexture::Format format)
@ -44,7 +45,9 @@ bool GSTexture11::Update(const GSVector4i& r, const void* data, int pitch, int l
g_perfmon.Put(GSPerfMon::TextureUploads, 1); g_perfmon.Put(GSPerfMon::TextureUploads, 1);
const D3D11_BOX box = {(UINT)r.left, (UINT)r.top, 0U, (UINT)r.right, (UINT)r.bottom, 1U}; const u32 bs = GetCompressedBlockSize();
const D3D11_BOX box = {Common::AlignDownPow2((u32)r.left, bs), Common::AlignDownPow2((u32)r.top, bs),
Common::AlignUpPow2((u32)r.right, bs), Common::AlignUpPow2((u32)r.bottom, bs), 1U};
const UINT subresource = layer; // MipSlice + (ArraySlice * MipLevels). const UINT subresource = layer; // MipSlice + (ArraySlice * MipLevels).
GSDevice11::GetInstance()->GetD3DContext()->UpdateSubresource(m_texture.get(), subresource, &box, data, pitch, 0); GSDevice11::GetInstance()->GetD3DContext()->UpdateSubresource(m_texture.get(), subresource, &box, data, pitch, 0);

View File

@ -48,13 +48,11 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, u32 width, u32 heigh
{ {
case Type::Texture: case Type::Texture:
{ {
// this is a little annoying. basically, to do mipmap generation, we need to be a render target. // This is a little annoying. basically, to do mipmap generation, we need to be a render target.
const D3D12_RESOURCE_FLAGS flags = (levels > 1) ? D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET : D3D12_RESOURCE_FLAG_NONE; // If it's a compressed texture, we won't be generating mips anyway, so this should be fine.
if (levels > 1 && d3d_format != DXGI_FORMAT_R8G8B8A8_UNORM) const D3D12_RESOURCE_FLAGS flags = (levels > 1 && !IsCompressedFormat(format)) ?
{ D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET :
Console.Warning("DX12: Refusing to create a %ux%u format %u mipmapped texture", width, height, format); D3D12_RESOURCE_FLAG_NONE;
return nullptr;
}
D3D12::Texture texture; D3D12::Texture texture;
if (!texture.Create(width, height, levels, d3d_format, srv_format, if (!texture.Create(width, height, levels, d3d_format, srv_format,
@ -184,10 +182,12 @@ bool GSTexture12::Update(const GSVector4i& r, const void* data, int pitch, int l
g_perfmon.Put(GSPerfMon::TextureUploads, 1); g_perfmon.Put(GSPerfMon::TextureUploads, 1);
const u32 width = r.width(); // Footprint and box must be block aligned for compressed textures.
const u32 height = r.height(); const u32 block_size = GetCompressedBlockSize();
const u32 width = Common::AlignUpPow2(r.width(), block_size);
const u32 height = Common::AlignUpPow2(r.height(), block_size);
const u32 upload_pitch = Common::AlignUpPow2<u32>(pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); const u32 upload_pitch = Common::AlignUpPow2<u32>(pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 required_size = CalcUploadSize(height, upload_pitch); const u32 required_size = CalcUploadSize(r.height(), upload_pitch);
D3D12_TEXTURE_COPY_LOCATION srcloc; D3D12_TEXTURE_COPY_LOCATION srcloc;
srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
@ -250,8 +250,9 @@ bool GSTexture12::Update(const GSVector4i& r, const void* data, int pitch, int l
dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dstloc.SubresourceIndex = layer; dstloc.SubresourceIndex = layer;
const D3D12_BOX srcbox{0u, 0u, 0u, static_cast<UINT>(r.width()), static_cast<UINT>(r.height()), 1u}; const D3D12_BOX srcbox{0u, 0u, 0u, width, height, 1u};
cmdlist->CopyTextureRegion(&dstloc, r.x, r.y, 0, &srcloc, &srcbox); cmdlist->CopyTextureRegion(&dstloc, Common::AlignDownPow2((u32)r.x, block_size),
Common::AlignDownPow2((u32)r.y, block_size), 0, &srcloc, &srcbox);
if (m_texture.GetState() != D3D12_RESOURCE_STATE_COPY_DEST) if (m_texture.GetState() != D3D12_RESOURCE_STATE_COPY_DEST)
m_texture.TransitionSubresourceToState(cmdlist, layer, D3D12_RESOURCE_STATE_COPY_DEST, m_texture.GetState()); m_texture.TransitionSubresourceToState(cmdlist, layer, D3D12_RESOURCE_STATE_COPY_DEST, m_texture.GetState());
@ -293,7 +294,8 @@ bool GSTexture12::Map(GSMap& m, const GSVector4i* r, int layer)
void GSTexture12::Unmap() void GSTexture12::Unmap()
{ {
pxAssert(m_map_level < m_texture.GetLevels()); // this can't handle blocks/compressed formats at the moment.
pxAssert(m_map_level < m_texture.GetLevels() && !IsCompressedFormat());
g_perfmon.Put(GSPerfMon::TextureUploads, 1); g_perfmon.Put(GSPerfMon::TextureUploads, 1);
// TODO: non-tightly-packed formats // TODO: non-tightly-packed formats
@ -351,6 +353,8 @@ void GSTexture12::Unmap()
void GSTexture12::GenerateMipmap() void GSTexture12::GenerateMipmap()
{ {
pxAssert(!IsCompressedFormat(m_format));
for (int dst_level = 1; dst_level < m_mipmap_levels; dst_level++) for (int dst_level = 1; dst_level < m_mipmap_levels; dst_level++)
{ {
const int src_level = dst_level - 1; const int src_level = dst_level - 1;

View File

@ -1644,9 +1644,7 @@ void GSTextureCache::IncAge()
HashCacheEntry& e = it->second; HashCacheEntry& e = it->second;
if (e.refcount == 0 && ++e.age > max_hash_cache_age) if (e.refcount == 0 && ++e.age > max_hash_cache_age)
{ {
if (!e.is_replacement) m_hash_cache_memory_usage -= e.texture->GetMemUsage();
m_hash_cache_memory_usage -= e.texture->GetMemUsage();
g_gs_device->Recycle(e.texture); g_gs_device->Recycle(e.texture);
m_hash_cache.erase(it++); m_hash_cache.erase(it++);
} }
@ -2129,6 +2127,7 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
// found a replacement texture! insert it into the hash cache, and clear paltex (since it's not indexed) // found a replacement texture! insert it into the hash cache, and clear paltex (since it's not indexed)
paltex = false; paltex = false;
const HashCacheEntry entry{replacement_tex, 1u, 0u, true}; const HashCacheEntry entry{replacement_tex, 1u, 0u, true};
m_hash_cache_memory_usage += entry.texture->GetMemUsage();
return &m_hash_cache.emplace(key, entry).first->second; return &m_hash_cache.emplace(key, entry).first->second;
} }
else if ( else if (
@ -3163,6 +3162,9 @@ void GSTextureCache::InvalidateTemporarySource()
void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex) void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture* tex)
{ {
// When we insert we update memory usage. Old texture gets removed below.
m_hash_cache_memory_usage += tex->GetMemUsage();
auto it = m_hash_cache.find(key); auto it = m_hash_cache.find(key);
if (it == m_hash_cache.end()) if (it == m_hash_cache.end())
{ {

View File

@ -68,7 +68,7 @@ static u32 GetBlockCount(u32 extent, u32 block_size)
static void CalcBlockMipmapSize(u32 block_size, u32 bytes_per_block, u32 base_width, u32 base_height, u32 mip, u32& width, u32& height, u32& pitch, u32& size) static void CalcBlockMipmapSize(u32 block_size, u32 bytes_per_block, u32 base_width, u32 base_height, u32 mip, u32& width, u32& height, u32& pitch, u32& size)
{ {
width = std::max<u32>(base_width >> mip, 1u); width = std::max<u32>(base_width >> mip, 1u);
height = std::max<u32>(base_width >> mip, 1u); height = std::max<u32>(base_height >> mip, 1u);
const u32 blocks_wide = GetBlockCount(width, block_size); const u32 blocks_wide = GetBlockCount(width, block_size);
const u32 blocks_high = GetBlockCount(height, block_size); const u32 blocks_high = GetBlockCount(height, block_size);
@ -635,9 +635,9 @@ bool DDSLoader(const std::string& filename, GSTextureReplacements::ReplacementTe
for (u32 level = 1; level <= info.mip_count; level++) for (u32 level = 1; level <= info.mip_count; level++)
{ {
GSTextureReplacements::ReplacementTexture::MipData md; GSTextureReplacements::ReplacementTexture::MipData md;
u32 mip_width, mip_height, mip_size; u32 mip_size;
CalcBlockMipmapSize(info.block_size, info.bytes_per_block, info.width, info.height, level, mip_width, mip_height, md.pitch, mip_size); CalcBlockMipmapSize(info.block_size, info.bytes_per_block, info.width, info.height, level, md.width, md.height, md.pitch, mip_size);
if (!ReadDDSMipLevel(fp.get(), filename, level, info, mip_width, mip_height, md.data, md.pitch, mip_size)) if (!ReadDDSMipLevel(fp.get(), filename, level, info, md.width, md.height, md.data, md.pitch, mip_size))
break; break;
tex->mips.push_back(std::move(md)); tex->mips.push_back(std::move(md));

View File

@ -23,6 +23,8 @@
#include "common/ScopedGuard.h" #include "common/ScopedGuard.h"
#include "Config.h" #include "Config.h"
#include "Host.h"
#include "IconsFontAwesome5.h"
#include "GS/GSLocalMemory.h" #include "GS/GSLocalMemory.h"
#include "GS/Renderers/HW/GSTextureReplacements.h" #include "GS/Renderers/HW/GSTextureReplacements.h"
@ -325,7 +327,7 @@ void GSTextureReplacements::ReloadReplacementMap()
if (!name.has_value()) if (!name.has_value())
continue; continue;
DevCon.WriteLn("Found %ux%u replacement '%.*s'", name->Width(), name->Height(), static_cast<int>(filename.size()), filename.data()); DbgCon.WriteLn("Found %ux%u replacement '%.*s'", name->Width(), name->Height(), static_cast<int>(filename.size()), filename.data());
s_replacement_texture_filenames.emplace(name.value(), std::move(fd.FileName)); s_replacement_texture_filenames.emplace(name.value(), std::move(fd.FileName));
// zero out the CLUT hash, because we need this for checking if there's any replacements with this hash when using paltex // zero out the CLUT hash, because we need this for checking if there's any replacements with this hash when using paltex
@ -529,7 +531,10 @@ GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementText
static bool log_once = false; static bool log_once = false;
if (!log_once) if (!log_once)
{ {
Console.Warning("Disabling mipmaps on one or more compressed replacement textures."); static const char* message =
"Disabling autogenerated mipmaps on one or more compressed replacement textures. Please generate mipmaps when compressing your textures.";
Console.Warning(message);
Host::AddIconOSDMessage("DisablingReplacementAutoGeneratedMipmap", ICON_FA_EXCLAMATION_CIRCLE, message, Host::OSD_WARNING_DURATION);
log_once = true; log_once = true;
} }
@ -548,10 +553,8 @@ GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementText
{ {
for (u32 i = 0; i < static_cast<u32>(rtex.mips.size()); i++) for (u32 i = 0; i < static_cast<u32>(rtex.mips.size()); i++)
{ {
const u32 mip = i + 1; const ReplacementTexture::MipData& mip = rtex.mips[i];
const u32 mipw = std::max<u32>(rtex.width >> mip, 1u); tex->Update(GSVector4i(0, 0, static_cast<int>(mip.width), static_cast<int>(mip.height)), mip.data.data(), mip.pitch, i + 1);
const u32 miph = std::max<u32>(rtex.height >> mip, 1u);
tex->Update(GSVector4i(0, 0, mipw, miph), rtex.mips[i].data.data(), rtex.mips[i].pitch, mip);
} }
} }

View File

@ -30,6 +30,8 @@ namespace GSTextureReplacements
struct MipData struct MipData
{ {
u32 width;
u32 height;
u32 pitch; u32 pitch;
std::vector<u8> data; std::vector<u8> data;
}; };

View File

@ -262,6 +262,7 @@ bool GSTextureVK::Update(const GSVector4i& r, const void* data, int pitch, int l
} }
m_texture.UpdateFromBuffer(cmdbuf, layer, 0, r.x, r.y, width, height, m_texture.UpdateFromBuffer(cmdbuf, layer, 0, r.x, r.y, width, height,
Common::AlignUpPow2(height, GetCompressedBlockSize()),
CalcUploadRowLengthFromPitch(upload_pitch), buffer, buffer_offset); CalcUploadRowLengthFromPitch(upload_pitch), buffer, buffer_offset);
m_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
@ -303,7 +304,8 @@ bool GSTextureVK::Map(GSMap& m, const GSVector4i* r, int layer)
void GSTextureVK::Unmap() void GSTextureVK::Unmap()
{ {
pxAssert(m_map_level < m_texture.GetLevels()); // this can't handle blocks/compressed formats at the moment.
pxAssert(m_map_level < m_texture.GetLevels() && !IsCompressedFormat());
g_perfmon.Put(GSPerfMon::TextureUploads, 1); g_perfmon.Put(GSPerfMon::TextureUploads, 1);
// TODO: non-tightly-packed formats // TODO: non-tightly-packed formats
@ -334,6 +336,7 @@ void GSTextureVK::Unmap()
} }
m_texture.UpdateFromBuffer(cmdbuf, m_map_level, 0, m_map_area.x, m_map_area.y, width, height, m_texture.UpdateFromBuffer(cmdbuf, m_map_level, 0, m_map_area.x, m_map_area.y, width, height,
Common::AlignUpPow2(height, GetCompressedBlockSize()),
CalcUploadRowLengthFromPitch(pitch), buffer.GetBuffer(), buffer_offset); CalcUploadRowLengthFromPitch(pitch), buffer.GetBuffer(), buffer_offset);
m_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);