The NVIDIA descriptor-heap AS-read workaround (#15) rewrote heap acceleration-structure reads into a load of the TLAS device address from a push-constant block. It always *synthesized a new* push-constant block, so any ray-tracing shader that already declared one ended up with two — which SPIR-V forbids ("at most one push constant block statically used per entry point"), and vkCreateShaderModule's spirv-val check rejected: Entry point id '4' uses more than one PushConstant interface. WorkaroundNvidiaAS::Patch now detects an existing PushConstant variable and, when present, appends a single ulong member (the TLAS address) to that block instead of adding a second one, reading the address through the shader's own push-constant variable. The append offset is the end of the user's block, computed from the members' explicit Offset/ArrayStride/ MatrixStride decorations (correct under both scalar and std140 layout) and rounded up to 8. Shaders with no push constant of their own keep getting a freshly synthesized single-member block at offset 0, exactly as before. That offset is published via Device::workaroundTlasPushOffset and RTPass feeds it to vkCmdPushDataEXT so the address lands where the rewritten load reads it (0 for the synthesized case, preserving prior behaviour). Verified on the affected driver (NVIDIA 610.43.02, RTX 4090): VulkanTriangle ray-traces correctly and validation-clean both with and without a user-declared raygen push constant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
4.9 KiB
C++
119 lines
4.9 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: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) {}
|
||
|
||
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) {
|
||
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.
|
||
.offset = Device::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;
|
||
|
||
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));
|
||
}
|
||
};
|
||
}
|
||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|