Merge pull request 'perf(sync): scope frame-loop barriers to swapchain image + real per-pass stages (#115)' (#137) from claude/issue-115 into master
This commit is contained in:
commit
82fd6916d9
6 changed files with 316 additions and 8 deletions
|
|
@ -815,7 +815,16 @@ void Window::Render() {
|
|||
: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
acquireBarrier.image = images[currentBuffer];
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &acquireBarrier);
|
||||
// Real per-pass stage union for this frame's passes — the swapchain image
|
||||
// is only ever written by compute/RT (and possibly transfer), never by the
|
||||
// whole pipeline, so the frame-edge barriers wait on exactly those stages
|
||||
// instead of ALL_COMMANDS. Derived once here and reused for the present
|
||||
// barrier below (passes don't change within a Render()).
|
||||
const VkPipelineStageFlags swapWriterStages = SwapchainStageUnion(passes);
|
||||
|
||||
// The acquire->GENERAL transition only needs to be visible before the first
|
||||
// pass writes the image, i.e. at the writer stages — not ALL_COMMANDS.
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, swapWriterStages, 0, 0, nullptr, 0, nullptr, 1, &acquireBarrier);
|
||||
|
||||
// Synthesise key-repeat events before listeners run, so the focused
|
||||
// widget's OnTextInput / OnKeyDown sees them in the same frame.
|
||||
|
|
@ -879,18 +888,28 @@ void Window::Render() {
|
|||
passes[i]->Record(drawCmdBuffers[currentBuffer], currentBuffer, *this);
|
||||
|
||||
if (i + 1 < passes.size()) {
|
||||
VkMemoryBarrier mb {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
};
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
// Make pass i's writes to the swapchain image visible to pass i+1.
|
||||
// The only resource the inter-pass hazard touches is that one image
|
||||
// (later passes overdraw earlier ones), so use an image memory
|
||||
// barrier scoped to its subresource instead of a queue-wide
|
||||
// VkMemoryBarrier — only this image's caches round-trip, exactly as
|
||||
// the intra-pass UI barrier already does. The stage masks come from
|
||||
// each pass's own SwapchainStage(), so a compute->compute edge only
|
||||
// serialises COMPUTE_SHADER while an RT pass on either side pulls in
|
||||
// RAY_TRACING_SHADER — never the ALL_COMMANDS full-pipeline stall.
|
||||
VkImageMemoryBarrier ib = BuildSwapchainInterPassBarrier(images[currentBuffer]);
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer],
|
||||
passes[i]->SwapchainStage(), passes[i + 1]->SwapchainStage(),
|
||||
0, 0, nullptr, 0, nullptr, 1, &ib);
|
||||
}
|
||||
}
|
||||
|
||||
presentBarrier.image = images[currentBuffer];
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
|
||||
// The ->PRESENT_SRC transition only needs to wait on the stages that
|
||||
// actually wrote the image (the per-pass union), not ALL_COMMANDS; present
|
||||
// itself is gated by the render-finished semaphore, so dst stays BOTTOM.
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], swapWriterStages, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
|
||||
|
||||
Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ export namespace Crafter {
|
|||
|
||||
RTPass(PipelineRTVulkan* p) : pipeline(p) {}
|
||||
|
||||
// An RT pass writes the swapchain image from the ray-tracing pipeline,
|
||||
// so the frame loop's barriers must wait on RAY_TRACING_SHADER — using
|
||||
// the compute stage here would under-synchronise and corrupt the image.
|
||||
VkPipelineStageFlags SwapchainStage() const override {
|
||||
return VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;
|
||||
}
|
||||
|
||||
void Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override {
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline);
|
||||
// NVIDIA descriptor-heap AS-read workaround (issue #15 / #7): feed
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ You should have received a copy of the GNU Lesser General Public
|
|||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
#include "vulkan/vulkan.h"
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
export module Crafter.Graphics:RenderPass;
|
||||
import std;
|
||||
import :GraphicsTypes;
|
||||
|
|
@ -23,8 +27,74 @@ import :GraphicsTypes;
|
|||
export namespace Crafter {
|
||||
struct Window;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
// Conservative union of every pipeline stage that can write the swapchain
|
||||
// storage image: a compute pass via COMPUTE_SHADER, a ray-tracing pass via
|
||||
// RAY_TRACING_SHADER, plus TRANSFER for any blit/copy writer. Used as the
|
||||
// RenderPass default (a pass that doesn't narrow its own stage) and as the
|
||||
// fallback when there are no passes to derive a real union from. Far tighter
|
||||
// than ALL_COMMANDS, but never under-synchronises a polymorphic pass.
|
||||
inline constexpr VkPipelineStageFlags kSwapchainWriterStages =
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
|
||||
| VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
||||
struct RenderPass {
|
||||
virtual void Record(GraphicsCommandBuffer cmd, std::uint32_t frameIdx, Window& window) = 0;
|
||||
virtual ~RenderPass() = default;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
// Pipeline stage at which this pass reads and writes the swapchain
|
||||
// storage image. The frame loop uses this to scope the inter-pass and
|
||||
// frame-edge barriers to the stages that actually touch the image
|
||||
// instead of ALL_COMMANDS. A subclass MUST narrow this to its real
|
||||
// stage (RTPass -> RAY_TRACING_SHADER, a compute pass -> COMPUTE_SHADER):
|
||||
// the default is the conservative writer union so an un-overridden pass
|
||||
// can never be under-synchronised, only over-synchronised.
|
||||
virtual VkPipelineStageFlags SwapchainStage() const { return kSwapchainWriterStages; }
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
};
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
// Union of the swapchain-access stages declared by `passes` — the "real"
|
||||
// per-pass stage union the frame-edge barriers (acquire dst / present src)
|
||||
// narrow to: an all-compute frame yields COMPUTE_SHADER only, a frame with
|
||||
// any RT pass folds in RAY_TRACING_SHADER, and so on. Falls back to the
|
||||
// conservative writer union when there are no passes (the image is still
|
||||
// transitioned, just never written), so the barrier is always well-formed.
|
||||
inline VkPipelineStageFlags SwapchainStageUnion(std::span<RenderPass* const> passes) {
|
||||
if (passes.empty()) return kSwapchainWriterStages;
|
||||
VkPipelineStageFlags stages = 0;
|
||||
for (RenderPass* pass : passes) stages |= pass->SwapchainStage();
|
||||
return stages;
|
||||
}
|
||||
|
||||
// The inter-pass swapchain barrier (replacing the old queue-wide
|
||||
// VkMemoryBarrier at the #115 location). Scoped to the swapchain image's
|
||||
// single colour subresource so only that image's shader caches round-trip —
|
||||
// unrelated buffers/images stay resident — exactly as the intra-pass UI
|
||||
// barrier already does. The image is bound as a storage image and stays in
|
||||
// VK_IMAGE_LAYOUT_GENERAL, so this is a pure memory dependency (no layout
|
||||
// transition). The execution scope (stage masks) is supplied per pass pair
|
||||
// by the caller via vkCmdPipelineBarrier.
|
||||
inline VkImageMemoryBarrier BuildSwapchainInterPassBarrier(VkImage image) {
|
||||
return {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,15 @@ export namespace Crafter {
|
|||
|
||||
void Record(GraphicsCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
// A UI pass draws into the swapchain image entirely from compute
|
||||
// shaders (see Dispatch), so the frame loop's inter-pass / frame-edge
|
||||
// barriers only need to synchronise the COMPUTE_SHADER stage for it.
|
||||
VkPipelineStageFlags SwapchainStage() const override {
|
||||
return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
||||
UIDispatchHeader FillHeader(std::uint32_t itemBufferSlot,
|
||||
std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f},
|
||||
|
|
|
|||
29
project.cpp
29
project.cpp
|
|
@ -473,6 +473,35 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
mbc.GetInterfacesAndImplementations(ifaces, mipBarrierImpls);
|
||||
cfg.tests.push_back(std::move(mipBarrierTest));
|
||||
|
||||
// Issue #115: the frame loop's inter-pass and acquire/present barriers
|
||||
// no longer use ALL_COMMANDS / a queue-wide VkMemoryBarrier. The stage
|
||||
// masks are derived per pass via RenderPass::SwapchainStage() (compute
|
||||
// vs ray-tracing) and unioned across the frame by SwapchainStageUnion,
|
||||
// and the inter-pass dependency is scoped to the swapchain image by
|
||||
// BuildSwapchainInterPassBarrier — all pure CPU logic over the pass
|
||||
// list, so this test drives them directly with no GPU at runtime.
|
||||
Test swapBarrierTest;
|
||||
Configuration& sbc = swapBarrierTest.config;
|
||||
sbc.path = cfg.path;
|
||||
sbc.name = "SwapchainBarrierScope";
|
||||
sbc.outputName = "SwapchainBarrierScope";
|
||||
sbc.type = ConfigurationType::Executable;
|
||||
sbc.target = cfg.target;
|
||||
sbc.march = cfg.march;
|
||||
sbc.mtune = cfg.mtune;
|
||||
sbc.debug = cfg.debug;
|
||||
sbc.sysroot = cfg.sysroot;
|
||||
sbc.dependencies = cfg.dependencies;
|
||||
sbc.externalDependencies = cfg.externalDependencies;
|
||||
sbc.compileFlags = cfg.compileFlags;
|
||||
sbc.linkFlags = cfg.linkFlags;
|
||||
sbc.defines = cfg.defines;
|
||||
sbc.cFiles = cfg.cFiles;
|
||||
std::vector<fs::path> swapBarrierImpls(impls.begin(), impls.end());
|
||||
swapBarrierImpls.emplace_back("tests/SwapchainBarrierScope/main");
|
||||
sbc.GetInterfacesAndImplementations(ifaces, swapBarrierImpls);
|
||||
cfg.tests.push_back(std::move(swapBarrierTest));
|
||||
|
||||
// Issue #47: the fused UI uber-kernel (shaders/ui-fused.comp.glsl) and
|
||||
// its C++ push-constant mirror UIFusedHeader. Compiles the real shader
|
||||
// with glslang, validates with spirv-val, and pins the push-constant
|
||||
|
|
|
|||
174
tests/SwapchainBarrierScope/main.cpp
Normal file
174
tests/SwapchainBarrierScope/main.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// Issue #115: the frame loop's inter-pass and frame-edge (acquire/present)
|
||||
// barriers used to set BOTH stage masks to VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
|
||||
// and the inter-pass dependency to a queue-wide VkMemoryBarrier — flushing
|
||||
// every cache and fully serialising against the whole pipeline every frame,
|
||||
// when all the next pass needs is the swapchain image the previous one wrote.
|
||||
//
|
||||
// The fix scopes the inter-pass dependency to the swapchain image's single
|
||||
// colour subresource (BuildSwapchainInterPassBarrier) and narrows the stage
|
||||
// masks to the stages that actually touch that image, derived per pass via the
|
||||
// polymorphic RenderPass::SwapchainStage() and unioned across the frame by
|
||||
// SwapchainStageUnion. The load-bearing correctness constraint is that this
|
||||
// derivation is per-pass: a compute pass writes via COMPUTE_SHADER, a ray-
|
||||
// tracing pass via RAY_TRACING_SHADER, so a hardcoded compute mask would
|
||||
// UNDER-synchronise an RT pass and corrupt the image. All three helpers are
|
||||
// pure CPU logic over the pass list, so this test drives them directly with no
|
||||
// GPU device at runtime — mirroring MipChainBarrierBatch / UploadStrategy.
|
||||
|
||||
#include <cstdlib>
|
||||
#include "vulkan/vulkan.h"
|
||||
|
||||
import Crafter.Graphics;
|
||||
import std;
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
// A pass that never overrides SwapchainStage(): stands in for any future pass
|
||||
// that forgets to narrow its stage, and must fall back to the conservative
|
||||
// writer union rather than silently picking one stage (which could under-sync).
|
||||
struct DefaultPass : RenderPass {
|
||||
void Record(GraphicsCommandBuffer, std::uint32_t, Window&) override {}
|
||||
};
|
||||
|
||||
VkImage SentinelImage() {
|
||||
return reinterpret_cast<VkImage>(static_cast<std::uintptr_t>(0xC0FFEE));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
// ─── per-pass SwapchainStage() polymorphism ─────────────────────────────
|
||||
// The whole reason the fix is "moderate, not trivial": these two stages
|
||||
// MUST differ, or scoping an RT pass's barrier to the compute stage would
|
||||
// under-synchronise it. Pin that they are distinct and each correct.
|
||||
Check(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT != VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
|
||||
"compute and ray-tracing write stages are distinct (the under-sync trap)");
|
||||
|
||||
UIRenderer ui; // real compute UI pass
|
||||
RTPass rt(nullptr); // real RT pass (SwapchainStage doesn't touch the pipeline)
|
||||
DefaultPass def; // un-overridden fallback
|
||||
|
||||
// Access through the base pointer — the frame loop only ever sees RenderPass*.
|
||||
RenderPass* uiBase = &ui;
|
||||
RenderPass* rtBase = &rt;
|
||||
RenderPass* defBase = &def;
|
||||
|
||||
Check(uiBase->SwapchainStage() == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
"UIRenderer reports the COMPUTE_SHADER stage");
|
||||
Check(rtBase->SwapchainStage() == VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
|
||||
"RTPass reports the RAY_TRACING_SHADER stage");
|
||||
Check(defBase->SwapchainStage() == kSwapchainWriterStages,
|
||||
"an un-overridden pass falls back to the conservative writer union");
|
||||
|
||||
// ─── the conservative writer union constant ─────────────────────────────
|
||||
constexpr VkPipelineStageFlags expectedUnion =
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
|
||||
| VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
Check(kSwapchainWriterStages == expectedUnion,
|
||||
"writer union = COMPUTE_SHADER | RAY_TRACING_SHADER | TRANSFER");
|
||||
// The point of the whole change: even the conservative fallback is far
|
||||
// tighter than the ALL_COMMANDS mask it replaces.
|
||||
Check(kSwapchainWriterStages != VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
"writer union is narrower than ALL_COMMANDS");
|
||||
|
||||
// ─── SwapchainStageUnion over the frame's pass list ─────────────────────
|
||||
// Empty frame: no writers to derive from, so fall back to the conservative
|
||||
// union (the image is still transitioned, just never written).
|
||||
Check(SwapchainStageUnion(std::span<RenderPass* const>{}) == kSwapchainWriterStages,
|
||||
"empty pass list falls back to the conservative writer union");
|
||||
|
||||
// All-compute frame: the union narrows to COMPUTE_SHADER ONLY — this is the
|
||||
// real perf win, no RAY_TRACING / TRANSFER / ALL_COMMANDS dragged in.
|
||||
{
|
||||
std::array<RenderPass*, 2> computeOnly = {uiBase, uiBase};
|
||||
VkPipelineStageFlags u = SwapchainStageUnion(computeOnly);
|
||||
Check(u == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
"all-compute frame narrows the union to COMPUTE_SHADER only");
|
||||
Check((u & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) == 0,
|
||||
"all-compute frame does NOT pull in RAY_TRACING_SHADER");
|
||||
}
|
||||
|
||||
// RT-only frame: narrows to RAY_TRACING_SHADER only.
|
||||
{
|
||||
std::array<RenderPass*, 1> rtOnly = {rtBase};
|
||||
Check(SwapchainStageUnion(rtOnly) == VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
|
||||
"RT-only frame narrows the union to RAY_TRACING_SHADER only");
|
||||
}
|
||||
|
||||
// Mixed frame: the union folds in BOTH stages — never under-syncs the RT
|
||||
// pass, never over-syncs to ALL_COMMANDS.
|
||||
{
|
||||
std::array<RenderPass*, 2> mixed = {uiBase, rtBase};
|
||||
VkPipelineStageFlags u = SwapchainStageUnion(mixed);
|
||||
Check(u == (VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR),
|
||||
"mixed compute+RT frame unions COMPUTE_SHADER | RAY_TRACING_SHADER");
|
||||
Check((u & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) != 0,
|
||||
"mixed frame keeps RAY_TRACING_SHADER (no RT under-sync)");
|
||||
}
|
||||
|
||||
// A frame containing an un-overridden pass conservatively widens to the
|
||||
// full writer union — safe by construction.
|
||||
{
|
||||
std::array<RenderPass*, 2> withDefault = {uiBase, defBase};
|
||||
Check(SwapchainStageUnion(withDefault) == kSwapchainWriterStages,
|
||||
"a frame with an un-overridden pass widens to the conservative union");
|
||||
}
|
||||
|
||||
// ─── BuildSwapchainInterPassBarrier ─────────────────────────────────────
|
||||
// Replaces the queue-wide VkMemoryBarrier: an image memory barrier scoped
|
||||
// to the swapchain image's single colour subresource, GENERAL->GENERAL (no
|
||||
// layout change — the storage image stays bound), matching the intra-pass
|
||||
// UI barrier's access scope.
|
||||
{
|
||||
VkImageMemoryBarrier b = BuildSwapchainInterPassBarrier(SentinelImage());
|
||||
Check(b.sType == VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, "inter-pass barrier sType is IMAGE_MEMORY_BARRIER");
|
||||
Check(b.image == SentinelImage(), "inter-pass barrier carries the swapchain image handle");
|
||||
Check(b.oldLayout == VK_IMAGE_LAYOUT_GENERAL && b.newLayout == VK_IMAGE_LAYOUT_GENERAL,
|
||||
"inter-pass barrier is GENERAL->GENERAL (pure memory dependency, no transition)");
|
||||
Check(b.srcAccessMask == VK_ACCESS_SHADER_WRITE_BIT,
|
||||
"inter-pass barrier src access is shader write");
|
||||
Check(b.dstAccessMask == (VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT),
|
||||
"inter-pass barrier dst access is shader read|write");
|
||||
Check(b.srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED && b.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED,
|
||||
"inter-pass barrier is not a queue-family transfer");
|
||||
Check(b.subresourceRange.aspectMask == VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
"inter-pass barrier covers the colour aspect");
|
||||
Check(b.subresourceRange.levelCount == 1 && b.subresourceRange.layerCount == 1,
|
||||
"inter-pass barrier is scoped to the single swapchain subresource");
|
||||
}
|
||||
|
||||
if (failures != 0) {
|
||||
std::println("{} check(s) failed", failures);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::println("all checks passed");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue