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,
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;
if (old_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
TransitionSubresourcesToLayout(
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},
{width, height, 1u}};

View File

@ -72,7 +72,7 @@ namespace Vulkan
VkFramebuffer CreateFramebuffer(VkRenderPass render_pass);
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:
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);
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;
}

View File

@ -428,7 +428,7 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
std::memcpy(ai.pMappedData, pixels, 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.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);
// 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 "GS/GSPng.h"
#include "GS/GSPerfMon.h"
#include "common/Align.h"
GSTexture11::GSTexture11(wil::com_ptr_nothrow<ID3D11Texture2D> texture, const D3D11_TEXTURE2D_DESC& desc,
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);
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).
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:
{
// 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 (levels > 1 && d3d_format != DXGI_FORMAT_R8G8B8A8_UNORM)
{
Console.Warning("DX12: Refusing to create a %ux%u format %u mipmapped texture", width, height, format);
return nullptr;
}
// This is a little annoying. basically, to do mipmap generation, we need to be a render target.
// If it's a compressed texture, we won't be generating mips anyway, so this should be fine.
const D3D12_RESOURCE_FLAGS flags = (levels > 1 && !IsCompressedFormat(format)) ?
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET :
D3D12_RESOURCE_FLAG_NONE;
D3D12::Texture texture;
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);
const u32 width = r.width();
const u32 height = r.height();
// Footprint and box must be block aligned for compressed textures.
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 required_size = CalcUploadSize(height, upload_pitch);
const u32 required_size = CalcUploadSize(r.height(), upload_pitch);
D3D12_TEXTURE_COPY_LOCATION srcloc;
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.SubresourceIndex = layer;
const D3D12_BOX srcbox{0u, 0u, 0u, static_cast<UINT>(r.width()), static_cast<UINT>(r.height()), 1u};
cmdlist->CopyTextureRegion(&dstloc, r.x, r.y, 0, &srcloc, &srcbox);
const D3D12_BOX srcbox{0u, 0u, 0u, width, height, 1u};
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)
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()
{
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);
// TODO: non-tightly-packed formats
@ -351,6 +353,8 @@ void GSTexture12::Unmap()
void GSTexture12::GenerateMipmap()
{
pxAssert(!IsCompressedFormat(m_format));
for (int dst_level = 1; dst_level < m_mipmap_levels; dst_level++)
{
const int src_level = dst_level - 1;

View File

@ -1644,9 +1644,7 @@ void GSTextureCache::IncAge()
HashCacheEntry& e = it->second;
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);
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)
paltex = false;
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;
}
else if (
@ -3163,6 +3162,9 @@ void GSTextureCache::InvalidateTemporarySource()
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);
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)
{
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_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++)
{
GSTextureReplacements::ReplacementTexture::MipData md;
u32 mip_width, mip_height, mip_size;
CalcBlockMipmapSize(info.block_size, info.bytes_per_block, info.width, info.height, level, mip_width, mip_height, md.pitch, mip_size);
if (!ReadDDSMipLevel(fp.get(), filename, level, info, mip_width, mip_height, md.data, md.pitch, mip_size))
u32 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, md.width, md.height, md.data, md.pitch, mip_size))
break;
tex->mips.push_back(std::move(md));

View File

@ -23,6 +23,8 @@
#include "common/ScopedGuard.h"
#include "Config.h"
#include "Host.h"
#include "IconsFontAwesome5.h"
#include "GS/GSLocalMemory.h"
#include "GS/Renderers/HW/GSTextureReplacements.h"
@ -325,7 +327,7 @@ void GSTextureReplacements::ReloadReplacementMap()
if (!name.has_value())
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));
// 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;
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;
}
@ -548,10 +553,8 @@ GSTexture* GSTextureReplacements::CreateReplacementTexture(const ReplacementText
{
for (u32 i = 0; i < static_cast<u32>(rtex.mips.size()); i++)
{
const u32 mip = i + 1;
const u32 mipw = std::max<u32>(rtex.width >> mip, 1u);
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);
const ReplacementTexture::MipData& mip = rtex.mips[i];
tex->Update(GSVector4i(0, 0, static_cast<int>(mip.width), static_cast<int>(mip.height)), mip.data.data(), mip.pitch, i + 1);
}
}

View File

@ -30,6 +30,8 @@ namespace GSTextureReplacements
struct MipData
{
u32 width;
u32 height;
u32 pitch;
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,
Common::AlignUpPow2(height, GetCompressedBlockSize()),
CalcUploadRowLengthFromPitch(upload_pitch), buffer, buffer_offset);
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()
{
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);
// 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,
Common::AlignUpPow2(height, GetCompressedBlockSize()),
CalcUploadRowLengthFromPitch(pitch), buffer.GetBuffer(), buffer_offset);
m_texture.TransitionToLayout(cmdbuf, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);