Resolve image aliases before buffer uploads

This commit is contained in:
nmzik 2026-09-14 16:29:43 +04:00
parent bce8924dd6
commit dd408ffd00
5 changed files with 146 additions and 16 deletions

View File

@ -15,6 +15,7 @@
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@ -850,7 +851,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
}
}
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages) {
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors) {
bool uses_dma = false;
for (auto* stage: stages) {
FindBuffers(*stage);
@ -859,12 +861,27 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
if (uses_dma) {
m_context.PrepareBda();
}
for (auto* stage: stages) {
RebindBuffers(*stage);
}
for (auto* stage: stages) {
RebindImages(*stage);
}
auto& cache = m_context.GetTextureCache();
for (auto& target: colors) {
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
}
}
// Discovery can read back PS5 metadata and submit the scheduler. Reserve draw buffers only
// after image identities are final; attachment layout transitions follow buffer alias copies.
for (auto* stage: stages) {
RebindBuffers(*stage);
}
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer,

View File

@ -176,7 +176,8 @@ private:
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages);
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors);
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask = false, bool exact_format = false);

View File

@ -366,8 +366,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
if (program.info.uses_dma) {
m_context.PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
RebindBuffers(bindings);
auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;

View File

@ -479,14 +479,10 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
const auto owner = cache.m_slot_images.try_get(target.image_id);
if (owner == nullptr || (!owner->registered && !owner->info.data.Empty()) ||
owner->binding.needs_rebind) {
EXIT("color target changed after render-state discovery\n");
}
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
@ -1116,7 +1112,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
descriptor_stages[stage_count++] = &*bindings.pixel;
}
const auto stages = std::span {descriptor_stages.data(), stage_count};
PrepareGraphicsBindings(stages);
PrepareGraphicsBindings(stages, std::span {state.color_info, state.color_count});
PreparedVertexBuffers vertex_bindings;
PreparedIndexBuffer index_binding;
if (!mesh_active) {

View File

@ -410,10 +410,16 @@ struct RenderExecutorTestAccess {
stages[1] = &*result.pixel;
}
executor.PrepareGraphicsBindings(
std::span{stages.data(), pixel_active ? 2u : 1u});
std::span{stages.data(), pixel_active ? 2u : 1u}, {});
return result;
}
static void PrepareGraphicsBindings(RenderExecutor &executor,
std::span<PreparedBindings *const> stages,
std::span<RenderColorInfo> colors) {
executor.PrepareGraphicsBindings(stages, colors);
}
static PipelineCache::Pipeline
CreateDescriptorPipeline(RenderExecutor &executor,
std::span<PreparedBindings *const> stages) {
@ -10971,11 +10977,17 @@ public:
rebound_array_target.desc = array_target;
rebound_array_target.image_id = array_target_id;
RenderDepthInfo no_array_depth{};
const auto acquired_array_view = array_binding.images[0].image_view;
RenderExecutorTestAccess::PrepareGraphicsBindings(
executor, std::span<PreparedBindings *const>{},
std::span{&rebound_array_target, 1u});
auto array_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &rebound_array_target, 1,
no_array_depth);
Require(name, "expanded array target rebind",
rebound_array_target.image_id == expanded_array_id &&
array_binding.images[0].image_id == expanded_array_id &&
array_binding.images[0].image_view == acquired_array_view &&
array_rendering.color_attachments[0].image_view != nullptr &&
array_rendering.num_color_attachments == 1 &&
array_rendering.width == 128 &&
@ -10987,6 +10999,106 @@ public:
"backing");
RenderExecutorTestAccess::ResetBindings(executor);
{
constexpr uint32_t before = 0x13579bdfu;
constexpr uint32_t guard = 0x2468ace0u;
constexpr uint32_t after = 0xa1b2c3d4u;
vk::ClearValue clear{};
clear.color.uint32[0] = before;
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), expanded_array_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 2}, clear);
clear.color.uint32[0] = guard;
TextureCacheTestAccess::ClearImage(
texture_cache, scheduler.Current(), expanded_array_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
ShaderRecompiler::IR::Program buffer_ir{};
buffer_ir.stage = ShaderType::Vertex;
buffer_ir.resource_tracking_complete = true;
buffer_ir.info.buffers.resize(1);
buffer_ir.info.buffers[0].read = true;
buffer_ir.info.buffers[0].formatted = true;
allocate_bindings(buffer_ir);
ShaderRecompiler::IR::CompiledShaderInfo buffer_program{};
buffer_program.stage = buffer_ir.stage;
buffer_program.info = std::move(buffer_ir.info);
buffer_program.bindings = std::move(buffer_ir.bindings);
constexpr uint64_t buffer_size = 2 * target_mip_size;
static_assert(buffer_size > BufferCache::CACHING_PAGESIZE);
ShaderBufferResource descriptor{};
descriptor.UpdateAddress48(array_target_address);
descriptor.fields[1] |= 4u << 16u;
descriptor.fields[2] = buffer_size / sizeof(uint32_t);
descriptor.fields[3] = 0x00014004u;
ShaderStageRuntime buffer_runtime{.program = &buffer_program};
auto &value = buffer_runtime.resources.buffers.emplace_back();
std::memcpy(value.dwords.data(), descriptor.fields, sizeof(descriptor.fields));
value.dword_count = 4;
auto buffer_bindings = executor.PrepareBindings(buffer_runtime);
std::array stages{&buffer_bindings};
RenderColorInfo target{};
target.desc = array_target;
target.image_id = array_target_id;
RenderExecutorTestAccess::PrepareGraphicsBindings(
executor, stages, std::span{&target, 1u});
const auto &buffer = buffer_bindings.buffers[0];
Require(name, "rediscovered target formatted-buffer read",
target.image_id == expanded_array_id &&
buffer.range == buffer_size &&
buffer_bindings.images.empty() &&
texture_cache.GetImage(target.image_id).backing.state.layout ==
vk::ImageLayout::eTransferSrcOptimal,
"target discovery did not precede the formatted image-to-buffer copy");
auto readback = CreateHostBuffer(name, 2 * sizeof(uint32_t),
vk::BufferUsageFlagBits::eTransferDst, {0, 0});
const std::array copies{
vk::BufferCopy{buffer.offset, 0, sizeof(uint32_t)},
vk::BufferCopy{buffer.offset + target_mip_size, sizeof(uint32_t),
sizeof(uint32_t)}};
scheduler.Current().Handle().copyBuffer(buffer.buffer, readback.buffer,
copies.size(), copies.data());
vk::BufferMemoryBarrier readback_barrier{};
readback_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
readback_barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
readback_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
readback_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
readback_barrier.buffer = readback.buffer;
readback_barrier.size = readback.size;
scheduler.Current().Handle().pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eHost,
{}, 0, nullptr, 1, &readback_barrier, 0, nullptr);
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &target, 1, no_array_depth);
Require(name, "attachment layout after formatted-buffer read",
rendering.color_attachments[0].image_layout ==
vk::ImageLayout::eColorAttachmentOptimal &&
texture_cache.GetImage(target.image_id).backing.state.layout ==
vk::ImageLayout::eColorAttachmentOptimal,
"the formatted buffer copy left the render attachment in its transfer layout");
vk::ClearAttachment clear_attachment{};
clear_attachment.aspectMask = vk::ImageAspectFlagBits::eColor;
clear_attachment.colorAttachment = 0;
clear_attachment.clearValue.color.uint32[0] = after;
const vk::ClearRect clear_rect{{{0, 0}, {128, 128}}, 0, 1};
scheduler.BeginRendering(rendering);
scheduler.Current().Handle().clearAttachments(1, &clear_attachment, 1, &clear_rect);
scheduler.EndRendering();
scheduler.Finish();
Require(name, "formatted alias observes prior GPU contents",
ReadBuffer(name, readback, 2) == std::vector<u32>{before, guard},
"formatted buffer acquisition copied stale guest bytes instead of the image");
DestroyBuffer(&readback);
Require(name, "rendering after formatted-buffer acquisition",
ReadCachedTexel(name, context, target.image_id) == std::vector<u32>{after} &&
ReadCachedTexel(name, context, target.image_id, {}, {1, 1, 1}, 1) ==
std::vector<u32>{guard},
"attachment rendering lost its new color or changed the untouched array layer");
RenderExecutorTestAccess::ResetBindings(executor);
}
auto colliding_msaa_texture = array_texture;
colliding_msaa_texture.fields[3] =
DstSel(4, 5, 6, 7) | (1u << 16u) |
@ -11269,6 +11381,8 @@ public:
TextureCacheTestAccess::Owner(texture_cache,
target_subresource_id) == nullptr,
"the displaced target slot was not erased after GPU completion");
RenderExecutorTestAccess::PrepareGraphicsBindings(
executor, std::span<PreparedBindings *const>{}, std::span{&rebound_color, 1u});
auto rebound_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &rebound_color, 1, rebound_depth);
Require(name, "guest target extent after host mip remap",
@ -11327,6 +11441,8 @@ public:
RenderDepthInfo ordered_depth{};
ordered_depth.desc = ordered_depth_desc;
ordered_depth.image_id = ordered_depth_id;
RenderExecutorTestAccess::PrepareGraphicsBindings(
executor, std::span<PreparedBindings *const>{}, std::span{&ordered_color, 1u});
auto ordered_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
executor, scheduler.Current(), &ordered_color, 1, ordered_depth);
Require(name, "color-before-depth acquisition order",