Fix layered image subresource synchronization

This commit is contained in:
nmzik
2026-07-16 22:26:40 +02:00
parent ae86249765
commit 41114be8da
10 changed files with 207 additions and 62 deletions
@@ -1001,7 +1001,7 @@ std::pair<VulkanBuffer*, uint64_t> BufferCache::ObtainBuffer(CommandBuffer* com
" size=0x%016" PRIx64 " read=%d written=%d formatted=%d\n",
vaddr, size, is_read, is_written, is_formatted);
}
m_texture_cache->InvalidateMemoryFromGPU(vaddr, size, true);
(void)m_texture_cache->InvalidateMemoryFromGPU(vaddr, size, true);
}
}
if (is_written) {
@@ -1421,6 +1421,7 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
}
ValidateGpuAccess(src_vaddr, size, true, false);
ValidateGpuAccess(dst_vaddr, size, false, true);
bool dst_image_transition = false;
{
std::lock_guard transaction(m_resource_mutex);
const bool src_image_gpu = m_texture_cache->HasGpuModifiedRangeOverlap(src_vaddr, size);
@@ -1452,7 +1453,7 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
" dst=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
src_vaddr, dst_vaddr, size);
}
m_texture_cache->InvalidateMemoryFromGPU(dst_vaddr, size);
dst_image_transition = m_texture_cache->InvalidateMemoryFromGPU(dst_vaddr, size);
// A clean target destination is handled above like a protected host write. Target
// aliases that require an actual GPU buffer copy remain unsupported.
if (m_texture_cache->HasGpuTargetPageOverlap(dst_vaddr, size)) {
@@ -1470,7 +1471,8 @@ void BufferCache::CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64
}
}
const auto [src, src_offset] = ObtainBuffer(command, ctx, src_vaddr, size, false, true);
const auto [dst, dst_offset] = ObtainBuffer(command, ctx, dst_vaddr, size, true, false);
const auto [dst, dst_offset] =
ObtainBuffer(command, ctx, dst_vaddr, size, true, false, dst_image_transition);
if (src == dst && src_offset < dst_offset + size && dst_offset < src_offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap, src_offset=0x%016" PRIx64
" dst_offset=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
@@ -330,6 +330,7 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
render_target_slice_offset);
}
r->base_array_layer = view.base_layer;
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
+1 -1
View File
@@ -624,7 +624,7 @@ ClassifyRenderTargetOverlap(const RenderTargetInfo& cached, bool cached_gpu_modi
}
// For an equal-address allocation-pool entry, a changed block raster or block size is a new
// image allocation, not a view of the old image. In Kyty we can only retire an
// already-published target with page-isolated storage :)
// already-published target with page-isolated storage.
const bool page_isolated =
cached.address % TRACKER_PAGE_SIZE == 0 && cached.size % TRACKER_PAGE_SIZE == 0 &&
requested.address % TRACKER_PAGE_SIZE == 0 && requested.size % TRACKER_PAGE_SIZE == 0;
+21 -9
View File
@@ -32,6 +32,7 @@
#include "libs/errno.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstring>
@@ -1247,6 +1248,24 @@ void RenderDrawIndexAuto(uint64_t submit_id, CommandBuffer* buffer, HW::Context*
index_bind, false, false, true);
}
bool IsSameColorResolveSubresource(const RenderColorInfo& src, const RenderColorInfo& dst) {
return src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
src.base_array_layer == dst.base_array_layer;
}
ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src, const RenderColorInfo& dst,
uint32_t width, uint32_t height) {
ImageImageCopy region {};
region.src_image = src.vulkan_buffer;
region.src_level = src.base_mip_level;
region.dst_level = dst.base_mip_level;
region.width = width;
region.height = height;
region.src_layer = src.base_array_layer;
region.dst_layer = dst.base_array_layer;
return region;
}
static bool ResolveColorTargets(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
uint32_t render_target_slice_offset) {
if (hw.GetColorControl().mode != 3) {
@@ -1268,7 +1287,7 @@ static bool ResolveColorTargets(uint64_t submit_id, CommandBuffer* buffer, const
src.type == RenderColorType::NoColorOutput || dst.type == RenderColorType::NoColorOutput) {
return false;
}
if (src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level) {
if (IsSameColorResolveSubresource(src, dst)) {
return true;
}
@@ -1278,14 +1297,7 @@ static bool ResolveColorTargets(uint64_t submit_id, CommandBuffer* buffer, const
return false;
}
std::vector<ImageImageCopy> regions;
ImageImageCopy region {};
region.src_image = src.vulkan_buffer;
region.src_level = src.base_mip_level;
region.dst_level = dst.base_mip_level;
region.width = width;
region.height = height;
regions.push_back(region);
const std::array regions {MakeColorResolveCopy(src, dst, width, height)};
MarkRenderTargetGpuWritten(dst);
UtilImageToImage(buffer, regions, dst.vulkan_buffer, dst.vulkan_buffer->layout);
@@ -15,6 +15,7 @@ namespace Libs::Graphics {
class CommandBuffer;
struct DepthStencilVulkanImage;
struct ImageImageCopy;
struct RenderTextureVulkanImage;
struct ShaderVertexInputInfo;
struct VulkanImage;
@@ -236,6 +237,7 @@ struct RenderColorInfo {
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {};
uint32_t base_mip_level = 0;
uint32_t base_array_layer = 0;
uint64_t base_addr = 0;
uint64_t buffer_size = 0;
uint32_t target_slot = 0;
@@ -243,6 +245,12 @@ struct RenderColorInfo {
VkClearColorValue color_clear_value {};
};
[[nodiscard]] bool IsSameColorResolveSubresource(const RenderColorInfo& src,
const RenderColorInfo& dst);
[[nodiscard]] ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src,
const RenderColorInfo& dst, uint32_t width,
uint32_t height);
struct ScissorRect {
int left = 0;
int top = 0;
+28 -39
View File
@@ -387,18 +387,9 @@ struct TextureCache::ReadbackWorker {
}
download.resize(info.size);
std::fill(download.begin(), download.end(), 0);
std::vector<ImageBufferCopy> regions;
regions.reserve(info.layers);
for (uint32_t layer = 0; layer < info.layers; layer++) {
ImageBufferCopy region {};
region.offset = static_cast<uint32_t>(slice_size * layer);
region.pitch = info.pitch;
region.width = info.width;
region.height = info.height;
region.src_layer = layer;
region.aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
regions.push_back(region);
}
const auto regions =
MakeLayeredImageBufferCopies(info.layers, slice_size, info.pitch, info.width,
info.height, VK_IMAGE_ASPECT_DEPTH_BIT);
UtilFillBuffer(cached.ctx, download.data(), info.size, regions, cached.image,
cached.image->layout);
guest.resize(info.size);
@@ -467,17 +458,8 @@ struct TextureCache::ReadbackWorker {
}
download.resize(info.size);
std::fill(download.begin(), download.end(), 0);
std::vector<ImageBufferCopy> regions;
regions.reserve(layers);
for (uint32_t layer = 0; layer < layers; layer++) {
ImageBufferCopy region {};
region.offset = static_cast<uint32_t>(slice_size * layer);
region.pitch = info.pitch;
region.width = info.width;
region.height = info.height;
region.src_layer = layer;
regions.push_back(region);
}
const auto regions =
MakeLayeredImageBufferCopies(layers, slice_size, info.pitch, info.width, info.height);
UtilFillBuffer(cached.ctx, download.data(), info.size, regions, cached.image,
cached.image->layout);
if (tiled) {
@@ -3046,7 +3028,6 @@ void TextureCache::MarkGpuWritten(VulkanImage* image) {
cached->depth.htile_address, cached->depth.htile_size, true,
[](uint64_t, uint64_t) noexcept {}, []() noexcept {});
meta->second.gpu_modified = true;
meta->second.clear_mask = 0;
}
}
if (!cached->gpu_modified) {
@@ -3110,29 +3091,34 @@ void TextureCache::SynchronizeRenderTargetToBufferLocked(CachedImage& cached) {
target.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const auto rows = static_cast<uint64_t>(target.height - 1);
TileSizeAlign exact {};
const bool exact_tiled = tiled &&
TileGetRenderTargetSize(target.width, target.height, target.pitch,
target.bytes_per_element, &exact) &&
exact.align == 65536 && exact.size == target.size;
const bool single_slice = TileGetRenderTargetSize(target.width, target.height, target.pitch,
target.bytes_per_element, &exact);
const bool layered_size = target.layers != 0 && single_slice &&
exact.size <= UINT64_MAX / target.layers &&
exact.size * target.layers == target.size;
const bool exact_tiled = tiled && exact.align == 65536 && layered_size;
if (cached.kind != CachedImage::Kind::RenderTarget || !cached.gpu_modified ||
cached.buffer_modified || cached.ctx == nullptr || cached.image == nullptr ||
(!linear && !exact_tiled) || target.address == 0 || target.size == 0 || target.width == 0 ||
target.height == 0 || target.pitch < target.width || target.bytes_per_element == 0 ||
target.levels != 1 || rows > (UINT64_MAX - target.width) / target.pitch) {
target.layers == 0 || target.size % target.layers != 0 || target.size > UINT32_MAX ||
target.levels != 1 || cached.image->layers != target.layers ||
rows > (UINT64_MAX - target.width) / target.pitch) {
EXIT("TextureCache: unsupported render-target buffer synchronization, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u pitch=%u bpe=%u levels=%u tile=%u"
" extent=%ux%u pitch=%u bpe=%u levels=%u layers=%u/%u tile=%u"
" gpu_modified=%d buffer_modified=%d\n",
target.address, target.size, target.width, target.height, target.pitch,
target.bytes_per_element, target.levels, target.tile_mode, cached.gpu_modified,
cached.buffer_modified);
target.bytes_per_element, target.levels, target.layers, cached.image->layers,
target.tile_mode, cached.gpu_modified, cached.buffer_modified);
}
const auto linear_elements = rows * target.pitch + target.width;
if (linear_elements > UINT64_MAX / target.bytes_per_element) {
EXIT("TextureCache: render-target buffer synchronization size overflow\n");
}
const auto linear_size = linear_elements * target.bytes_per_element;
if (linear_size > target.size || cached.image->format != target.format ||
const auto slice_size = target.size / target.layers;
if (linear_size > slice_size || cached.image->format != target.format ||
cached.image->extent.width != target.width ||
cached.image->extent.height != target.height ||
(tiled && target.bytes_per_element != 1 && target.bytes_per_element != 2 &&
@@ -3149,7 +3135,9 @@ void TextureCache::SynchronizeRenderTargetToBufferLocked(CachedImage& cached) {
VulkanDeviceWaitIdle(cached.ctx);
m_buffer_transition_linear.resize(target.size);
std::fill(m_buffer_transition_linear.begin(), m_buffer_transition_linear.end(), 0);
UtilFillBuffer(cached.ctx, m_buffer_transition_linear.data(), linear_size, target.pitch,
const auto regions = MakeLayeredImageBufferCopies(target.layers, slice_size, target.pitch,
target.width, target.height);
UtilFillBuffer(cached.ctx, m_buffer_transition_linear.data(), target.size, regions,
cached.image, cached.image->layout);
if (tiled) {
m_buffer_transition_guest.resize(target.size);
@@ -3171,7 +3159,7 @@ void TextureCache::SynchronizeRenderTargetToBufferLocked(CachedImage& cached) {
cached.buffer_modified = true;
}
void TextureCache::InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool TextureCache::InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool formatted_buffer_write) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
@@ -3226,23 +3214,24 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
sampled_write = sampled;
}
if (match == m_images.end()) {
return;
return false;
}
if (sampled_write) {
(**match).buffer_modified = true;
return;
return true;
}
if (action == BufferImageAlias::VideoOutWrite) {
auto& cached = **match;
cached.buffer_modified = true;
return;
return true;
}
if (action == BufferImageAlias::RenderTargetWrite) {
SynchronizeRenderTargetToBufferLocked(**match);
return;
return true;
}
m_memory_tracker.UntrackMemory(vaddr, size);
m_images.erase(match);
return true;
}
DepthStencilVulkanImage* TextureCache::FindDepthTargetByRange(uint64_t vaddr, uint64_t size) {
@@ -56,7 +56,7 @@ public:
uint64_t size, uint32_t packed_clear);
void MarkGpuWritten(VulkanImage* image);
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
void InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RenderTextureVulkanImage* FindRenderTargetByRange(CommandBuffer* command,
uint64_t vaddr, uint64_t size);
+21
View File
@@ -22,6 +22,27 @@ static Common::Mutex g_scratch_buffer_mutex;
static std::unique_ptr<uint8_t[]> g_scratch_buffer;
static uint64_t g_scratch_buffer_capacity = 0;
std::vector<ImageBufferCopy> MakeLayeredImageBufferCopies(uint32_t layers, uint64_t slice_size,
uint32_t pitch, uint32_t width,
uint32_t height,
VkImageAspectFlags aspect) {
if (layers == 0 || slice_size == 0 || slice_size > UINT32_MAX ||
slice_size > UINT32_MAX / layers || pitch < width || width == 0 || height == 0) {
EXIT("invalid layered image-buffer copy layout\n");
}
std::vector<ImageBufferCopy> regions(layers);
for (uint32_t layer = 0; layer < layers; layer++) {
auto& region = regions[layer];
region.offset = static_cast<uint32_t>(slice_size * layer);
region.pitch = pitch;
region.width = width;
region.height = height;
region.src_layer = layer;
region.aspect = aspect;
}
return regions;
}
UtilScratchBuffer::UtilScratchBuffer(uint64_t size) {
EXIT_IF(size == 0);
+6
View File
@@ -6,6 +6,7 @@
#include <span>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
namespace Libs::Graphics {
@@ -64,6 +65,11 @@ struct ImageImageCopy {
int dst_z = 0;
};
[[nodiscard]] std::vector<ImageBufferCopy>
MakeLayeredImageBufferCopies(uint32_t layers, uint64_t slice_size, uint32_t pitch, uint32_t width,
uint32_t height,
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT);
enum class StagingBufferType { Texture, Vertex, ReadBack };
class UtilScratchBuffer {