This commit is contained in:
Jorijn van der Graaf 2026-05-05 23:49:29 +02:00
commit b3db40ebec
6 changed files with 212 additions and 64 deletions

View file

@ -123,6 +123,7 @@ export namespace Crafter {
inline static VkSwapchainKHR swapchain = VK_NULL_HANDLE;
inline static PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
inline static PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
inline static PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
inline static PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
inline static PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
inline static PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;

View file

@ -28,18 +28,58 @@ import :Window;
export namespace Crafter {
struct TlasWithBuffer {
VkDeviceAddress address;
VkDeviceAddress address = 0;
VulkanBuffer<char, false> buffer;
VkAccelerationStructureKHR accelerationStructure;
VkAccelerationStructureKHR accelerationStructure = VK_NULL_HANDLE;
VulkanBuffer<VkAccelerationStructureInstanceKHR, true> instanceBuffer;
VulkanBuffer<char, false> scratchBuffer;
// Parallel to instanceBuffer, indexed by TLAS instance ID. Filled
// from each element's userMetadata during BuildTLAS. Consumers
// (e.g. ray-query collision) bind this in the descriptor heap and
// look up via rayQueryGetIntersectionInstanceIdEXT to recover
// application-side per-instance data without touching the
// Vulkan-mandated instanceCustomIndex (which renderers may already
// use for their own encoding).
VulkanBuffer<std::uint32_t, true> metadataBuffer;
// Last instance count this TLAS was built (not refit) for. When
// elements.size() matches this, BuildTLAS does an in-place refit
// (UPDATE mode) which is dramatically cheaper than a full rebuild
// — refit walks the existing BVH and updates AABBs, while rebuild
// reconstructs the topology from scratch. A change in count forces
// a fresh rebuild because the AS is sized for that primitive count.
std::uint32_t builtInstanceCount = 0;
};
class RenderingElement3D {
public:
VkAccelerationStructureInstanceKHR instance;
// Position in `elements`, maintained by Add/Remove for O(1) swap-and-pop.
// Sentinel value = not currently registered.
std::uint32_t indexInElements = std::numeric_limits<std::uint32_t>::max();
// Application-defined per-instance tag, copied verbatim into
// tlases[*].metadataBuffer at this element's TLAS instance ID
// every BuildTLAS. Crafter doesn't interpret it.
std::uint32_t userMetadata = 0;
// When true, BuildTLAS skips copying instance.transform into the
// TLAS instance buffer — the application's compute shader writes
// the transform field directly into instanceBuffer at this
// element's TLAS instance ID. Other instance fields (mask,
// customIndex, SBT offset, BLAS reference) are still copied from
// the CPU instance struct.
//
// Used to take per-frame transform updates off the CPU for bodies
// whose transforms derive from GPU-side state (physics nodes that
// already live on the GPU).
bool transformOwnedByGpu = false;
static std::vector<RenderingElement3D*> elements;
inline static TlasWithBuffer tlases[Window::numFrames];
static void BuildTLAS(VkCommandBuffer cmd, std::uint32_t index);
// Register / unregister with `elements`. Use these instead of touching
// the vector directly: linear find+erase is O(n) and pathological at
// the body counts physics targets (millions of braces).
static void Add(RenderingElement3D* e);
static void Remove(RenderingElement3D* e);
};
}

View file

@ -247,6 +247,14 @@ export namespace Crafter {
std::uint16_t FontAtlasImageSlot() const noexcept { return fontAtlasImageSlot_; }
std::uint16_t FontAtlasSamplerSlot() const noexcept { return fontAtlasSamplerSlot_; }
// Heap slot whose descriptor in each per-frame heap points at that
// frame's swapchain image. Other passes (e.g. a ray-tracing pass
// that wants to render the world directly into the swapchain) can
// write to the same image by referencing this slot. Order in
// window.passes controls compositing — push such passes BEFORE
// the UI pass so UI overlays render on top.
std::uint16_t OutImageSlot() const noexcept { return outImageSlot_; }
private:
Window* window_ = nullptr;
DescriptorHeapVulkan* heap_ = nullptr;