Reading an acceleration structure through VK_EXT_descriptor_heap aborts with VK_ERROR_DEVICE_LOST on NVIDIA 610.43.02 — a brand-new-extension driver fault isolated in #7 (engine setup is correct and validation-clean; images/buffers through the same heap work, and both traceRayEXT and inline rayQuery fault identically on the AS read). An acceleration structure can equally be reached by its device address via OpConvertUToAccelerationStructureKHR, which reads no descriptor and so never touches the faulting heap path. glslang has no GLSL spelling for that conversion, so VulkanShader rewrites the compiled SPIR-V at module-load time: every `OpLoad %accelStruct <heap-ptr>` becomes a load of the TLAS device address from a synthesized push-constant block followed by the convert. RTPass pushes the active frame's TLAS address into that push constant. User GLSL and example code are unchanged; acceleration structures still bind into the heap normally. The workaround is gated on Device::workaroundDescriptorHeapAS (true only on the NVIDIA proprietary driver) and confined to one fenced block in Crafter.Graphics-ShaderVulkan.cppm plus the RTPass push and the shaderInt64 feature toggle — delete those once a fixed NVIDIA driver ships and the heap AS read becomes the direct path again. Verified: VulkanTriangle ray-traces correctly on native NVIDIA (RTX 4090), validation-layer-clean, no device loss. The SPIR-V rewrite was independently validated with spirv-val on both the VulkanTriangle and Sponza raygen modules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
275 lines
11 KiB
C++
275 lines
11 KiB
C++
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
|
#include "vulkan/vulkan.h"
|
|
#endif
|
|
#include <cassert>
|
|
|
|
import Crafter.Graphics;
|
|
using namespace Crafter;
|
|
import std;
|
|
import Crafter.Event;
|
|
import Crafter.Math;
|
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
|
int main() {
|
|
Device::Initialize();
|
|
Window window(1280, 720, "HelloVulkan");
|
|
VkCommandBuffer cmd = window.StartInit();
|
|
DescriptorHeapVulkan descriptorHeap;
|
|
descriptorHeap.Initialize(1,1,0);
|
|
|
|
VkSpecializationMapEntry entry = {
|
|
.constantID = 0,
|
|
.offset = 0,
|
|
.size = sizeof(uint16_t)
|
|
};
|
|
|
|
VkSpecializationInfo specilizationInfo = {
|
|
.mapEntryCount = 1,
|
|
.pMapEntries = &entry,
|
|
.dataSize = sizeof(uint16_t),
|
|
.pData = &descriptorHeap.bufferStartElement
|
|
};
|
|
|
|
std::array<VulkanShader, 3> shaders{{
|
|
{"raygen.spv", "main", VK_SHADER_STAGE_RAYGEN_BIT_KHR, &specilizationInfo},
|
|
{"miss.spv", "main", VK_SHADER_STAGE_MISS_BIT_KHR, nullptr},
|
|
{"closesthit.spv", "main", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, nullptr}
|
|
}};
|
|
|
|
ShaderBindingTableVulkan shaderTable;
|
|
shaderTable.Init(shaders);
|
|
|
|
std::array<VkRayTracingShaderGroupCreateInfoKHR, 1> raygenGroups {{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,
|
|
.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,
|
|
.generalShader = 0,
|
|
.closestHitShader = VK_SHADER_UNUSED_KHR,
|
|
.anyHitShader = VK_SHADER_UNUSED_KHR,
|
|
.intersectionShader = VK_SHADER_UNUSED_KHR,
|
|
},
|
|
}};
|
|
std::array<VkRayTracingShaderGroupCreateInfoKHR, 1> missGroups {{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,
|
|
.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,
|
|
.generalShader = 1,
|
|
.closestHitShader = VK_SHADER_UNUSED_KHR,
|
|
.anyHitShader = VK_SHADER_UNUSED_KHR,
|
|
.intersectionShader = VK_SHADER_UNUSED_KHR,
|
|
},
|
|
}};
|
|
std::array<VkRayTracingShaderGroupCreateInfoKHR, 1> hitGroups {{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,
|
|
.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR,
|
|
.generalShader = VK_SHADER_UNUSED_KHR,
|
|
.closestHitShader = 2,
|
|
.anyHitShader = VK_SHADER_UNUSED_KHR,
|
|
.intersectionShader = VK_SHADER_UNUSED_KHR,
|
|
},
|
|
}};
|
|
|
|
PipelineRTVulkan pipeline;
|
|
pipeline.Init(cmd, raygenGroups, missGroups, hitGroups, shaderTable);
|
|
|
|
Mesh triangleMesh;
|
|
std::array<Vector<float, 3, 3>, 3> verts {{{-150, -150, 100}, {0, 150, 100}, {150, -150, 100}}};
|
|
std::array<std::uint32_t, 3> index {{2,1,0}};
|
|
triangleMesh.Build(verts, index, cmd);
|
|
|
|
RenderingElement3D renderer = {
|
|
.instance = {
|
|
.instanceCustomIndex = 0,
|
|
.mask = 0xFF,
|
|
.instanceShaderBindingTableRecordOffset = 0,
|
|
.flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR,
|
|
.accelerationStructureReference = triangleMesh.blasAddr
|
|
}
|
|
};
|
|
|
|
RenderingElement3D::elements.emplace_back(&renderer);
|
|
|
|
MatrixRowMajor<float, 4, 3, 1> transform = MatrixRowMajor<float, 4, 3, 1>::Identity();
|
|
transform.Store(reinterpret_cast<float*>(renderer.instance.transform.matrix));
|
|
RenderingElement3D::BuildTLAS(cmd, 0);
|
|
RenderingElement3D::BuildTLAS(cmd, 1);
|
|
RenderingElement3D::BuildTLAS(cmd, 2);
|
|
|
|
window.FinishInit();
|
|
|
|
auto imgSlots = descriptorHeap.AllocateImageSlots(1);
|
|
auto bufSlots = descriptorHeap.AllocateBufferSlots(1);
|
|
|
|
VkDeviceAddressRangeKHR tlasRange0 = {
|
|
.address = RenderingElement3D::tlases[0].address,
|
|
};
|
|
|
|
VkDeviceAddressRangeKHR tlasRange1 = {
|
|
.address = RenderingElement3D::tlases[1].address,
|
|
};
|
|
|
|
VkDeviceAddressRangeKHR tlasRange2 = {
|
|
.address = RenderingElement3D::tlases[2].address,
|
|
};
|
|
|
|
VkImageDescriptorInfoEXT imageInfo0 = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
|
|
.pView = &window.imageViews[0],
|
|
.layout = VK_IMAGE_LAYOUT_GENERAL
|
|
};
|
|
|
|
VkImageDescriptorInfoEXT imageInfo1 = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
|
|
.pView = &window.imageViews[1],
|
|
.layout = VK_IMAGE_LAYOUT_GENERAL
|
|
};
|
|
|
|
VkImageDescriptorInfoEXT imageInfo2 = {
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
|
|
.pView = &window.imageViews[2],
|
|
.layout = VK_IMAGE_LAYOUT_GENERAL
|
|
};
|
|
|
|
VkResourceDescriptorInfoEXT resources[6] = {
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
|
|
.data = { .pAddressRange = &tlasRange0}
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
.data = { .pImage = &imageInfo0 }
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
|
|
.data = { .pAddressRange = &tlasRange1}
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
.data = { .pImage = &imageInfo1 }
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
|
|
.data = { .pAddressRange = &tlasRange2}
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
.data = { .pImage = &imageInfo2 }
|
|
},
|
|
};
|
|
|
|
VkHostAddressRangeEXT destinations[6] = {
|
|
{
|
|
.address = descriptorHeap.resourceHeap[0].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.bufferDescriptorSize
|
|
},
|
|
{
|
|
.address = descriptorHeap.resourceHeap[0].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.imageDescriptorSize
|
|
},
|
|
{
|
|
.address = descriptorHeap.resourceHeap[1].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.bufferDescriptorSize
|
|
},
|
|
{
|
|
.address = descriptorHeap.resourceHeap[1].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.imageDescriptorSize
|
|
},
|
|
{
|
|
.address = descriptorHeap.resourceHeap[2].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.bufferDescriptorSize
|
|
},
|
|
{
|
|
.address = descriptorHeap.resourceHeap[2].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
|
|
.size = Device::descriptorHeapProperties.imageDescriptorSize
|
|
},
|
|
};
|
|
|
|
Device::vkWriteResourceDescriptorsEXT(Device::device, 6, resources, destinations);
|
|
|
|
descriptorHeap.resourceHeap[0].FlushDevice();
|
|
descriptorHeap.resourceHeap[1].FlushDevice();
|
|
descriptorHeap.resourceHeap[2].FlushDevice();
|
|
|
|
window.descriptorHeap = &descriptorHeap;
|
|
RTPass rtPass(&pipeline);
|
|
window.passes.push_back(&rtPass);
|
|
|
|
// NOTE: reading the acceleration structure through VK_EXT_descriptor_heap
|
|
// aborts with VK_ERROR_DEVICE_LOST on NVIDIA 610.43.02 (a driver fault —
|
|
// see #7). The engine transparently works around it: on the NVIDIA driver
|
|
// VulkanShader rewrites the heap AS read into a TLAS-device-address +
|
|
// OpConvertUToAccelerationStructureKHR path and RTPass feeds the address in
|
|
// as push data. Nothing here (or in raygen.glsl) changes. See README.md
|
|
// ("Native status") and interfaces/Crafter.Graphics-ShaderVulkan.cppm.
|
|
window.Render();
|
|
window.StartSync();
|
|
}
|
|
#else
|
|
// DOM-mode port. Same scene (one triangle), software-emulated raytracing
|
|
// via compute. Shaders are read from .wgsl files shipped as static
|
|
// assets (see project.cpp). Renders barycentric colors via the
|
|
// hit/miss/raygen mega-switch in PipelineRTWebGPU.
|
|
int main() {
|
|
Device::Initialize();
|
|
static Window window(1280, 720, "HelloVulkan");
|
|
auto cmd = window.StartInit();
|
|
|
|
DescriptorHeapWebGPU heap;
|
|
heap.Initialize(/*images*/ 4, /*buffers*/ 4, /*samplers*/ 2);
|
|
|
|
std::array<WebGPUShader, 3> shaders {{
|
|
WebGPUShader(std::filesystem::path("raygen.wgsl"), "raygen_main", WebGPURTStage::Raygen),
|
|
WebGPUShader(std::filesystem::path("miss.wgsl"), "miss_main", WebGPURTStage::Miss),
|
|
WebGPUShader(std::filesystem::path("closesthit.wgsl"), "closesthit_main", WebGPURTStage::ClosestHit),
|
|
}};
|
|
ShaderBindingTableWebGPU sbt;
|
|
sbt.Init(shaders);
|
|
|
|
std::array<RTShaderGroup, 1> raygenGroups {{
|
|
{ .type = RTShaderGroupType::General, .generalShader = 0 },
|
|
}};
|
|
std::array<RTShaderGroup, 1> missGroups {{
|
|
{ .type = RTShaderGroupType::General, .generalShader = 1 },
|
|
}};
|
|
std::array<RTShaderGroup, 1> hitGroups {{
|
|
{ .type = RTShaderGroupType::TrianglesHitGroup, .closestHitShader = 2 },
|
|
}};
|
|
|
|
PipelineRTWebGPU pipeline;
|
|
pipeline.Init(cmd, raygenGroups, missGroups, hitGroups, sbt);
|
|
|
|
Mesh triangleMesh;
|
|
std::array<Vector<float, 3, 3>, 3> verts {{{-150, -150, 100}, {0, 150, 100}, {150, -150, 100}}};
|
|
std::array<std::uint32_t, 3> index {{2, 1, 0}};
|
|
triangleMesh.Build(verts, index, cmd);
|
|
|
|
static RenderingElement3D renderer;
|
|
renderer.instance.transform.matrix[0][0] = 1; renderer.instance.transform.matrix[0][1] = 0; renderer.instance.transform.matrix[0][2] = 0; renderer.instance.transform.matrix[0][3] = 0;
|
|
renderer.instance.transform.matrix[1][0] = 0; renderer.instance.transform.matrix[1][1] = 1; renderer.instance.transform.matrix[1][2] = 0; renderer.instance.transform.matrix[1][3] = 0;
|
|
renderer.instance.transform.matrix[2][0] = 0; renderer.instance.transform.matrix[2][1] = 0; renderer.instance.transform.matrix[2][2] = 1; renderer.instance.transform.matrix[2][3] = 0;
|
|
renderer.instance.instanceCustomIndex = 0;
|
|
renderer.instance.mask = 0xFF;
|
|
renderer.instance.instanceShaderBindingTableRecordOffset = 0;
|
|
renderer.instance.flags = kRTGeometryInstanceForceOpaque;
|
|
renderer.instance.accelerationStructureReference = triangleMesh.blasAddr;
|
|
RenderingElement3D::Add(&renderer);
|
|
RenderingElement3D::BuildTLAS(cmd, 0);
|
|
|
|
window.descriptorHeap = &heap;
|
|
window.FinishInit();
|
|
|
|
RTPass rtPass(&pipeline);
|
|
window.passes.push_back(&rtPass);
|
|
|
|
window.Render();
|
|
window.StartUpdate();
|
|
window.StartSync();
|
|
}
|
|
#endif
|