diff --git a/common/Vulkan/Texture.cpp b/common/Vulkan/Texture.cpp index 77623fede8..0ad591778b 100644 --- a/common/Vulkan/Texture.cpp +++ b/common/Vulkan/Texture.cpp @@ -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(buffer_offset), row_length, height, + const VkBufferImageCopy bic = {static_cast(buffer_offset), row_length, buffer_height, {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1u}, {static_cast(x), static_cast(y), 0}, {width, height, 1u}}; diff --git a/common/Vulkan/Texture.h b/common/Vulkan/Texture.h index fa4c480d85..bf4a764ae6 100644 --- a/common/Vulkan/Texture.h +++ b/common/Vulkan/Texture.h @@ -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; diff --git a/pcsx2/Frontend/VulkanHostDisplay.cpp b/pcsx2/Frontend/VulkanHostDisplay.cpp index e0e2460679..f19de05657 100644 --- a/pcsx2/Frontend/VulkanHostDisplay.cpp +++ b/pcsx2/Frontend/VulkanHostDisplay.cpp @@ -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; } diff --git a/pcsx2/Frontend/imgui_impl_vulkan.cpp b/pcsx2/Frontend/imgui_impl_vulkan.cpp index 8583989a06..17689e4a45 100644 --- a/pcsx2/Frontend/imgui_impl_vulkan.cpp +++ b/pcsx2/Frontend/imgui_impl_vulkan.cpp @@ -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. diff --git a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp index b54b92dbbe..aa59e7b171 100644 --- a/pcsx2/GS/Renderers/DX11/GSTexture11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTexture11.cpp @@ -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 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); diff --git a/pcsx2/GS/Renderers/DX12/GSTexture12.cpp b/pcsx2/GS/Renderers/DX12/GSTexture12.cpp index dd1be966c1..850589734a 100644 --- a/pcsx2/GS/Renderers/DX12/GSTexture12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSTexture12.cpp @@ -48,13 +48,11 @@ std::unique_ptr 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(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(r.width()), static_cast(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; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index dcb0d0b7e8..e565933d20 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -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()) { diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp b/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp index f5dd7b29b1..e321f7798b 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacementLoaders.cpp @@ -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(base_width >> mip, 1u); - height = std::max(base_width >> mip, 1u); + height = std::max(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)); diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp b/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp index fbc75d173a..e42b429a2e 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacements.cpp @@ -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(filename.size()), filename.data()); + DbgCon.WriteLn("Found %ux%u replacement '%.*s'", name->Width(), name->Height(), static_cast(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(rtex.mips.size()); i++) { - const u32 mip = i + 1; - const u32 mipw = std::max(rtex.width >> mip, 1u); - const u32 miph = std::max(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(mip.width), static_cast(mip.height)), mip.data.data(), mip.pitch, i + 1); } } diff --git a/pcsx2/GS/Renderers/HW/GSTextureReplacements.h b/pcsx2/GS/Renderers/HW/GSTextureReplacements.h index 110fcb5e94..cc61be7702 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureReplacements.h +++ b/pcsx2/GS/Renderers/HW/GSTextureReplacements.h @@ -30,6 +30,8 @@ namespace GSTextureReplacements struct MipData { + u32 width; + u32 height; u32 pitch; std::vector data; }; diff --git a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp index 50f11bb93f..d73076561e 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSTextureVK.cpp @@ -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);