diff --git a/AGENTS.md b/AGENTS.md index 34872ed..ee7eb5c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,11 +12,11 @@ ### Commands - ```sh - cmake -S . -B build -GNinja - ninja -C build - ./build/cuber - ``` +```sh +cmake -S . -B build -GNinja +ninja -C build +./build/cuber +``` ### Dependencies @@ -34,7 +34,8 @@ To add a new dependency: The project is split into static libraries: -- **`cbt_opengl`** — OpenGL abstraction and window management (window, context, buffer, texture, vao, shader, descriptor) +- **`cbt_opengl`** — OpenGL abstraction and window management (window, + context, buffer, texture, vao, shader, descriptor) - **`cbt_scene`** — Base scene class - **`scenes_cube`** — Spinning cube scene implementation - **`scenes_sphere`** — Cube-to-sphere mapped mesh with diffuse lighting @@ -52,10 +53,13 @@ to the custom scripts instead of system-installed packages. - **No semicolons after closing braces** for namespaces/classes - `auto` for obvious types (e.g. `auto main(...) -> int`) - **East const** (e.g. `char const*` not `const char*`) -- **Trailing return type** for all function definitions, including operators (e.g. `auto operator=(T&&) noexcept -> T&`) -- **Public members first** in class declarations, private members at the bottom +- **Trailing return type** for all function definitions, including + operators (e.g. `auto operator=(T&&) noexcept -> T&`) +- **Public members first** in class declarations, private members at the + bottom - `<>` includes only for system headers (std, OS, etc.) -- `""` includes for third-party dependencies (e.g. `fmt`, `nlohmann/json`) +- `""` includes for third-party dependencies (e.g. `fmt`, + `nlohmann/json`) - **Naming:** `snake_case` for variables, functions, and classes - **Naming:** `SCREAMING_SNAKE_CASE` only for macros and constants - Include order: @@ -82,21 +86,34 @@ to the custom scripts instead of system-installed packages. - Follow the 50/72 rule: - Subject line: max 50 characters - Body lines: wrapped at 72 characters -- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`, etc.) +- Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`, + etc.) - Separate subject from body with a blank line Example: - ``` - feat: add stopwatch timer +``` +feat: add stopwatch timer - Replace Hello World with a live stopwatch that prints elapsed time - in HH:MM:SS.mmm format, updating every 10ms with color output. - ``` +Replace Hello World with a live stopwatch that prints elapsed time +in HH:MM:SS.mmm format, updating every 10ms with color output. +``` + +## Documentation (Markdown) + +- Wrap normal text and lists at **max 80 columns** (for readability in + terminals and editors). +- **Exceptions**: Tables and code blocks (```` ``` ````) can exceed 80 + columns when formatting requires it (e.g. trees, alignment). +- Use standard Markdown: `**bold**`, `` `inline code` ``, `##` headings, + `-` or numbered lists, fenced code blocks with language hints + (```` ```cpp ````, ```` ```sh ````). +- Keep examples concise, up-to-date, and self-documenting. +- This file (`AGENTS.md`) follows its own rules. ## Source Layout -``` +```text cuber/ CMakeLists.txt # Build configuration cuber.cpp # Entry point diff --git a/README.md b/README.md index 8e907ce..7447bf6 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,112 @@ Q key Quit - **cube** — spinning colored cube with per-face colors - **sphere** — cube-to-sphere mapped mesh with per-face colors and - diffuse lighting + diffuse lighting (uses the new pipeline with render-to-texture + post-processing) + +## Pipeline Abstraction (`cbt::gfx`) + +The project now has a clean, easy-to-use **graphics pipeline** layer in +`cbt/gfx.hpp`. Think of it like a simple "drawing recipe" that hides +all the messy OpenGL details (shaders, buffers, VAOs, uniforms, +framebuffers). It's inspired by libraries like Sokol but made for C++ +with RAII classes, `pipeline_desc` structs, and beginner-friendly +methods. + +### Why it exists (for dummies) +- **No more copy-paste GL calls** in your scenes. +- **Easy to add effects**: Render to a texture (offscreen), then do + post-processing (blur, vignette, color grading, bloom, SSAO, etc.). +- **Future-proof**: The same code works if we add a Vulkan backend later + (just swap the internal `impl`—no changes to your scene code). +- **Switching scenes works smoothly** (no more glitches from leftover + GL state like depth test or bound textures). + +It abstracts the typical graphics pipeline stages you might see in +diagrams (vertex shader, rasterizer, pixel shader, output merger) into +one simple object. Post-processing is a second "step" after the main +draw. + +### How to use it (step-by-step for dummies) +1. **Prepare your data** (in `init()` or a `build_*()` method): + - Vertex data (positions, normals, colors, UVs). + - Index data (optional, for triangles). + - Attribute description (where each piece of data lives in the + vertex, e.g. location 0 = position at offset 0). + - Shader source code (vertex + fragment as raw strings). + +2. **Create a `pipeline_desc`** (the recipe): + ```cpp + gfx::pipeline_desc desc { + .vertex_data = std::as_bytes(std::span{my_vertices}), + .index_data = std::as_bytes(std::span{my_indices}), + .attributes = { + {.location = 0, .num_components = 3, .offset = 0}, // pos + {.location = 1, .num_components = 3, .offset = 12}, // normal + // ... more + }, + .vertex_stride = sizeof(MyVertex), + .vertex_shader_src = my_vert_src, + .fragment_shader_src = my_frag_src, + .depth_test = true, // on for 3D, off for 2D post-process + }; + ``` + +3. **Build the pipeline**: + ```cpp + m_pipeline = gfx::pipeline{desc}; + if (!m_pipeline.valid()) { /* error */ } + ``` + +4. **Draw it** (in `render()`): + ```cpp + m_pipeline.draw(model_matrix, view_matrix, proj_matrix); + ``` + +### Render-to-Texture + Post-Processing (the cool part) +Use `gfx::render_target` for offscreen rendering (like a temporary +canvas): + +```cpp +// In class +gfx::render_target m_rt{0, 0}; +gfx::pipeline m_post_pipeline; // second pipeline for effects + +// In init() +m_rt.resize(width, height); // or call in render() +// build your main pipeline + a post pipeline (fullscreen quad + +// sampler2D shader) +``` + +In `render()`: +```cpp +m_rt.resize(width, height); +m_rt.bind(); // draw to texture instead of screen +// ... clear, draw main scene pipeline ... +m_rt.unbind(); + +// Post-processing step +m_post_pipeline.bind_texture("u_texture", m_rt.color_id(), 0); +m_post_pipeline.draw(...); // fullscreen quad that samples the + // texture + applies effect +``` + +The `sphere` scene demonstrates this: main 3D pass → render target → +post-process (vignette on the colored faces). + +### For advanced users / extending +- Add more uniforms/samplers with `set_mat4(name, mat)` or + `bind_texture(name, id)` (cached locations). +- To add Vulkan: implement a new `impl` in `gfx.cpp` that uses + `VkPipeline`, `VkFramebuffer`, etc. (the public API stays identical). +- See `scenes/sphere.cpp` for a full example (including fullscreen + quad for post-processing). + +This keeps your scene code tiny and clean while giving you powerful +graphics features. + +## Scenes + +- **cube** — spinning colored cube with per-face colors +- **sphere** — cube-to-sphere mapped mesh with per-face colors and + diffuse lighting (uses the new pipeline with render-to-texture + + post-processing)