Crafter.Graphics/interfaces/Crafter.Graphics-RTPass.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

143 lines
6.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
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:RTPass;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :RenderPass;
import :Window;
import :Device;
import :PipelineRTVulkan;
import :RenderingElement3D;
export namespace Crafter {
struct RTPass : RenderPass {
PipelineRTVulkan* pipeline;
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
// the active frame's TLAS device address into the push-constant
// block that VulkanShader synthesizes, so the rewritten raygen can
// reach the acceleration structure by address instead of through
// the faulting heap descriptor. Inert on every other driver.
if (Device::workaroundDescriptorHeapAS && pipeline->workaroundNeedsTlas) {
VkDeviceAddress tlasAddr = RenderingElement3D::tlases[frameIdx].address;
VkPushDataInfoEXT pushInfo {
.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT,
// Where the rewritten raygen reads the TLAS address: 0 when
// VulkanShader synthesized a fresh block, or the offset of
// the member it appended to the shader's existing block.
// Tracked per-pipeline (copied from the shader table) so a
// later-loaded shader can't clobber it.
.offset = pipeline->workaroundTlasPushOffset,
.data = { .address = &tlasAddr, .size = sizeof(tlasAddr) },
};
Device::vkCmdPushDataEXT(cmd, &pushInfo);
}
Device::vkCmdTraceRaysKHR(cmd,
&pipeline->raygenRegion,
&pipeline->missRegion,
&pipeline->hitRegion,
&pipeline->callableRegion,
window.width, window.height, 1);
}
};
}
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :RenderPass;
import :Window;
import :WebGPU;
import :PipelineRTWebGPU;
import :RenderingElement3D;
export namespace Crafter {
// DOM-mode RT pass — dispatches the megakernel pipeline at frame Record
// time. Picks up the current TLAS for the frame and the application's
// raygen-side push data (typically empty in v1; pass via window.passes
// wiring if needed later).
struct RTPass : RenderPass {
PipelineRTWebGPU* pipeline;
// Optional per-dispatch push data forwarded after the standard
// RTDispatchHeader. Null means "no extra data".
const void* pushPtr = nullptr;
std::uint32_t pushBytes = 0;
// Resolved WebGPU resource handles for each user binding the
// pipeline was loaded with, in declaration order. The example
// owns the storage (typically a small std::array of u32). Null /
// 0 means "no user bindings".
const void* handlesPtr = nullptr;
std::uint32_t handlesCount = 0;
// Wavefront bounce budget: number of (TRACE; SHADE) iterations.
// 1 = primary rays only; 2 = primary + one continuation/shadow
// bounce; etc. The library unrolls GENERATE; (PREP; TRACE; SHADE)
// ×maxDepth; RESOLVE.
std::uint32_t maxDepth = 1;
// Destination storage-texture handle for an HDR-output pipeline
// (one Init'd with a non-default hdrOutputFormat). RESOLVE writes
// the linear accumulator here instead of the canvas; the app runs
// its own composite→swapchain pass afterwards. Ignored (0) for the
// default canvas path.
std::uint32_t outTexHandle = 0;
// Per-bounce ray budget as a multiple of the pixel count. The
// wavefront ray/hit/payload buffers hold raysPerPixel·W·H rays, so
// a closest-hit may emit up to raysPerPixel rays per pixel within
// one bounce (e.g. one shadow ray per light — rtAccumulate is
// atomic, issue #30) before rtEmitRay starts dropping. Memory
// scales linearly; keep at 1 for single-ray-per-pixel pipelines.
std::uint32_t raysPerPixel = 1;
RTPass(PipelineRTWebGPU* p) : pipeline(p) {}
void Record(WebGPUCommandEncoderRef /*cmd*/, std::uint32_t frameIdx, Window& window) override {
const std::uint32_t gx = (window.width + 7u) / 8u;
const std::uint32_t gy = (window.height + 7u) / 8u;
auto& tlas = RenderingElement3D::tlases[frameIdx];
WebGPU::wgpuDispatchRT(
pipeline->pipelineHandle,
pushPtr,
static_cast<std::int32_t>(pushBytes),
tlas.buffer.handle,
static_cast<std::int32_t>(tlas.builtInstanceCount),
static_cast<std::int32_t>(gx),
static_cast<std::int32_t>(gy),
handlesPtr,
static_cast<std::int32_t>(handlesCount),
static_cast<std::int32_t>(maxDepth),
outTexHandle,
static_cast<std::int32_t>(raysPerPixel));
}
};
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM