Crafter.Graphics/interfaces/Crafter.Graphics-RenderPass.cppm
catbot 9414863cff perf(sync): scope frame-loop barriers to swapchain image + real per-pass stages (#115)
The inter-pass and acquire/present barriers in the frame loop set both
stage masks to ALL_COMMANDS, and the inter-pass dependency used a
queue-wide VkMemoryBarrier — fully serialising against every pipeline
stage and flushing all caches every frame, when all the next pass needs
is the swapchain image the previous one wrote.

Replace the inter-pass global VkMemoryBarrier with an image memory
barrier scoped to the swapchain image's single colour subresource (as
the intra-pass UI barrier already does), and derive the barrier stage
masks per pass: RenderPass::SwapchainStage() is overridden by UIRenderer
(COMPUTE_SHADER) and RTPass (RAY_TRACING_SHADER), so a compute->compute
edge only serialises COMPUTE while an RT pass pulls in RAY_TRACING — the
acquire/present frame-edge masks use the real union of the frame's
passes (SwapchainStageUnion). The base default and the empty-passes
fallback are the conservative COMPUTE | RAY_TRACING | TRANSFER union, so
a polymorphic or un-overridden pass can only be over- not
under-synchronised.

Adds SwapchainBarrierScope (pure CPU) pinning the per-pass derivation,
the union narrowing, and the inter-pass barrier scope; FrameLoopSync
already drives the real GPU frame loop with validation enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:44:33 +00:00

100 lines
4.8 KiB
C++

/*
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
*/
module;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
export module Crafter.Graphics:RenderPass;
import std;
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
}