Inside the companion
First Light
Make your first CPU image, rasterize triangles, and trace an allocation budget.
Project files 15
Build your first renderer
Build commands, expected images, and the boundaries of the CPU companions.
# Parallax native workshop
A standard-library-only C++23 graphics reference with three small executables:
- `prism-render`: perspective sphere/ground rendering with ambient + Lambert
lighting and directional sphere shadows.
- `prism-raster`: screen-space triangles with barycentric color interpolation
and a depth buffer.
- `prism-memory`: a bounded PMR allocator and ownership experiment.
Scenes are typed C++ values authored in `src/scene.cpp` and `src/raster_lab.cpp`.
Every public rendering boundary validates those values before arithmetic,
allocation, or pixel access. There are no external dependencies or input parsers.
The browser's interactive models are teaching tools; these sources produce real
PPM images.
## Build and run
Requires a C++23 compiler/standard library and CMake 3.25 or later. Verified with
Apple Clang 21 and CMake 4.4.4 on macOS. The sources target standard C++ on macOS
and Linux; Linux has not been verified in this environment.
The standard library must supply `<expected>` as well as the compiler accepting
`-std=c++23`; a language-mode switch alone does not install library features.
From this `native` directory:
```sh
cmake --preset clang-debug
cmake --build --preset clang-debug
ctest --preset clang-debug
./build/clang-debug/prism-render studio.ppm
./build/clang-debug/prism-raster triangles.ppm
./build/clang-debug/prism-memory
```
These presets select Ninja and Clang, so both must be on your tool path. The
website's **Workshop → Build your studio** primer explains each stage from the
beginning. `clang-release` uses a separate Release tree; configure, build, and
test it with the same three commands and that preset name.
For AddressSanitizer and UndefinedBehaviorSanitizer with the Clang preset:
```sh
cmake --preset clang-sanitize
cmake --build --preset clang-sanitize
ctest --preset clang-sanitize
```
For a different compiler, use a new build tree and explicitly select the compiler
and generator. The equivalent sanitizer option is `-DPRISM_SANITIZERS=ON` for
Clang/GCC. Do not change compilers inside an existing configured tree. Personal
preset overrides belong in the untracked `CMakeUserPresets.json`.
Both image executables accept exactly one output path; `--help` prints usage.
Exit 0 means success; exit 1 means an argument, validation, allocation, or I/O
failure. Output files are replaced when valid rendering completes and writing
starts. A write failure can leave an incomplete output: this reference does not
claim atomic publication. Choose an output path you can replace.
## Follow the owners
`include/prism/scene.hpp` defines `Scene`, `Vec3`, `Color`, and error-returning
validation. `demo_scene()` returns an independent owner of its title and sphere
storage. Change that typed scene, then rebuild to run your experiment.
`src/renderer.cpp` constructs a perspective camera, intersects spheres and a
checker ground plane, shades direct light, and applies an explicit gamma-2
display transform. Its `Image` owns one RGB vector. `pixels()` returns a borrowed
const span only from an existing owner; requesting it from a temporary is
rejected at compile time. Pixel-center sampling and fixed inputs make repeated
renders deterministic for a given build. Cross-platform floating-point bit
identity is not promised.
The P6 header always uses ungrouped ASCII numbers, regardless of the application's
global locale. Its gamma-2 samples are a documented PPM variant, not the exact
BT.709 transfer specified by Netpbm or the exact sRGB transfer.
`src/raster_lab.cpp` starts with **already projected screen-space vertices**.
Each triangle owns three vertices with screen X/Y, depth, and linear RGB. The
rasterizer allocates one RGB image and one depth vector; edge functions produce
barycentric weights, and those weights interpolate color and depth. Smaller
depth wins independently of submission order; exact depth ties keep the first
sample. Both windings work. Degenerate triangles cover nothing. The pixel loop
is restricted to the triangle's box intersected with the viewport before any
index is formed. All edges are inclusive for this isolated-triangle reference;
a watertight mesh renderer needs a deliberately tested top-left edge rule.
`src/memory_lab.cpp` instruments a PMR interface with `CountingResource`. A
512-byte `monotonic_buffer_resource` with `null_memory_resource()` upstream
cannot grow onto the heap. A failed reserve preserves existing vector elements.
PMR containers die before `release()`, while an ordinary owning string remains
valid afterward. No invalidated view is read. Counters measure logical live
requested bytes, not metadata or arena physical usage. This teaching resource
is single-threaded; sharing it would require synchronization.
The ownership design applies the supplied C++ Core Guidelines' R.1 (RAII), R.5
(scoped objects), I.4 (typed interfaces), and ES.103 (avoid overflow). It uses no
owning raw pointers or manual `new`/`delete` in the renderer or PMR implementation.
The locale regression transfers a test facet directly to its standard-library
locale owner. Output streams close through RAII, and explicit close errors are
checked. These are selected design practices, not a claim of complete Guidelines
conformance. `Scene`, `Image`, and `RasterScene` are public candidate aggregates;
their cross-field invariants are checked at operation boundaries rather than
preserved by private class state (see C.2/C.40).
## Coordinate and budget contracts
Ray scenes use a right-handed world with Y up. The camera looks from `position`
toward `look_at` and cannot point directly parallel to world up.
`light_direction` points from a surface toward the light. Colors are linear RGB
in 0–1. Sphere radii are 0.001–100, world coordinates/ground height are finite
within ±1,000, and vertical field of view is 10–120 degrees.
Raster vertices use +X right and +Y down. Pixel centers are `(x + 0.5, y + 0.5)`.
Coordinates must be finite within ±8,192; partly off-screen triangles are allowed.
Depth must be finite in 0–1, with 0 near and 1 far. RGB is interpolated in screen
space: this is not a perspective-correct texture interpolator.
| Resource | Limit |
| --- | --- |
| Width and height | 1–2,048 each |
| Total pixels | 2,097,152 |
| Ray scene spheres | 32 |
| Ray pixels × spheres | 33,554,432 (shadows add at most the same sphere work again) |
| Raster triangles | 64 |
| Raster pixels × triangles | 16,777,216 |
| Scene title | 1–80 bytes, no ASCII controls |
Validation divides before multiplying to check combined work budgets. Validated
pixel counts bound RGB storage; raster depth storage is also bounded by the same
pixel count. These are deliberately small teaching budgets, not universal engine
limits.
The `expected` return values cover ordinary validation and reported I/O failures;
they do not make these functions `noexcept`. Standard-library allocation and
diagnostic construction can still throw. The command-line entry points supply
the final exception boundary. The fixed ray epsilon and near-degenerate raster
threshold are teaching policies, not proofs of robust predicates at every scale.
## Check the published C++ examples
The native build above needs no JavaScript runtime. For an additional curriculum
check from the **project root**, use the website's existing Bun installation and
choose a disposable output directory:
```sh
bun native/tests/verify_lesson_examples.ts clang++ ../../work/first-light-audit/examples
```
This compiles all 15 published First Light C++ examples directly from the lesson
source with C++23 and warnings treated as errors. The compiler's standard library
must implement `std::expected`. It supplies the explicitly named declarations for fragments, runs each example, checks the gradient's
independent expected values, exercises normalization failures, and checks the
published error producer and caller against known channel bytes, non-finite and
out-of-range inputs, error indices, and unchanged pixels after failure. It also
verifies that a temporary image cannot supply a borrowed view. GLSL and the annotated ray
equation are reviewed separately; they are not C++ translation units.
## Extension exercises
The reference implements shadows, raster coverage, interpolated depth, and RGB.
Reflections, a 3D raster camera, homogeneous clipping, perspective-correct UVs,
textures, GPU execution, and asynchronous tile scheduling are extension exercises.
A reflected-ray implementation needs a fixed bounce budget and tested secondary
ray offsets. A parallel renderer needs explicit task ownership, cancellation,
joining, and resource lifetime rules before sharing memory. The synchronous
references remain the correctness baseline.
The earlier optional JSON/Glaze experiment was archived outside this delivered
project when the course scope was corrected. It is not a dependency or part of
this graphics reference. Original downloaded attachments were left untouched.
This is the file included with the course. Copy and download give you its complete source.
Taking your work offline? Download all course companions
