Inside the companion
Vulkan · Signal Garden
Query a device and study a clear-color window with explicit frame and presentation lifetimes.
Project files 6
Open the Signal Garden checkpoint
Requirements, build commands, clear-window steps, and synchronization contracts.
# 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
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
