# Chromatic Observatory — Course 2 native starter An original C++23 / OpenGL 3.3 core / GLSL 330 starter for **The Shader Workshop**. It creates a GLFW context, loads functions with GLEW, owns GL names with move-only objects, validates shader compile/link results and a required uniform, uploads an interleaved VAO/VBO, and draws a rotating colored triangle. The `--camera` checkpoint connects tested model/view/projection matrices to three overlapping exhibits, depth testing, orbit controls and real framebuffer resize. Hidden smoke modes capture actual P6 PPMs. This is the beginning of the sixteen-week project. Indexed panels, a complete room, texture upload, the characterized linear/sRGB path, materials, offscreen passes, transparent sorting, shadow mapping, instancing, and interactive shader reload are progressive learner extensions taught in the course. They are not implemented by this starter. The CPU contract helpers include normalization, matrix multiplication, Z rotation, translation, look-at views, homogeneous point/direction transforms, a supported perspective projection, and bounded capture sizes. ## 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. ## Local dependencies - A C++23 compiler and standard library with `std::expected`. - CMake 3.25 or newer. - An existing GLFW installation, version 3.3 or newer, with its CMake package. - An existing GLEW installation and OpenGL development libraries/driver. CMake discovers these dependencies locally. It does not download packages, install system software, or modify the parent CPU renderer project. If discovery fails, point `CMAKE_PREFIX_PATH` at an existing dependency prefix or configure your target machine's development environment deliberately. Keep compiler, dependency, and driver evidence with the project rather than assuming a setup command is universal. AMD on CachyOS is the course's primary target. The starter does not assume a Mesa version, session backend, or driver capability based on the GPU brand; record the actual renderer/version on that machine. OpenGL 3.3 core is the required baseline. OpenGL 4.3 compute and core debug callbacks are not required. macOS is a compatibility path. Apple deprecated OpenGL in macOS 10.14; its native implementation supports at most OpenGL 4.1. The starter requests a forward-compatible core context on Apple. Its local verification below is not AMD/Linux verification. ## Build and run From this directory: ```sh cmake -S . -G Ninja -DCMAKE_CXX_COMPILER=clang++ -B build -DCMAKE_BUILD_TYPE=Debug cmake --build build --parallel ctest --test-dir build --output-on-failure ./build/observatory ``` Escape or the window close button ends the interactive application. ```sh ./build/observatory --smoke ./build/observatory --capture observatory.ppm ./build/observatory --bad-shader ./build/observatory --check-stage fragment path/to/study.frag ./build/observatory --check-program path/to/study.vert path/to/study.frag ``` `--smoke` creates a hidden window, draws one frame, and exits. It still requires a working desktop display/context service; “hidden” does not mean display-independent. `--capture` also uses one hidden frame and writes the specified path, replacing an existing file. It reads the back buffer before presentation, validates the capture extent/byte budget, uses tightly packed RGB rows, and reverses the row order for PPM. It checks write and close status. Image viewers differ in their PPM support. `--bad-shader` deliberately returns failure with a GLSL compiler diagnostic. It is an error-path demonstration, not a passing visual test. Initial source files are bounded to 64 KiB, and compiler logs are bounded. The application catches ordinary exceptions at its outer boundary and reports failure on standard error. The shader-check commands create a hidden context too. `--check-stage` compiles a single vertex or fragment stage. `--check-program` compiles and links a supplied pair. They do not validate application-required uniforms, update values, draw the extension, or establish that its visual algorithm is correct. A complete vertex stage can require a different compatible fragment stage from the starter's. Development shader paths are compiled from this source directory so that launching from a different working directory works. A relocated binary still needs that source asset path; a packaged application would need a deliberate asset-location and installation policy. ## Ordered camera checkpoints Use the chapter-1 repository-root build, or the equivalent commands above from this directory. The following commands assume the local `build` directory. 1. **Predict one point.** Open `contracts.hpp` and `contracts_test.cpp`. Run `./build/observatory-contracts`: 25 CPU checks, no context. Eye `(0,0,4)` maps world origin to `(0,0,-4,1)` and its own position to `(0,0,0,1)`. Directions with homogeneous w=0 do not translate. The tests also cover a side view, a known frustum edge, projection endpoints and invalid/degenerate inputs. 2. **Draw the tested chain.** Open `main.cpp` and `shaders/camera.vert`. Run `./build/observatory --camera --capture camera.ppm`. Expect three colored triangles at distinct translations/depths on a dark background. The shader evaluates projection × view × model × position; the CPU uploads column-major storage with transpose=false. All three active uniforms are required. 3. **Make depth visible.** Run `./build/observatory --camera --reverse-draw --capture reversed.ppm`, then `cmp camera.ppm reversed.ppm`. On the same local host the opaque noncoplanar scene has identical bytes. If order changes visibility, inspect the depth attachment, test enable, clear and write state before altering camera math. This is not a transparent-order or cross-driver hash guarantee. 4. **Resize for real.** Run `./build/observatory --resize-smoke --capture resized.ppm`. It draws once, requests 640×480 logical size, waits up to two seconds for a changed pixel extent, rebuilds projection and viewport, checks four-to-three aspect and captures the second draw. The local scaled display reported 1760×1200 then 1280×960; your pixel dimensions can differ. 5. **Find home.** Run `./build/observatory --camera`. Arrow keys orbit, W/S zoom, R resets yaw=0/pitch=0/distance=4, and Escape closes. Input is inactive while unfocused, pitch is clamped to ±1.2 radians, distance to [1.5,10], and frame delta to 0.05 seconds. A zero-size drawable waits for events and resets the motion clock. Manually check minimize/restore/focus/reset on your own desktop. Then replace an exhibit with your own indexed panel or box while preserving the three-exhibit scene and CPU tests. The core depth query uses `glGetFramebufferAttachmentParameteriv` with the default framebuffer's `GL_DEPTH` attachment and `GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE`. A legacy `GL_DEPTH_BITS` integer query failed during actual core-context testing and was corrected. The renderer validates the actual depth attachment, enables `GL_LESS`, and clears color/depth before each opaque draw. No legacy fixed-function matrix stack is used. For supported Clang/GCC sanitizer builds from this directory: ```sh cmake -S . -B build-sanitize -G Ninja -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Debug -DOBSERVATORY_SANITIZERS=ON cmake --build build-sanitize --parallel ctest --test-dir build-sanitize --output-on-failure ./build-sanitize/observatory --resize-smoke ``` Sanitizer CTest checks host mathematics; the last command also exercises the real context path. Sanitizers do not validate every GPU access or prove memory safety. ## Ownership and limits The stack order is GLFW session → window/context → inner GL owners. All GL owners are destroyed before their context/window. The current context stays on the same thread for the lifetime of these owners. Their Debug assertions check that contract; the wrappers do not constitute a general context scheduler or GPU lifetime checker. Object names are not copied. Moving transfers the name and leaves a zero name in the source owner. The initial vertex data is fixed and trusted. Any external mesh extension must validate counts, index ranges, byte products, and GL integer conversions before upload/draw. Capture accepts positive dimensions up to 8192 per side and at most 16,777,216 pixels. A minimized framebuffer is skipped in the interactive loop. The first palette is artistic. It is not a characterized linear/sRGB output path; chapter 5 teaches the learner to establish that contract. RAII does not make C++ memory-safe, prove indices correct, or establish completion of queued GPU work. ## Verification performed Verified locally on 2026-10-24: - AppleClang 21.0.0.21000334, C++23, Debug build; warning flags enabled; build completed without diagnostics. - Locally discovered GLFW 3.5.1 and GLEW 2.3.1; no dependency installation. - `ctest`: one target passed, exercising **25 CPU contracts**, including zero/non-finite normalization, rotation, projection bounds and near/far mapping, exact capture budget and an over-budget row. This target creates no graphics context. - Actual hidden-context capture returned zero and reported `GL_VERSION: 4.1 Metal - 91.7` and `GL_RENDERER: Apple M4 Pro`. The resulting 1760 × 1200 framebuffer PPM was converted for visual inspection and showed the expected colored triangle on the dark background. Framebuffer size differed from the logical 880 × 600 window. - Intentional invalid GLSL returned one with a stage compiler diagnostic. Debug context-lifetime assertions remained satisfied during error unwinding. - Both complete GLSL course snippets compiled in actual desktop contexts. The chapter 3 pulse fragment also linked with the starter vertex stage; the chapter 4 position-only camera vertex stage linked with a compatible constant-output fragment stage. These checks establish stage/program validity, not application uniform updates or visual correctness of those learner extensions. The first sandboxed desktop run lacked WindowServer service access. The verified graphics runs used the tool's approved desktop access, and the blocked first process was stopped. This distinguishes an execution-environment restriction from a driver or shader failure. The new camera checkpoint additionally builds and passes CPU contracts in strict Debug, Release and ASan/UBSan configurations. Real Debug/Release camera captures match on this host; normal/reversed submission captures match; a real resize passes in both Debug and ASan/UBSan without diagnostics. Captures were visually inspected. See `audits/renderer-cpu-opengl-update.md`. No AMD/CachyOS execution, native Windows execution, manual minimize/focus testing, shader hot reload, advanced rendering extensions, or general memory/security audit is claimed here. Perform those checks when the relevant project stages and target machine are available. ## Sources and further reading Course prose, scene/project, code, exercises, and week plans are original. These sources document API contracts or provide an optional comparison: - [GLFW getting started](https://www.glfw.org/docs/latest/quick.html) and [context guide](https://www.glfw.org/docs/latest/context_guide.html). - [Khronos OpenGL 3.3 core specification](https://registry.khronos.org/OpenGL/specs/gl/glspec33.core.pdf). - [Khronos GLSL 3.30 specification](https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.3.30.pdf). - [Apple's archived OpenGL guide and deprecation notice](https://developer.apple.com/opengl/). - [C++ Core Guidelines: resource ownership](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#rr-raii). - [LearnOpenGL](https://learnopengl.com/): corresponding chapter links are attached to the original course lessons; the learner does not need to leave the course to get the explanations or progressive assignments.