Course 4 — Real-Time Rendering & GPU Engineering: Vulkan, Metal & CUDA in Modern C++
A fully lab-based course: modern C++20 and CMake, the same scenes rendered through Vulkan and Metal, general-purpose GPU computing in CUDA C++ and Python, and profiling on every lab — from a cleared swapchain to a shipped mini-engine
A lab-first course that takes someone who already has the mathematics (see the prerequisite below) and turns them into an engineer who can architect, implement, and optimize real-time 3D rendering and GPU compute. Every idea is learned by building it — in modern C++20, built with CMake — running it on real GPUs, capturing it in a profiler, and reconciling the measured frame against what was predicted. The target is fluency for rendering-engineer and GPU-engineer work: the graphics pipeline end to end, engine architecture (resource lifetimes, render graphs, multithreaded command recording), the advanced techniques a modern renderer is made of (shadows, deferred shading, terrain LOD, GPU-driven rendering, point splatting), and the performance engineering that makes it all ship, from battery-powered laptops to desktop GPUs.
Three GPU programming models are treated as one curriculum, because a working rendering engineer needs all three views:
- Vulkan (C++20) — the fully explicit model: you own memory, synchronization, and submission. Written against the LunarG SDK; runs on the Mac through MoltenVK and natively on the Linux desktop’s RTX 4090 — the same code on two very different GPUs, which is itself a recurring measurement.
- Metal (Swift, then metal-cpp) — the Apple-native model. The API-learning labs are written in Swift, the language of Metal by Tutorials and of Xcode’s first-class GPU tooling; the engine-architecture module then adds a metal-cpp backend, so the C++ engine core drives both APIs — the way cross-platform engines actually ship on Apple GPUs.
- CUDA (C++ and Python) — general-purpose GPU computing on the RTX 4090: kernels, the memory hierarchy, reductions and scans, streams, occupancy. Worked in CUDA C++ under CMake and in Python (Numba kernels, CuPy arrays), because prototyping in Python and shipping in C++ is the real workflow — and because the compute mental model (warps, shared memory, bandwidth) is exactly what advanced rendering (GPU culling, sorting, splatting) is built from.
Prerequisite: Course 1 — Mathematical & Theoretical Foundations is assumed as mastered — this course applies linear algebra, geometry, and numerical analysis, and never re-teaches them. Where a lab leans on a specific result it links back: Section 1 (vector spaces, linear maps, inner products — every transform and every lighting dot product), Section 3 (floating point, conditioning — depth precision, accumulated error), Section 8 (sampling — texture filtering and mipmapping are the sampling theorem), and Section 16 (2-D signals — convolution post-effects, sRGB). Course 3 is the ideal on-ramp for the machine model (caches, SIMD, calling conventions) and for C; this course’s Module 0 bridges from there to the C++20 the engine is written in. No graphics experience is assumed.
Note on AI use: As across this site, the lab write-ups are drafted with AI assistance so every lab has the same structure to work from. The substance is mine: every line of engine, shader, and kernel code is written and debugged by me, every capture and benchmark is taken on my own hardware, and every predicted-vs-measured reconciliation is my own. The write-ups specify what to build and how to measure it — they do not contain the solutions. You only learn this by building it.
How each lab is structured
Every lab file follows the same template so it can be worked as a self-contained session:
- Goal: what skill/concept the lab builds and why it matters for the craft.
- Recommended reading: the specific chapters (from the books below and, where relevant, Courses 1/3) to read before writing code.
- Prerequisites: the labs and setup steps this lab assumes are done.
- Project & environment setup: the exact CMake targets, repo paths, SDK pieces, and run configurations — so nothing about how to build and run the lab is ambiguous. The rendering/compute code itself is never given (see the AI note above); only the scaffolding is.
- Background: the minimum theory, with the key equations and the API concepts involved.
- Tasks: numbered implementation steps. Dual-track labs split into Vulkan (C++20) and Metal (Swift) subsections; compute labs split into CUDA C++ and CUDA Python.
- Deliverable & expected results: the artifact to produce (screenshot, capture file, benchmark table), the exact files to record and where they go in the repo, and the behavior/numbers to expect.
- Profiling & performance: which tool to reach for (Tracy, Xcode GPU capture, RenderDoc, Nsight), what to capture, and the frame/kernel budget to check against.
- Analysis & reconciliation: predict by hand, then explain any gap between predicted and measured.
- Going further: optional extensions.
Predicted-vs-measured tables leave the Measured cells as “…” — they are filled at the machine, same convention as Course 2’s bench labs.
Hardware & platforms (already on hand)
- Apple Silicon Mac (M-series) — the primary development machine and the Apple-GPU target: Metal natively (Xcode, Swift, metal-cpp) and Vulkan through MoltenVK. Its tile-based deferred rendering (TBDR) architecture vs. the 4090’s immediate-mode desktop architecture is a running comparison in Modules 5–6.
- Linux desktop (NVIDIA GeForce RTX 4090) — the NVIDIA target: a discrete Ada Lovelace GPU with its own VRAM across PCIe, running native Vulkan on Linux and hosting the CUDA module (nvcc, Nsight Systems/Compute) — the Linux side of every cross-platform measurement. Power and thermals via
nvidia-smikeep efficiency measurable even on a desktop power budget. Development stays on the Mac: CLion’s remote toolchain builds, runs, and debugs on this box over SSH — edit on the laptop, execute on the 4090. - Raspberry Pi 5 (optional) — Vulkan 1.2 through Mesa’s V3DV driver; an optional third port target in the Going-further sections, never required.
No other hardware is needed: every lab runs on the Mac, the Linux box, or both.
Toolchain & software setup
# --- Mac: core toolchain ---
brew install cmake ninja # CMake ≥ 3.28, Ninja generator
xcode-select --install # or full Xcode (required for Metal + GPU capture)
# Vulkan: install the LunarG Vulkan SDK for macOS (includes MoltenVK, glslc,
# validation layers, vulkaninfo). Source setup-env.sh from your shell profile.
# --- Dependencies (fetched by CMake, no manual installs) ---
# GLFW (windowing), GLM (math), VMA (Vulkan Memory Allocator), volk (loader),
# stb (image I/O), tinygltf (meshes), Tracy (frame profiler),
# Google Benchmark (microbenchmarks) — all via FetchContent, pinned in the repo.
# --- Metal ---
# Xcode + Swift for the API-learning labs (MTKView, MSL shaders, GPU capture).
# metal-cpp (single-header C++ bindings) enters in Module 4.
# --- Linux desktop (RTX 4090) ---
# NVIDIA driver + CUDA Toolkit (nvcc, Nsight Systems, Nsight Compute),
# Vulkan SDK / vulkan-tools via apt, RenderDoc (Linux build),
# nvidia-smi for power/thermals. Python: uv venv with numba, cupy, jupyter.
# Remote workflow: CLion on the Mac with a remote toolchain (SSH) to this box —
# CMake presets work unchanged; build/run/profile happen on the 4090.The profiler shelf — used in every module, not just Module 6
A defining habit of this course: no lab is “done” from its screenshot alone — each one ends inside a tool. These are introduced one at a time and then assumed:
| Tool | What it measures | Platform |
|---|---|---|
| Tracy | CPU frame timeline, zones, locks, GPU zones — the engine’s heartbeat | Mac + Linux |
| Google Benchmark | Microbenchmarks of CPU-side engine code (math, culling, sorting) | Mac + Linux |
| Xcode GPU capture | Full Metal frame debugging: encoder timeline, shader profiler, GPU counters | Mac |
| Instruments — Metal System Trace | CPU↔︎GPU timeline, display pacing, thermal state over time | Mac |
| RenderDoc | Vulkan frame debugging: event browser, resource inspection, pixel history | Linux |
| Nsight Systems | System-wide CPU/GPU timeline, CUDA streams, copy/compute overlap | Linux |
| Nsight Compute | Per-kernel deep-dive: occupancy, memory throughput, roofline | Linux |
nvidia-smi / powermetrics |
Power draw and thermals — the efficiency axis | Linux / Mac |
| In-engine HUD | Timestamp queries (Vulkan) / counter sample buffers (Metal), rolling frame stats | Both |
Recommended-reading key
| Abbrev. | Book | Role |
|---|---|---|
| MbT | Begbie & Horga (Kodeco), Metal by Tutorials, 5th ed. | The Metal track: Swift, MTKView, MSL, and Apple-GPU idioms, tutorial-paced |
| C&S | Castorina & Sassone, Mastering Graphics Programming with Vulkan | The engine-architecture track: bindless resources, render graphs, GPU-driven rendering |
| Motta | Motta, GPU Programming with C++ and CUDA | The CUDA C++ track: kernels, memory, streams, and performance |
| Halladay | Halladay, Practical Shader Development | Shader intuition: vertex/fragment thinking, lighting, and effects, API-agnostic |
| Lengyel | Lengyel, Mathematics for 3D Game Programming and Computer Graphics, 3rd ed. | The graphics-math reference: transforms, frustums, planes, projections — read for application, the theory being Course 1’s |
| D&P | Dunn & Parberry, 3D Math Primer for Graphics and Game Development, 2nd ed. | Gentler second telling of the same math; good for coordinate-space conventions |
All six are cited inline where used, like PEI in Course 2 and Yiu in Course 3 — tutorial and reference books without formal exercise sets, so none is a Books-page entry; this course’s labs are the exercises. Chapter references are given by chapter title rather than number where editions differ — confirm against the copy in hand.
Free primary sources, cited throughout: the Vulkan Tutorial and vkguide.dev (the Vulkan on-ramp and the engine-shaped second pass), the Vulkan specification’s synchronization chapter, Apple’s Metal documentation and WWDC session videos (TBDR, GPU counters, metal-cpp), the GLM manual, and NVIDIA’s CUDA C++ Programming Guide and Best Practices Guide.
Why two graphics APIs (and where each language is used)
Writing the same renderer against both APIs is the fastest way to learn what is essential (the pipeline, resource states, synchronization) versus what is an API’s opinion (render passes, descriptor sets vs. argument buffers, explicit vs. tracked hazards). The differences are worked, not footnoted — the coordinate conventions (Vulkan’s clip space vs. Metal’s NDC), the memory models, and the hazard-tracking philosophies each get measured head-to-head.
- Vulkan labs are C++20 — the explicit API in the systems language, the combination the cross-platform engine world runs on.
- Metal API-learning labs (Modules 2–3) are Swift — matching MbT 5th ed. and Xcode’s tooling, so the learning loop (edit MSL → run → GPU capture) has zero friction.
- From Module 4 the engine core is C++20 with a metal-cpp backend — one scene graph, one render graph, two API backends. Swift remains the app shell; C++ owns the frame.
A note on scope: compute shaders inside the graphics APIs are used freely (culling, sorting, post-processing). CUDA is kept for Module 1 and for interop comparisons — it is the deepest tooling ecosystem for learning GPU microarchitecture, which then transfers to reading Metal and Vulkan GPU captures intelligently.
Module & lab map
The course is 8 modules, 33 labs. Each lab is its own page; work them in order — the engine grows monotonically from Lab 0.1’s empty CMake project to the Module 7 capstone.
Module 0 · Toolchain: C++20, CMake, and three GPU bring-ups
The project skeleton every later lab builds in, and first pixels from both graphics APIs.
Module 1 · CUDA & GPU compute foundations
The GPU as a throughput machine, learned where the tooling is deepest — in both CUDA C++ and Python.
Module 2 · The raster pipeline: triangles to textured meshes
The graphics pipeline proper, every lab built twice: Vulkan/C++ and Metal/Swift.
Module 3 · Shading: light, materials & post-processing
The fragment shader earns its keep: local illumination through physically based shading to the HDR post stack.
Module 4 · Engine architecture in C++20
From “a program that draws” to “an engine”: resource lifetimes, a render graph, threads, and a scene. metal-cpp arrives here.
Module 5 · Advanced real-time techniques
The techniques a modern renderer is made of — each one predicted, built on both APIs where the hardware allows, and profiled.
Module 6 · Performance engineering & profiling
The tools become the subject: find the bottleneck, name it, fix it, prove the fix.
Module 7 · Capstone: a small rendering engine, shipped
Everything converges: a streaming 3D world viewer on two APIs and two GPUs, with a staff-level performance report.
Repository structure
All lab work lives beside the Course 2 and Course 3 workspaces in the companion repo diivanand/diiv_website_custom_courses, under a top-level course4/ folder — a self-contained CMake project whose build scaffolding is fully set up in advance (presets, pinned dependencies, per-lab stub targets that already compile), so lab time goes to the code, never the build:
diiv_website_custom_courses/course4/
README.md # build quickstart
CMakeLists.txt # top-level project + CMakePresets.json (debug / release / profile / linux-*)
cmake/ # course4_options (warnings, C++20, sanitizers) + pinned FetchContent deps
engine/ # the C++20 engine that grows across Modules 2-7
core/ vulkan/ metal/ # metal/ = metal-cpp backend (Module 4+; needs third_party/metal-cpp)
shaders/ # glsl/ (→ SPIR-V via the compile_shaders target) and msl/
metal-swift/ # SwiftPM package: the Swift/MTKView apps of Modules 0, 2-3 (open in Xcode to capture)
cuda/ # Module 1: CUDA C++ (built by the linux-* presets) and python/ (numba/cupy)
labs/ # one folder per lab — everything that lab produces
lab-<M>-<N>/
notes.md # predicted-vs-measured tables, reconciliation
captures/ # Tracy traces, .gputrace, RenderDoc .rdc, Nsight reports
benchmarks/ # Google Benchmark JSON, HUD stat dumps
src/ # (C++ labs) the lab's executable — a stub target until you write it
assets/ # meshes, textures, environment maps, splat scenes (gitignored)
docs/ # setup notes: Vulkan SDK, metal-cpp, the Linux/4090 box, asset sources
Naming convention: everything a lab produces lives in its folder. A lab is “done” when its notes.md has every Measured cell filled and its captures/ folder holds the profiler evidence.
What you’ll be able to do at the end
- Stand up a modern C++20/CMake codebase with profiling, benchmarks, and sanitizers wired in from the first commit.
- Explain and use the GPU’s execution and memory model — warps, occupancy, coalescing, shared memory — and write CUDA kernels in C++ and Python that saturate a memory bus on purpose.
- Drive the full graphics pipeline on both Vulkan and Metal: pipelines and pipeline state, buffers and staging, textures and samplers, depth, blending — and explain every difference between the two APIs’ models.
- Implement the standard shading stack — Blinn–Phong through physically based shading with image-based lighting — and an HDR post-processing chain, in both GLSL and MSL.
- Architect an engine: RAII resource lifetimes, frames in flight, a render graph with explicit barriers, multithreaded command recording, and a culled, instanced scene — one C++ core, two API backends.
- Build the techniques modern renderers are made of: cascaded shadow maps, deferred shading (and its TBDR-aware variant on Apple GPUs), chunked-LOD terrain with streaming, GPU-driven culling with indirect draws, and a Gaussian-splat point renderer.
- Profile like it’s part of the job — because it is: Tracy, Xcode GPU capture and Metal counters, RenderDoc, Nsight Systems/Compute — and run an optimization ladder with before/after evidence, including power.
- Ship a capstone: a streaming 3D world viewer running on two APIs and two GPUs, with a written performance report a staff engineer would sign.
Relationship to Courses 1 & 3
Course 1 is the theory this course spends; Course 3 is the machine model underneath it.
- Transforms, projections, and lighting math (Modules 2–3) apply Course 1 Section 1 — every MVP chain is a composition of linear maps; every lighting equation is inner products and orthogonal decompositions.
- Depth precision, accumulation error, and fast-math tradeoffs (Modules 2, 5, 6) apply Section 3 — floating point and conditioning, now with a Z-buffer attached.
- Texture filtering, mipmapping, and screen-space aliasing (Labs 2.4, 3.4) apply Section 8 — minification is resampling; the mip chain is an anti-aliasing filter bank.
- Convolution post-effects and color pipelines (Labs 3.4, 5.2) apply Section 16 — separable Gaussian blur, sRGB and gamma, the image as a 2-D signal.
- The GPU as an architecture (Module 1) extends Course 3 Module 2 — the cache ladder and ILP probes re-run on a machine that hides latency with parallelism instead of speculation; Course 3 Module 5’s NEON lanes reappear as warps.
- C++20 engine code (Module 0 onward) stands on Course 3 Part IV’s modern-C discipline — the same care about layout, aliasing, and undefined behavior, now with RAII and templates in the toolbox.