diff --git a/docs/internal/upstream/martin-pr16-vulkan-color-pass-barrier.md b/docs/internal/upstream/martin-pr16-vulkan-color-pass-barrier.md new file mode 100644 index 00000000..054603ff --- /dev/null +++ b/docs/internal/upstream/martin-pr16-vulkan-color-pass-barrier.md @@ -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 `` | +| Source co-author trailer | Claude Fable 5 `` | +| 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. diff --git a/runtime/src/gpu_vk_renderer.c b/runtime/src/gpu_vk_renderer.c index e56fc61f..9a33588b 100644 --- a/runtime/src/gpu_vk_renderer.c +++ b/runtime/src/gpu_vk_renderer.c @@ -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; diff --git a/runtime/tests/test_vk_color_self_barrier.py b/runtime/tests/test_vk_color_self_barrier.py new file mode 100644 index 00000000..dfdb5a4c --- /dev/null +++ b/runtime/tests/test_vk_color_self_barrier.py @@ -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")