FrameGen: give the capture barrier a real source scope, and ask for TRANSFER_SRC

Two findings from the Vulkan audit, both of which let the interpolation read an
image it had no valid dependency on.

The barrier before Process used TOP_OF_PIPE with MEMORY_READ. TOP_OF_PIPE in
srcStageMask specifies no stage of execution, so the first synchronization scope
was empty and no availability operation happened for the colour-attachment and
transfer writes the frame made into that image in the PREVIOUS submission.
Submission order on a queue orders execution; it is not a memory dependency. So
the interpolation could read a partially-flushed frame -- which presents as
smearing during motion and does not respond to any interpolation setting, since
the interpolation is not what is wrong. Source scope is now
COLOR_ATTACHMENT_OUTPUT|TRANSFER with the matching write access, and the mirror
barrier's empty BOTTOM_OF_PIPE second scope becomes ALL_COMMANDS.

Separately, the swapchain was created COLOR_ATTACHMENT|TRANSFER_DST, but
FrameGen::Process copies FROM the presented image as TRANSFER_SRC_OPTIMAL. Both
that layout and vkCmdCopyImage require TRANSFER_SRC usage. Nothing else in the
renderer reads a WSI image, so it had never been needed. Requested only where the
surface reports it, with a warning otherwise, since it is not guaranteed on
Android.
This commit is contained in:
jpolo1224
2026-08-22 06:24:56 -04:00
parent e7ab163b54
commit 1e9f8c70da
2 changed files with 34 additions and 3 deletions
+14 -3
View File
@@ -1382,10 +1382,19 @@ namespace vk::frame_gen
to_read.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
to_read.image = g_source_image;
to_read.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
to_read.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
// The frame's WRITES are what has to become visible here, not a read.
//
// This used TOP_OF_PIPE with MEMORY_READ, and TOP_OF_PIPE in srcStageMask specifies no
// stage at all -- an empty first scope, so no availability operation happened for the
// colour-attachment and transfer writes the frame made into this image in the previous
// submission. Submission order on a queue is not a memory dependency. The interpolation
// could therefore read a partially-flushed frame, which looks like smearing and does not
// respond to any interpolation setting.
to_read.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
to_read.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(g_native.cmd[slot], VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
vkCmdPipelineBarrier(g_native.cmd[slot],
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 0, nullptr, 0, nullptr, 1, &to_read);
@@ -1398,12 +1407,14 @@ namespace vk::frame_gen
VkImageMemoryBarrier from_read = to_read;
from_read.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
from_read.newLayout = g_source_layout;
// BOTTOM_OF_PIPE in dstStageMask is likewise an empty second scope. The presentation
// engine is the consumer, so the release has to be visible to it.
from_read.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
from_read.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
vkCmdPipelineBarrier(g_native.cmd[slot],
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &from_read);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &from_read);
// Plan straight after Process, as the ARMSX2 driver does: Process is what updates the
// pacer's view of the frame, and GeneratedFrameCount() only answers once it has warmed up.
+20
View File
@@ -410,6 +410,26 @@ namespace vk
swap_info.imageColorSpace = m_color_space;
swap_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
// Frame generation READS the presented image: FrameGen::Process copies it into the
// interpolation chain as TRANSFER_SRC_OPTIMAL. Both that layout and vkCmdCopyImage
// require the image to have been created with TRANSFER_SRC, and nothing else in the
// renderer ever reads a WSI image, so this was never needed before and was never asked
// for -- the copy was reading an image the driver had not prepared for reading.
//
// Requested only where the surface offers it, since TRANSFER_SRC is not guaranteed on
// Android surfaces. Where it is missing, frame generation captures nothing valid, which
// is a better failure than an invalid one.
if (g_cfg.video.frame_generation != frame_generation_mode::off &&
(surface_descriptors.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT))
{
swap_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
else if (g_cfg.video.frame_generation != frame_generation_mode::off)
{
rsx_log.warning("Swapchain: surface does not support TRANSFER_SRC; frame generation"
" cannot read presented frames on this surface.");
}
swap_info.preTransform = pre_transform;
swap_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swap_info.imageArrayLayers = 1;