2026-07-22 18:09:06 +02:00
|
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2025-11-25 19:54:03 +01:00
|
|
|
|
|
2026-05-01 23:35:37 +02:00
|
|
|
|
module;
|
2026-05-18 02:07:48 +02:00
|
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-01 23:35:37 +02:00
|
|
|
|
#include "vulkan/vulkan.h"
|
2026-05-18 02:07:48 +02:00
|
|
|
|
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-01 23:35:37 +02:00
|
|
|
|
export module Crafter.Graphics:RTPass;
|
2026-05-18 02:07:48 +02:00
|
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2025-11-25 19:54:03 +01:00
|
|
|
|
import std;
|
2026-05-01 23:35:37 +02:00
|
|
|
|
import :RenderPass;
|
|
|
|
|
|
import :Window;
|
|
|
|
|
|
import :Device;
|
|
|
|
|
|
import :PipelineRTVulkan;
|
2026-06-03 01:59:54 +00:00
|
|
|
|
import :RenderingElement3D;
|
2025-11-25 19:54:03 +01:00
|
|
|
|
|
|
|
|
|
|
export namespace Crafter {
|
2026-05-01 23:35:37 +02:00
|
|
|
|
struct RTPass : RenderPass {
|
|
|
|
|
|
PipelineRTVulkan* pipeline;
|
feat: windows on their own clock, deferred deletion for every handle owner, staged re-uploads, and RTPass push data
Window
- updateInterval runs Update on the window's own clock while the
compositor sends no frame callbacks (hidden, minimized): the Wayland loop
is a poll() loop, and the Win32 loop sleeps until the next update. Both
read the interval each pass.
- SetPresentMode (FIFO, Mailbox, Immediate). A mode the surface lacks, or
whose swapchain has other than numFrames images (Mesa's Mailbox has four),
falls back to FIFO until the mode is set again.
- AddPollFd / RemovePollFd wake the Wayland loop on the app's descriptors.
- The swapchain has TRANSFER_DST where the surface allows it.
- CaptureNextFrame replaces SaveFrame: it records the copy in the frame,
before the present, where SaveFrame read an image already presented. A
frame unwound past before the write still frees the readback buffer.
- Inter-pass barriers on the swapchain image carry the access masks of the
stages the passes use.
Deferred deletion (issue #101)
- VulkanBuffer, Mesh, VulkanShader, ComputeShader and PipelineRTVulkan hand
their handles to the deletion queue from their destructors and moves, as
a frame in flight may still use them; a static owner goes after the
driver has torn down. VulkanShader and PipelineRTVulkan move and can't be
copied; re-Load and re-Init queue the old pipeline.
- The queue and its mutex are never destroyed, and the queue takes handles
from any thread; Device::frameCounter is atomic. Both the queue and the
staging rings count frames as one window renders them (documented).
- DrainDeletions also lets every buffer's staging ring start over: without
a Window nothing else reclaims either, so an app without one calls it
after waiting for its work.
Meshes and buffers
- A Mesh rebuild queues the old acceleration structure and builds into the
same storage after a barrier, so the BLAS address stays put when it fits.
The refit and rebuild barriers cover compute ray queries too.
- RTBuildOptions::noDuplicateAnyHit. Mesh::geometryFlags replaces opaque,
and the refit and rebuild fallbacks keep the flags.
- A re-upload into a buffer a frame in flight may read (UploadDeviceLocal
of the same size, UploadDeviceLocalRange) copies on the GPU, after those
reads, through a per-frame staging ring; on ReBAR it used to write the
buffer in place. Empty uploads record nothing.
- A compressed Mesh::Build that keeps its buffers decompresses into them
after the reads of frames in flight, and the hit shaders and ray queries
wait for the decompress as well as the BLAS build. Its vertex buffer
takes a ranged refit's copies.
- Mesh move-assigns (queueing the BLAS it replaces), so it can be replaced
in place or erased from a vector.
- VulkanBuffer::Create asks for a device address only for buffers with
SHADER_DEVICE_ADDRESS usage, as vkGetBufferDeviceAddress requires; the
others have address 0. UIRenderer::RegisterBuffer refuses those.
- Build inputs are synchronized as shader reads.
Other
- RTPass pushes the app's own push data and traces at an explicit size.
- DescriptorHeapVulkan hands freed slot ranges out again (they leaked), and
keeps its free lists sorted.
- ImageVulkan's mip chain clamps each level to 1 texel: a non-square
image's shorter side went to 0.
- VulkanShader throws on a missing file instead of failing in bad_alloc.
- CRAFTER_GRAPHICS_DEVICE picks the GPU by its number among the distinct
GPUs Vulkan lists; anything but such a number is an error.
- The debug messenger prints info messages only with
CRAFTER_GRAPHICS_VALIDATION set.
- Windows: the WinRT gamepad code moved to a C file (mingw-w64's WinRT
headers don't compile as C++), -lcombase dropped, a reference leak fixed.
- project.cpp honours --local for Crafter.Event, Math and Asset, as Asset
does for Math, and builds every test through one helper, projectArgs
included.
- Tests: DebugMessengerSeverity, DeletionQueueThreads, DeviceSelection,
HeapSlotReuse, MeshLifetime, MipChainNonSquare, PipelineLifetime,
RTPassPushData, SwapchainTransferDst, UploadInFlight, VulkanBufferAddress,
VulkanShaderLifetime, WindowUpdateClock; others extended.
Breaking: SaveFrame is CaptureNextFrame; Mesh::opaque is geometryFlags; a
buffer without SHADER_DEVICE_ADDRESS usage has no address; frameCounter is
std::atomic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:07:54 +02:00
|
|
|
|
// The application's push data, pushed at offset 0 before every trace
|
|
|
|
|
|
// (e.g. a camera), as the DOM backend's RTPass takes it: it starts the
|
|
|
|
|
|
// shaders' push block. The pointed-to bytes are read at Record time,
|
|
|
|
|
|
// so updating them before Render() is enough. Null means none.
|
|
|
|
|
|
const void* pushPtr = nullptr;
|
|
|
|
|
|
std::uint32_t pushBytes = 0;
|
|
|
|
|
|
// The trace size; 0 means the window's current size, as before. Set
|
|
|
|
|
|
// it to trace into an image of its own size rather than the
|
|
|
|
|
|
// swapchain's.
|
|
|
|
|
|
std::uint32_t width = 0;
|
|
|
|
|
|
std::uint32_t height = 0;
|
2026-05-01 23:35:37 +02:00
|
|
|
|
|
|
|
|
|
|
RTPass(PipelineRTVulkan* p) : pipeline(p) {}
|
2026-03-09 20:10:19 +01:00
|
|
|
|
|
2026-06-17 19:44:33 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-03 01:59:54 +00:00
|
|
|
|
void Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override {
|
2026-05-01 23:35:37 +02:00
|
|
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline);
|
feat: windows on their own clock, deferred deletion for every handle owner, staged re-uploads, and RTPass push data
Window
- updateInterval runs Update on the window's own clock while the
compositor sends no frame callbacks (hidden, minimized): the Wayland loop
is a poll() loop, and the Win32 loop sleeps until the next update. Both
read the interval each pass.
- SetPresentMode (FIFO, Mailbox, Immediate). A mode the surface lacks, or
whose swapchain has other than numFrames images (Mesa's Mailbox has four),
falls back to FIFO until the mode is set again.
- AddPollFd / RemovePollFd wake the Wayland loop on the app's descriptors.
- The swapchain has TRANSFER_DST where the surface allows it.
- CaptureNextFrame replaces SaveFrame: it records the copy in the frame,
before the present, where SaveFrame read an image already presented. A
frame unwound past before the write still frees the readback buffer.
- Inter-pass barriers on the swapchain image carry the access masks of the
stages the passes use.
Deferred deletion (issue #101)
- VulkanBuffer, Mesh, VulkanShader, ComputeShader and PipelineRTVulkan hand
their handles to the deletion queue from their destructors and moves, as
a frame in flight may still use them; a static owner goes after the
driver has torn down. VulkanShader and PipelineRTVulkan move and can't be
copied; re-Load and re-Init queue the old pipeline.
- The queue and its mutex are never destroyed, and the queue takes handles
from any thread; Device::frameCounter is atomic. Both the queue and the
staging rings count frames as one window renders them (documented).
- DrainDeletions also lets every buffer's staging ring start over: without
a Window nothing else reclaims either, so an app without one calls it
after waiting for its work.
Meshes and buffers
- A Mesh rebuild queues the old acceleration structure and builds into the
same storage after a barrier, so the BLAS address stays put when it fits.
The refit and rebuild barriers cover compute ray queries too.
- RTBuildOptions::noDuplicateAnyHit. Mesh::geometryFlags replaces opaque,
and the refit and rebuild fallbacks keep the flags.
- A re-upload into a buffer a frame in flight may read (UploadDeviceLocal
of the same size, UploadDeviceLocalRange) copies on the GPU, after those
reads, through a per-frame staging ring; on ReBAR it used to write the
buffer in place. Empty uploads record nothing.
- A compressed Mesh::Build that keeps its buffers decompresses into them
after the reads of frames in flight, and the hit shaders and ray queries
wait for the decompress as well as the BLAS build. Its vertex buffer
takes a ranged refit's copies.
- Mesh move-assigns (queueing the BLAS it replaces), so it can be replaced
in place or erased from a vector.
- VulkanBuffer::Create asks for a device address only for buffers with
SHADER_DEVICE_ADDRESS usage, as vkGetBufferDeviceAddress requires; the
others have address 0. UIRenderer::RegisterBuffer refuses those.
- Build inputs are synchronized as shader reads.
Other
- RTPass pushes the app's own push data and traces at an explicit size.
- DescriptorHeapVulkan hands freed slot ranges out again (they leaked), and
keeps its free lists sorted.
- ImageVulkan's mip chain clamps each level to 1 texel: a non-square
image's shorter side went to 0.
- VulkanShader throws on a missing file instead of failing in bad_alloc.
- CRAFTER_GRAPHICS_DEVICE picks the GPU by its number among the distinct
GPUs Vulkan lists; anything but such a number is an error.
- The debug messenger prints info messages only with
CRAFTER_GRAPHICS_VALIDATION set.
- Windows: the WinRT gamepad code moved to a C file (mingw-w64's WinRT
headers don't compile as C++), -lcombase dropped, a reference leak fixed.
- project.cpp honours --local for Crafter.Event, Math and Asset, as Asset
does for Math, and builds every test through one helper, projectArgs
included.
- Tests: DebugMessengerSeverity, DeletionQueueThreads, DeviceSelection,
HeapSlotReuse, MeshLifetime, MipChainNonSquare, PipelineLifetime,
RTPassPushData, SwapchainTransferDst, UploadInFlight, VulkanBufferAddress,
VulkanShaderLifetime, WindowUpdateClock; others extended.
Breaking: SaveFrame is CaptureNextFrame; Mesh::opaque is geometryFlags; a
buffer without SHADER_DEVICE_ADDRESS usage has no address; frameCounter is
std::atomic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:07:54 +02:00
|
|
|
|
if (pushBytes != 0) {
|
|
|
|
|
|
VkPushDataInfoEXT userPush {
|
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT,
|
|
|
|
|
|
.offset = 0,
|
|
|
|
|
|
.data = { .address = pushPtr, .size = pushBytes },
|
|
|
|
|
|
};
|
|
|
|
|
|
Device::vkCmdPushDataEXT(cmd, &userPush);
|
|
|
|
|
|
}
|
2026-06-03 01:59:54 +00:00
|
|
|
|
// 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.
|
2026-06-03 18:35:39 +00:00
|
|
|
|
if (Device::workaroundDescriptorHeapAS && pipeline->workaroundNeedsTlas) {
|
2026-06-03 01:59:54 +00:00
|
|
|
|
VkDeviceAddress tlasAddr = RenderingElement3D::tlases[frameIdx].address;
|
|
|
|
|
|
VkPushDataInfoEXT pushInfo {
|
|
|
|
|
|
.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT,
|
2026-06-03 02:28:02 +00:00
|
|
|
|
// 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.
|
2026-06-03 18:35:39 +00:00
|
|
|
|
// Tracked per-pipeline (copied from the shader table) so a
|
|
|
|
|
|
// later-loaded shader can't clobber it.
|
|
|
|
|
|
.offset = pipeline->workaroundTlasPushOffset,
|
2026-06-03 01:59:54 +00:00
|
|
|
|
.data = { .address = &tlasAddr, .size = sizeof(tlasAddr) },
|
|
|
|
|
|
};
|
|
|
|
|
|
Device::vkCmdPushDataEXT(cmd, &pushInfo);
|
|
|
|
|
|
}
|
2026-05-01 23:35:37 +02:00
|
|
|
|
Device::vkCmdTraceRaysKHR(cmd,
|
|
|
|
|
|
&pipeline->raygenRegion,
|
|
|
|
|
|
&pipeline->missRegion,
|
|
|
|
|
|
&pipeline->hitRegion,
|
|
|
|
|
|
&pipeline->callableRegion,
|
feat: windows on their own clock, deferred deletion for every handle owner, staged re-uploads, and RTPass push data
Window
- updateInterval runs Update on the window's own clock while the
compositor sends no frame callbacks (hidden, minimized): the Wayland loop
is a poll() loop, and the Win32 loop sleeps until the next update. Both
read the interval each pass.
- SetPresentMode (FIFO, Mailbox, Immediate). A mode the surface lacks, or
whose swapchain has other than numFrames images (Mesa's Mailbox has four),
falls back to FIFO until the mode is set again.
- AddPollFd / RemovePollFd wake the Wayland loop on the app's descriptors.
- The swapchain has TRANSFER_DST where the surface allows it.
- CaptureNextFrame replaces SaveFrame: it records the copy in the frame,
before the present, where SaveFrame read an image already presented. A
frame unwound past before the write still frees the readback buffer.
- Inter-pass barriers on the swapchain image carry the access masks of the
stages the passes use.
Deferred deletion (issue #101)
- VulkanBuffer, Mesh, VulkanShader, ComputeShader and PipelineRTVulkan hand
their handles to the deletion queue from their destructors and moves, as
a frame in flight may still use them; a static owner goes after the
driver has torn down. VulkanShader and PipelineRTVulkan move and can't be
copied; re-Load and re-Init queue the old pipeline.
- The queue and its mutex are never destroyed, and the queue takes handles
from any thread; Device::frameCounter is atomic. Both the queue and the
staging rings count frames as one window renders them (documented).
- DrainDeletions also lets every buffer's staging ring start over: without
a Window nothing else reclaims either, so an app without one calls it
after waiting for its work.
Meshes and buffers
- A Mesh rebuild queues the old acceleration structure and builds into the
same storage after a barrier, so the BLAS address stays put when it fits.
The refit and rebuild barriers cover compute ray queries too.
- RTBuildOptions::noDuplicateAnyHit. Mesh::geometryFlags replaces opaque,
and the refit and rebuild fallbacks keep the flags.
- A re-upload into a buffer a frame in flight may read (UploadDeviceLocal
of the same size, UploadDeviceLocalRange) copies on the GPU, after those
reads, through a per-frame staging ring; on ReBAR it used to write the
buffer in place. Empty uploads record nothing.
- A compressed Mesh::Build that keeps its buffers decompresses into them
after the reads of frames in flight, and the hit shaders and ray queries
wait for the decompress as well as the BLAS build. Its vertex buffer
takes a ranged refit's copies.
- Mesh move-assigns (queueing the BLAS it replaces), so it can be replaced
in place or erased from a vector.
- VulkanBuffer::Create asks for a device address only for buffers with
SHADER_DEVICE_ADDRESS usage, as vkGetBufferDeviceAddress requires; the
others have address 0. UIRenderer::RegisterBuffer refuses those.
- Build inputs are synchronized as shader reads.
Other
- RTPass pushes the app's own push data and traces at an explicit size.
- DescriptorHeapVulkan hands freed slot ranges out again (they leaked), and
keeps its free lists sorted.
- ImageVulkan's mip chain clamps each level to 1 texel: a non-square
image's shorter side went to 0.
- VulkanShader throws on a missing file instead of failing in bad_alloc.
- CRAFTER_GRAPHICS_DEVICE picks the GPU by its number among the distinct
GPUs Vulkan lists; anything but such a number is an error.
- The debug messenger prints info messages only with
CRAFTER_GRAPHICS_VALIDATION set.
- Windows: the WinRT gamepad code moved to a C file (mingw-w64's WinRT
headers don't compile as C++), -lcombase dropped, a reference leak fixed.
- project.cpp honours --local for Crafter.Event, Math and Asset, as Asset
does for Math, and builds every test through one helper, projectArgs
included.
- Tests: DebugMessengerSeverity, DeletionQueueThreads, DeviceSelection,
HeapSlotReuse, MeshLifetime, MipChainNonSquare, PipelineLifetime,
RTPassPushData, SwapchainTransferDst, UploadInFlight, VulkanBufferAddress,
VulkanShaderLifetime, WindowUpdateClock; others extended.
Breaking: SaveFrame is CaptureNextFrame; Mesh::opaque is geometryFlags; a
buffer without SHADER_DEVICE_ADDRESS usage has no address; frameCounter is
std::atomic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:07:54 +02:00
|
|
|
|
width != 0 ? width : window.width, height != 0 ? height : window.height, 1);
|
2026-05-01 23:35:37 +02:00
|
|
|
|
}
|
2025-11-25 19:54:03 +01:00
|
|
|
|
};
|
2026-05-01 23:35:37 +02:00
|
|
|
|
}
|
2026-05-18 02:07:48 +02:00
|
|
|
|
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-18 18:43:30 +02:00
|
|
|
|
|
|
|
|
|
|
#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;
|
2026-05-19 00:27:09 +02:00
|
|
|
|
// 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;
|
2026-05-31 16:24:41 +00:00
|
|
|
|
// 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;
|
2026-06-09 12:55:14 +00:00
|
|
|
|
// 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;
|
2026-06-09 22:45:25 +00:00
|
|
|
|
// 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;
|
2026-05-18 18:43:30 +02:00
|
|
|
|
|
|
|
|
|
|
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),
|
2026-05-19 00:27:09 +02:00
|
|
|
|
static_cast<std::int32_t>(gy),
|
|
|
|
|
|
handlesPtr,
|
2026-05-31 16:24:41 +00:00
|
|
|
|
static_cast<std::int32_t>(handlesCount),
|
2026-06-09 12:55:14 +00:00
|
|
|
|
static_cast<std::int32_t>(maxDepth),
|
2026-06-09 22:45:25 +00:00
|
|
|
|
outTexHandle,
|
|
|
|
|
|
static_cast<std::int32_t>(raysPerPixel));
|
2026-05-18 18:43:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|