# Signal Garden · Vulkan companions Course 5 teaches an original 16-week Vulkan renderer project in eight chapters. The complete **Signal Garden scene remains a staged extension project**. A complete runnable clear-color window now supplies the presentation foundation; mesh/material/compute scene integration is explicitly separate. ## Current host-language baseline The companions build as **C++23** with extensions disabled. The recipes select Ninja and Clang explicitly; on macOS, `clang++` normally selects Apple Clang. Use a fresh build directory when changing generator or compiler. CMake describes and configures the targets, Ninja runs their build steps, and Clang compiles them. No setup command here installs a dependency. The shader language is configured separately from C++. No C++26 feature is required by the GPU programs. Check the exact compiler and standard-library support before adopting a later feature. Older idioms remain useful when their contracts are clear: the purpose of scoped owners and checked return values here is to make cleanup and failure paths explicit, not to replace every line with new syntax. The independent C++23 audit is recorded in `audits/gpu-courses.md` at the project root; earlier C++20 evidence below retains its original scope. ## What is included - `capabilities.cpp`: a complete headless Vulkan 1.3 capability-report program. It reports actual physical devices, queue families, relevant limits, swapchain extension availability, and synchronization2/dynamic-rendering support. It enables the Khronos validation layer if installed. It creates **no surface, logical device, swapchain, or GPU submission**. It cannot establish presentation support, enabled device features, or renderer correctness. - `contracts.hpp`: GPU-independent checked alignment and bounded byte-count helpers. They demonstrate allocation-boundary arithmetic, not a GPU allocator. - `contracts_test.cpp`: meaningful boundary cases, including overflow, zero alignment/stride, exact budget, and non-power-of-two alignment. ## Run the real clear-color window `clear_window.cpp` is a complete C++23 executable. It creates a GLFW window, Vulkan instance/surface/device, two frame slots, a queried swapchain, dynamic rendering clear, acquire/submit/present operations, and orderly resource cleanup. It has no graphics pipeline or mesh yet: attachment clearing needs neither. **Requirements:** a graphical desktop session; CMake >=3.24, Ninja, Clang; Vulkan headers/loader >=1.3; GLFW >=3.4; a device with `dynamicRendering`, `synchronization2`, one graphics-and-present queue family, `VK_KHR_swapchain`, and the queried/enabled `swapchainMaintenance1` feature through its KHR or EXT extension. The matching surface-maintenance instance extension and `VK_KHR_get_surface_capabilities2` are enabled. CMake discovers existing packages; no build step installs system dependencies. Unsupported requirements produce a rejection. Separate graphics/present families are outside this checkpoint's deliberately small scope. From the **Parallax project root**: ```sh cmake -S native/advanced/vulkan -B native/build-signal -G Ninja \ -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug \ -DSIGNAL_BUILD_WINDOW=ON -DSIGNAL_BUILD_VULKAN_PROBE=ON cmake --build native/build-signal ctest --test-dir native/build-signal --output-on-failure ./native/build-signal/signal-capabilities ./native/build-signal/signal-window --frames 12 ./native/build-signal/signal-window --frames 300 --resize ./native/build-signal/signal-window --frames 300 --resize --sync-validation ``` The last command explicitly requires Khronos core and synchronization validation; missing layers are a failure, not silently skipped. Without that request the executable clearly prints `validation=off (not checked)`. A validation error causes a nonzero result. Run without `--frames` to keep the blue-teal window open. `--frames nope` must fail before creating a device. Close/minimize/restore should also be exercised manually on the actual Linux desktop session. Expected bounded result: the selected device and queue family, each swapchain extent/image count, then `submitted=12` (or 300) and `shutdown=GPU-and-presentation-drained`. The resize check requests 960×540 logical window dimensions after two submissions; pixel extents depend on the display. Expect additional swapchain generations. Successful submission is not a pixel oracle; inspect the displayed clear color separately. ### The lifetime contract to study - `Frame` owns command storage, an acquire semaphore, and a signaled-at-start submission fence. Reset that fence only after usable acquisition/recording, immediately before the submission which will signal it. - `Image` borrows its swapchain image and owns its view, render-finished semaphore, and presentation fence. Render-finished semaphores are indexed by acquired image, not the rotating CPU frame slot. - The acquire wait and the first layout barrier meet at `COLOR_ATTACHMENT_OUTPUT`. The clear writes there, then the image transitions to `PRESENT_SRC_KHR`; the submitted signal covers `ALL_COMMANDS`. - `TIMEOUT`/`NOT_READY` skip work. `OUT_OF_DATE` schedules recreation; `SUBOPTIMAL` may finish a usable frame and schedules recreation. Zero extent skips rendering while events continue. Surface/device loss exits with a diagnostic rather than pretending recovery succeeded. - Resize deliberately blocks: drain command work **and tracked presentation fences**, destroy the old generation, then create the next. Creation failure exits safely; there is no asynchronous retired-generation queue in this lesson. - `vkDeviceWaitIdle` alone is not a presentation-completion proof. Maintenance1 presentation fences authorize reclamation of presentation resources, including wait semaphores; they do not report actual scanout timing. The KHR promotion aliases the EXT structures used by this source for older-header compatibility. - `OUT_OF_DATE` and `SURFACE_LOST` presentation errors still enqueue semaphore waits and are tracked. Out-of-memory enqueue failures leave synchronization state unaffected and are not assigned a pending presentation wait. - If a shutdown wait fails for a reason other than device loss, the fallback destructor terminates rather than freeing resources whose completion is unknown. Device-loss cleanup follows the separate API rule. - Scoped owners unwind partially initialized state. Renderer destruction drains before member handles are released. A failed submission never leaves cleanup waiting for a frame fence that no operation will signal. **Executed on 2026-10-24:** official Vulkan-Headers 1.3.296, Apple Clang 21, GLFW 3.5, existing MoltenVK 1.4.3 on Apple M4 Pro. The 300-frame resize run created three swapchain images, resized 1600×1000 →1920×1080 pixels, recorded two generations, and drained successfully. No SDK or driver was installed. This was direct linking to the already-installed MoltenVK library, not a Khronos loader/KosmicKrisp or AMD/RADV validation run. The requested validation mode correctly failed because no validation layer was discoverable. See `audits/renderer-vulkan-metal-update.md` for full evidence and limits. ## Build the portable arithmetic check From this directory, choose a build directory outside your source tree: ```sh cmake -S . -G Ninja -DCMAKE_CXX_COMPILER=clang++ -B /tmp/prism-signal-contracts -DCMAKE_BUILD_TYPE=Debug cmake --build /tmp/prism-signal-contracts ctest --test-dir /tmp/prism-signal-contracts --output-on-failure ``` The earlier helpers and test were compiled with Apple Clang, C++20, warnings enabled, AddressSanitizer, and UndefinedBehaviorSanitizer. The test passed on the authoring Mac. This says nothing about Vulkan GPU execution on Linux. ## Build the capability probe on CachyOS Use the distribution's supported Vulkan development environment: a loader and headers at least version 1.3, an AMD-compatible Vulkan driver, validation layers, and Vulkan tools. Mesa RADV is a Vulkan implementation for supported AMD GPUs; the laptop model alone does not identify the installed GPU or driver. Inspect the existing configuration before changing packages. No system packages are installed by this project. ```sh cmake -S . -G Ninja -DCMAKE_CXX_COMPILER=clang++ -B /tmp/prism-signal-vulkan -DCMAKE_BUILD_TYPE=Debug -DSIGNAL_BUILD_VULKAN_PROBE=ON cmake --build /tmp/prism-signal-vulkan /tmp/prism-signal-vulkan/signal-capabilities vulkaninfo --summary ``` The Vulkan probe passed a **C++20 syntax/type check with warnings treated as errors** against the official Khronos Vulkan-Headers release [`v1.3.296`](https://github.com/KhronosGroup/Vulkan-Headers/releases/tag/v1.3.296), commit `29f979ee5aa58b7b005f805ea8df7a855c39ff37`. The release archive was downloaded only into the authoring work directory; no SDK, loader, or driver was installed. Its SHA-256 was `e204e0b3c19f622d197df945737f5db913d6621830999b8578d34e80a7c90585`. The independent audit reran the check with C++23. To reproduce it, use the following command with the extracted official headers: ```sh clang++ -std=c++23 -Wall -Wextra -Wpedantic -Werror -fsyntax-only -I /absolute/path/to/Vulkan-Headers-1.3.296/include capabilities.cpp ``` This check caught and corrected a generic-lambda deduction error in the enumeration helper's null data-pointer call. It does **not** link or execute the program, exercise loader discovery, or validate a Linux GPU. **The earlier static-only limitation is superseded by the dated MoltenVK execution above. AMD/CachyOS remains the primary target and is not executed in that Mac run; a configured KosmicKrisp runtime is another separate test lane.** Run the probe on the actual target and save its output with OS, driver, and date. No AMD GPU model, queue topology, or optional feature is assumed. A loader supporting 1.3 does not guarantee that every enumerated physical device does; the program skips the 1.3 feature chain for older physical devices and reports that distinction. The independent audit also built the official Khronos glslang 16.6.0 compiler (commit `e1b562a8bed273a02f30b59b66a5d499793cede5`) in the task's work directory, with optional optimizer/tests/external dependencies disabled. Both published GLSL stages compiled successfully for Vulkan 1.3, entry point `main`, generating SPIR-V 1.6. A subsequent independent SPIRV-Tools validation run also passed for both current published modules with the same Vulkan 1.3 target. A deliberately misaligned uniform-block member was rejected by the validator; mismatched stage interfaces were rejected by the linker. These checks do not establish runtime pipeline compatibility or GPU execution. See `audits/vulkan-static-validation.md` at the project root for exact revisions, commands, controls, and limits. ## Verify the published shaders without a GPU The optional verifier reads the actual course snippets each time. It compiles and links each shader module with glslang, records interface information, and runs the separate SPIRV-Tools validator against the generated SPIR-V. It is a build-time check, not a website or native-program runtime dependency. From the **Parallax project root**, with Bun, `glslang`, and `spirv-val` on your tool path: ```sh bun native/advanced/vulkan/verify_shaders.ts ``` If the tools live elsewhere, provide their absolute executable paths: ```sh GLSLANG_BIN=/absolute/path/to/glslang SPIRV_VAL_BIN=/absolute/path/to/spirv-val bun native/advanced/vulkan/verify_shaders.ts ``` Generated sources, modules, interface reports, and logs go under `native/build-vulkan-shaders/`; passing one directory argument selects a different output location. The verifier replaces its generated filenames there. Missing tools or a failed command fail the check. It does not download or install tools. The camera vertex shader and drift compute shader are separate programs, not a vertex/fragment pair. Host binding layouts, selected-device limits, and the renderer still require their own checks. Use the [Khronos valid-usage reference](https://docs.vulkan.org/guide/latest/validation_overview.html) to connect each API rule to its exact condition. At runtime, explicitly configure the validation modes you need, including synchronization validation; enabling the base layer does not mean all optional checks ran. A zero-message run is evidence about the executed path, not proof of a correct image or all future frames. ## Runtime integration The headless instance intentionally enables no window-system extensions. When building the real renderer, obtain the required instance extensions from your window library, create a real surface, query presentation support for that surface, and enable only your required device extensions/features. The course baseline asks for synchronization2 and dynamic rendering through a checked Vulkan 1.3 device path. Missing features produce a clear unsupported message. ## Build milestones, not a hidden engine 1. Capability passport and explicit device selection. 2. Clear-color window; acquire/submit/present; resize, minimize, and shutdown. 3. Bounded mesh uploads, staging retirement, and alignment checks. 4. A documented shader/descriptor/pipeline interface. 5. Single-queue dependencies and completion-aware resource retirement. 6. Dynamic rendering, depth, and a small texture. 7. Bounded compute seeds with a CPU oracle and explicit later readers. 8. An original scene, GPU capture, measured experiment, and portfolio note. Lesson code panels are labeled as shaders, integration fragments, or pseudocode. They do not silently supply omitted initialization or error handling. A shader snippet is not a complete renderer. No ray-tracing extension is required. The initial frame fences must be signaled or their first wait explicitly skipped. Reset a fence only before a submission that will signal it. Frame slots and acquired swapchain image indices are different. Presentation semaphore reuse and swapchain retirement follow their own completion rules; a rendering fence is not proof that presentation has finished. Query supported maintenance extensions before adopting their presentation-fence path. AMD/Linux remains the primary target. As checked in October 2026, the official macOS Vulkan SDK includes KosmicKrisp, a Vulkan 1.4-conformant implementation on Apple Silicon using Metal 4 (macOS 26+). A configured M4 Pro can therefore run Vulkan with validation as a second lane; it need not wait for the AMD machine. KosmicKrisp requires the Vulkan loader and does not require the MoltenVK portability-enumeration extension. Query extensions instead of hard-coding that extension for all Apple builds. The existing headless probe does not request it. Record the chosen implementation and keep Mac and AMD results separate. This update changes the supported verification plan; no runtime was installed or executed during the static validation follow-up. See [LunarG’s current macOS guide](https://vulkan.lunarg.com/doc/view/latest/mac/getting_started.html) and [Mesa’s KosmicKrisp documentation](https://docs.mesa3d.org/drivers/kosmickrisp.html). ## Primary references - [Khronos · swapchain semaphore reuse and presentation lifetime](https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html) - [Khronos · presentation resource fences](https://docs.vulkan.org/refpages/latest/refpages/source/VkSwapchainPresentFenceInfoKHR.html) - [Khronos · present result/error semantics](https://docs.vulkan.org/refpages/latest/refpages/source/vkQueuePresentKHR.html) - [Khronos · synchronization examples](https://docs.vulkan.org/guide/latest/synchronization_examples.html) - [GLFW · Vulkan guide and loader selection](https://www.glfw.org/docs/latest/vulkan_guide.html) - [Khronos development environment](https://docs.vulkan.org/tutorial/latest/02_Development_environment.html) - [Khronos physical devices and queues](https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/00_Setup/03_Physical_devices_and_queue_families.html) - [Khronos memory allocation](https://docs.vulkan.org/guide/latest/memory_allocation.html) - [Khronos synchronization](https://docs.vulkan.org/guide/latest/synchronization.html) - [Khronos presentation semaphore reuse](https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html) - [Mesa RADV](https://docs.mesa3d.org/drivers/radv.html) - [Apple metal-cpp](https://developer.apple.com/metal/cpp/) for the next native API course