Crafter.Graphics/examples
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Jorijn van der Graaf 079b6b0e1f
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 20:20:29 +02:00
..
CustomShader feat(vulkan): run the compute/UI path without VK_EXT_descriptor_heap 2026-10-06 17:28:46 +02:00
Decompression SPDX license update 2026-07-22 18:09:06 +02:00
HDRBloom SPDX license update 2026-07-22 18:09:06 +02:00
HelloDom SPDX license update 2026-07-22 18:09:06 +02:00
HelloUI SPDX license update 2026-07-22 18:09:06 +02:00
HelloWindow SPDX license update 2026-07-22 18:09:06 +02:00
InputSystem SPDX license update 2026-07-22 18:09:06 +02:00
RayQueryPick SPDX license update 2026-07-22 18:09:06 +02:00
RTMultiShadow SPDX license update 2026-07-22 18:09:06 +02:00
RTStress SPDX license update 2026-07-22 18:09:06 +02:00
RTVolume feat: windows on their own clock, deferred deletion for every handle owner, staged re-uploads, and RTPass push data 2026-10-20 20:20:29 +02:00
Sponza docs: where the NVIDIA descriptor-heap faults stand on 615.78.08 2026-10-19 03:03:17 +02:00
SponzaBench docs: where the NVIDIA descriptor-heap faults stand on 615.78.08 2026-10-19 03:03:17 +02:00
VulkanTriangle docs: where the NVIDIA descriptor-heap faults stand on 615.78.08 2026-10-19 03:03:17 +02:00
LICENSE SPDX license update 2026-07-22 18:09:06 +02:00
README.md test(webgpu-rt): RTMultiShadow example exercising N shadow rays/pixel/bounce (#30) 2026-06-09 22:45:33 +00:00

Examples

Each example is a self-contained crafter-build project that depends on the parent Crafter.Graphics via LocalProject. To build and run any of them:

cd examples/<name>
crafter-build -r

Index

HelloWindow

Minimum viable program: open a window, run the event loop. No Vulkan rendering. Useful as a smoke test for Device::Initialize + Window + the platform backend.

VulkanTriangle

Ray-traced single triangle through vkCmdTraceRaysKHR. Shows the full ray-tracing setup: DescriptorHeapVulkan with image and buffer slots, PipelineRTVulkan from raygen / miss / closesthit SPIR-V, BLAS via Mesh::Build, TLAS via RenderingElement3D::BuildTLAS, direct vkWriteResourceDescriptorsEXT for swapchain views, RTPass on window.passes. Smallest test of the bindless ray-tracing path.

HelloUI

Compute-shader UI demo using all three UI tiers:

  • Tier 3 components: DrawButton, DrawSlider, DrawProgressBar, composed via Rect::SubRect for resize-safe layout.
  • Tier 2 standard shaders: DispatchQuads for the background and components, DispatchCircles for a cursor-tracking dot, DispatchText for the button label (with the FontAtlas wired up to UIRenderer).
  • Tier 1 is available too — any custom ComputeShader registered on the same heap can be dispatched alongside the standard ones.

Hit-testing and animation are user code (see the EventListener subscriptions on window.onMouseMove / onMouseLeftClick); the library does not track widgets or focus.

Drop a TTF in this directory as font.ttf before running (the example loads it via Font("font.ttf")).

InputSystem

Guided tour of Crafter::Input: name actions ("Jump", "Move", "Fire", "Look", "Zoom"), bind them to keys / mouse / gamepad (with composite bindings for WASD-as-Vector2 and analog sticks), and consume them as events. Demonstrates:

  • The compile-time Key(CrafterKeys::Space) helper that folds to a per-platform raw scancode — bindings stay cross-platform-readable in source while runtime data stores raw codes only.
  • All four action types (Button, Axis1D, Vector2) with multiple bindings per action (any-of semantics).
  • Map::StartRebind — press R, then press any input to remap "Jump" at runtime. Captured input is filtered out for that frame.
  • BindingToString / BindingFromString round-trip — print the default bindings as the on-disk format.
  • Gamepad hot-plug events: plug a controller in mid-run and the bindings start firing immediately.

Console-driven (no UI rendering needed); focus the window and watch stdout.

CustomShader

Tier 1 demo: a user-authored compute shader (inverse-circle.comp.glsl) running alongside the shipped drawQuads. The custom shader inverts RGB under each item-circle — exactly the kind of effect attempt #2's closed shader couldn't express. Shows:

  • Defining your own item POD struct in C++ + matching std430 struct in GLSL.
  • #include "../../shaders/ui-shared.glsl" for the bindless heap declarations + UIDispatchHeader push-constant contract.
  • ComputeShader::Load for the .spv, UIRenderer::RegisterBuffer for your SSBO, FillHeader to populate the standard prefix, and UIRenderer::Dispatch to launch — the same pattern the standard shaders use under the hood.
  • The inter-dispatch SHADER_WRITE → SHADER_READ|WRITE barrier is inserted automatically, so the custom shader sees the colored stripes drawn by the prior DispatchQuads and reads/writes the swapchain image safely.

RayQueryPick

Regression test for the WebGPU software ray-query shim. Builds a 512-instance TLAS and shoots one ray through a rayQuery=true PlainComputeShader, reading the committed hit back to the host and checking it against the analytically-known answer. Guards against the hardcoded-leaf-start TLAS-traversal bug (issue #25) that made every rayQuery pick miss for realistic instance counts. WebGPU/DOM only.

HDRBloom

Cross-backend-shaped HDR bloom on the WebGPU backend (issue #27). The RT pipeline's RESOLVE writes linear radiance into a user rgba16float texture (StorageImage2D, PipelineRTWebGPU::Init(..., RGBA16Float)), two PlainComputeShader passes threshold + blur it through float storage targets (UICustomBindingKind::StorageTexture), and a UI custom shader composites scene + bloom with a Reinhard tonemap onto the canvas. Exercises the three primitives an HDR post-process chain needs: float textures, write-only storage-texture bindings, and pre-tonemap radiance out of the wavefront. The threshold/blur passes run from onBeforeUpdate so each gets its own queue submit — the storage-write → sampled-read barrier WebGPU only provides between submits (or between passes), never within a single compute pass. WebGPU/DOM only; the same chain is wireable on Vulkan today via an offscreen HDR heap image + a composite RenderPass (the present path records passes generically and barriers between them).

RTMultiShadow

Multi-light shadowing through the wavefront RT pipeline (issue #30). Five pillars on a checkered ground, four colored point lights; the closest-hit emits one shadow ray per light from the same invocation, so up to four rays per pixel resolve in a single SHADE pass. Exercises both halves of the >1-ray-per-pixel-per-bounce widening: the atomic rtAccumulate (per-channel f32 CAS — concurrent same-pixel adds don't race) and RTPass::raysPerPixel (scales the ray/hit/payload buffers so the per-light emits aren't capacity-dropped). Any regression shows up as flickering dark noise or a missing shadow color in the overlap regions. WebGPU/DOM only.