vulkan: order back-to-back color passes

Add same-layout color attachment barriers before geometry and wide-surface render passes so prior writes are visible to LOAD and blend operations.

Co-authored-by: Martin Penkava <mpenkava1337@gmail.com>

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-07-14 10:12:07 -07:00
parent 7f9f59a2bb
commit 92cbb1a327
3 changed files with 70 additions and 0 deletions
@@ -0,0 +1,22 @@
# Martin Penkava PR #16: Vulkan color pass self-barrier
Last evaluated: 2026-07-14
| Item | Value |
| --- | --- |
| Head repository/branch | `shaneomac1337/psxrecomp`, `smackdown2-fixes` (submitted as `mstan/psxrecomp` PR #16) |
| Source commit | `4517bd5a3ed0901a2402ff7b93bebe3de2cf9afd` |
| Source author | Martin Penkava `<mpenkava1337@gmail.com>` |
| Source co-author trailer | Claude Fable 5 `<noreply@anthropic.com>` |
| Local base | `7085721afe338a03cb114321a3576cdff420b732` (`origin/master`) |
| Local branch | `fix/pr16-vulkan-color-pass-barrier-mpenkava` |
## Included scope
Adds an explicit color-attachment memory dependency between back-to-back geometry, wide-surface, and wide-clear render passes when the image remains in `COLOR_ATTACHMENT_OPTIMAL`. This mirrors the existing depth/stencil ordering and covers drivers that do not tolerate the missing external dependency.
## Deliberate exclusions
- Packed upload alignment and swapchain acquire synchronization are isolated on separate branches.
- All `PSX_VK_*` WIP diagnostics from the source commit are excluded.
- CRT shaders and the later presentation redesign are excluded.
+27
View File
@@ -1685,6 +1685,29 @@ static void set_scissor_px(VkCommandBuffer cb, int x, int y, int w, int h) {
VkRect2D sc = { { x * s_scale, y * s_scale }, { (uint32_t)(w * s_scale), (uint32_t)(h * s_scale) } };
p_vkCmdSetScissor(cb, 0, 1, &sc);
}
/* A same-layout transition is elided by img_to(), while the render pass has
* no external dependency. Explicitly order a preceding pass's attachment
* writes before a following LOAD/blend pass on the same color image. */
static void color_self_barrier(VkCommandBuffer cb, VkImage img) {
VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = img;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
p_vkCmdPipelineBarrier(cb,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, NULL, 0, NULL, 1, &barrier);
}
static void begin_geo_pass(VkCommandBuffer cb) {
/* Explicit stencil ordering across one-shot submits: this pass both TESTS
* and WRITES the stencil (PSX mask bits) that a PRIOR submit's pass wrote.
@@ -1709,6 +1732,7 @@ static void begin_geo_pass(VkCommandBuffer cb) {
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
0, 0, NULL, 0, NULL, 1, &db);
color_self_barrier(cb, s_vram_img);
VkRenderPassBeginInfo rp = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp.renderPass = s_rpass; rp.framebuffer = s_fbo;
rp.renderArea.extent.width = VRAM_W * s_scale;
@@ -2235,6 +2259,7 @@ static void wide_pass_begin(VkCommandBuffer cb) {
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
0, 0, NULL, 0, NULL, 1, &db);
color_self_barrier(cb, s_wide_img[i]);
VkRenderPassBeginInfo rp = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp.renderPass = s_rpass; rp.framebuffer = s_wide_fb[i];
rp.renderArea.extent.width = (uint32_t)(s_wide_w * S);
@@ -2728,6 +2753,7 @@ static void vkb_wide_clear(int base_x, int y, int h, uint16_t color) {
if (y1 <= y0) return;
VkCommandBuffer cb = begin_oneshot();
img_to(cb, s_wide_img[i], &s_wide_layout[i], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
color_self_barrier(cb, s_wide_img[i]);
VkRenderPassBeginInfo rp = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp.renderPass = s_rpass; rp.framebuffer = s_wide_fb[i];
rp.renderArea.extent.width = (uint32_t)(s_wide_w * S);
@@ -2765,6 +2791,7 @@ static void vkb_wide_clear_margins(int base_x, int y, int h, uint16_t color, int
if (y1 <= y0 || margin * 2 >= W) return;
VkCommandBuffer cb = begin_oneshot();
img_to(cb, s_wide_img[i], &s_wide_layout[i], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
color_self_barrier(cb, s_wide_img[i]);
VkRenderPassBeginInfo rp = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp.renderPass = s_rpass; rp.framebuffer = s_wide_fb[i];
rp.renderArea.extent.width = (uint32_t)W;
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
from pathlib import Path
import re
source = (Path(__file__).parents[1] / "src" / "gpu_vk_renderer.c").read_text()
helper = re.search(
r"static void color_self_barrier\(.*?\n\}", source, flags=re.DOTALL
)
assert helper, "color_self_barrier not found"
body = helper.group(0)
for token in (
"VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL",
"VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT",
"VK_ACCESS_COLOR_ATTACHMENT_READ_BIT",
"VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT",
):
assert token in body, f"missing {token}"
assert source.count("color_self_barrier(cb, s_vram_img)") == 1
assert source.count("color_self_barrier(cb, s_wide_img[i])") == 3
print("Vulkan color self-barrier test passed")