# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a cross-platform Shadertoy-like fragment shader viewer built with SDL3 + OpenGL 3.3. The application loads and displays GLSL shaders from the `shaders/` directory with runtime switching capabilities. ## Build and Development Commands ### Building (CMake - Development) ```bash mkdir build && cd build cmake .. cmake --build . --config Release ``` ### Building (Makefile - Release Packages) ```bash make windows_release # Creates .zip with DLLs make macos_release # Creates .dmg with app bundle make linux_release # Creates .tar.gz make show_version # Display build version (YYYY-MM-DD format) ``` Platform-specific debug builds: ```bash make windows_debug make macos_debug make linux_debug ``` ### Running the Application ```bash ./shadertoy [SHADER_PATH] [-F|--fullscreen] # Examples: ./shadertoy shaders/test.frag.glsl ./shadertoy -F shaders/fractal_pyramid.frag.glsl ``` **Runtime Controls:** - `ESC` - Exit - `F3` - Toggle fullscreen - `LEFT/RIGHT ARROW` - Cycle through shaders in directory ## Architecture ### Core Design All application logic resides in `src/main.cpp` (~469 lines) - a monolithic design that's straightforward to understand. Key components: 1. **Shader Loading System** - Automatic directory scanning of `.glsl` files, sorted alphabetically 2. **OpenGL Rendering** - Single fullscreen quad with fragment shader using GL_TRIANGLE_STRIP 3. **Event Loop** - SDL3-based with vsync (SDL_GL_SwapWindow + 1ms delay) 4. **Resource Path Resolution** - Multi-path fallback system for executable, relative, and macOS bundle paths ### Global State (main.cpp) ```cpp shader_list_ // Vector of discovered .glsl shader paths current_shader_index_ // Active shader in rotation current_program_ // OpenGL shader program handle shader_start_ticks_ // Base time for iTime uniform calculation window_ // SDL3 window pointer shaders_directory_ // Shader directory path (resolved at startup) ``` ### Dependencies - **SDL3** - Window/input management, OpenGL context - **GLAD** - OpenGL 3.3 loader (statically linked via `third_party/glad/`) - **C++17 stdlib** - filesystem, fstream, vector, algorithms ### Platform-Specific Code Uses preprocessor defines (`WINDOWS_BUILD`, `MACOS_BUILD`, `LINUX_BUILD`) for: - `getExecutableDirectory()` - Windows API, mach-o dyld, or /proc/self/exe - `getResourcesDirectory()` - Special macOS app bundle handling (Contents/Resources) - Linking flags - Windows uses static linking, macOS links OpenGL framework ## Shader System ### Shader Format (GLSL 3.3 Core) All shaders must follow this structure: ```glsl #version 330 core precision highp float; out vec4 FragColor; in vec2 vUV; // Normalized [0,1] coordinates from vertex shader uniform vec2 iResolution; // Window resolution in pixels uniform float iTime; // Time since shader loaded (seconds) // Shadertoy-style entry point void mainImage(out vec4 fragColor, in vec2 fragCoord) { // fragCoord is in pixel coordinates // Your shader code here } // Wrapper converts vUV to pixel coordinates void main() { vec2 fragCoordPixels = vUV * iResolution; vec4 outColor; mainImage(outColor, fragCoordPixels); FragColor = outColor; } ``` ### Adding New Shaders 1. Create `.glsl` file in `shaders/` directory (use `.frag.glsl` convention) 2. Follow required format above 3. File automatically appears in runtime shader rotation (arrow keys to navigate) 4. **No code changes required** - directory is scanned at startup ### Shader Loading Pipeline 1. Directory scan on startup (`scanShaderDirectory()`) 2. Sort alphabetically 3. Load fragment shader source from disk 4. Compile vertex shader (fixed, embedded in main.cpp) 5. Compile fragment shader with error logging 6. Link program with error handling 7. Update uniforms each frame (iResolution, iTime) ### Supported Shadertoy Features - ✅ `iTime` - Time uniform - ✅ `iResolution` - Window resolution (vec2, not vec3) - ✅ `mainImage()` function signature - ❌ `iMouse` - Not implemented - ❌ `iChannel0-3` - No texture channels (multi-pass not supported) - ❌ `iFrame`, `iTimeDelta`, `iDate` - Not implemented ### Converting from Shadertoy When porting Shadertoy shaders: 1. Copy the `mainImage()` function as-is 2. Add standard header (see format above) 3. Add wrapper `main()` function 4. Remove texture channel references (iChannel0-3) 5. Change `iResolution.xy` to `iResolution` (this project uses vec2) ### Common Conversion Issues and Solutions Based on experience converting complex shaders like ddla_light_tunnel: #### iResolution vec3 vs vec2 - **Shadertoy:** `iResolution` is `vec3(width, height, width/height)` - **This project:** `iResolution` is `vec2(width, height)` - **Solution:** Create vec3 manually: `vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);` - **Then use:** `r.xy` for resolution, `r.y` for height (as in original) #### Uninitialized Variables - **Problem:** Shadertoy code may have `vec3 rgb;` without initialization - **Shadertoy behavior:** Likely initializes to `vec3(0.0)` (black) - **This project:** Uninitialized variables contain undefined values (often causes black screen or wrong colors) - **Solution:** Always initialize: `vec3 rgb = vec3(0.0);` - **Wrong approach:** Don't use random noise unless shader explicitly uses iChannel texture for noise #### Low mix() Factors Are Intentional - **Example:** `rgb = mix(rgb, calculated_color, 0.01);` means 1% new color, 99% existing - **Don't change these factors** - they create subtle effects intentionally - **If output is black:** Problem is likely the base value (rgb), not the mix factor #### iChannel Textures - **Shadertoy shows iChannel0-3** in UI, but shader may not use them - **If iChannel3 has RGB noise but code doesn't reference it:** The noise is not actually used - **Check code for `texture(iChannelN, ...)` calls** - if none exist, ignore the iChannel setup - **Procedural noise replacement:** Only if shader explicitly samples the texture #### Color Swapping Issues - **If colors are completely wrong:** Don't randomly swap color variables - **First verify:** Code matches original Shadertoy exactly (except required GLSL changes) - **Common mistake:** Changing color applications that were correct in original - **Debug approach:** Revert to exact original code, only modify for GLSL 3.3 compatibility #### Division by Small Values - Color Overflow Artifacts - **Problem:** Division by values near zero causes extreme values → color overflow artifacts (green points, strange colors) - **Example:** `.01*vec4(6,2,1,0)/length(u*sin(iTime))` can divide by ~0.0 when near center and sin≈0 - **Symptoms:** Bright white center that pulses to green, or random color artifacts in specific areas - **Root cause:** Even with tonemap (tanh/clamp), extreme intermediate values cause precision issues or saturation - **Solution - Double Protection:** 1. **Add epsilon to denominator:** `max(denominator, 0.001)` prevents division by exact zero 2. **Clamp the result:** `min(result, vec4(50.0))` prevents extreme accumulation 3. **Only clamp problematic terms:** Don't clamp everything or scene becomes dim - **Example fix:** ```glsl // Original (causes overflow): o += .01*vec4(6,2,1,0)/length(u*sin(t+t+t)) + 1./s * length(u); // Fixed (prevents overflow while maintaining brightness): vec4 brightTerm = min(.01*vec4(6,2,1,0)/max(length(u*sin(t+t+t)), 0.001), vec4(50.0)); o += brightTerm + 1./s * length(u); ``` - **Key values:** epsilon ~0.001 (not too small, not too large), clamp ~50.0 (allows brightness without explosion) - **Why this works in Shadertoy:** WebGL/browsers may handle float overflow differently than native OpenGL drivers #### Debugging Black/Wrong Output 1. **Check compilation errors first** - shader must compile without errors 2. **Initialize all variables** - especially vec3 colors 3. **Verify vec3 iResolution handling** - create vec3 from vec2 if needed 4. **Don't modify mix factors** - keep original values 5. **Compare with original code** - ensure logic is identical 6. **Test progressive changes** - add header, test; add wrapper, test; etc. 7. **Check for division by small values** - if you see color artifacts (especially green), look for divisions that can approach zero ## Build System Details ### Version Numbering Releases use build date format: `shadertoy-YYYY-MM-DD-{platform}` (auto-generated by Makefile) ### Release Artifacts - **Windows:** `.zip` with `shadertoy.exe` + `SDL3.dll` + shaders - **macOS:** `.dmg` with app bundle containing embedded SDL3.framework (arm64 only) - **Linux:** `.tar.gz` with binary + shaders ### Resource Bundling - Shaders copied to release packages - LICENSE and README.md included - Platform-specific dependencies bundled (DLLs on Windows, frameworks on macOS) ## Important Notes ### Vertex Shader The vertex shader is hardcoded in `main.cpp` and creates a fullscreen quad. It: - Takes `vec2 aPos` (location 0) in NDC space [-1, 1] - Outputs `vec2 vUV` normalized to [0, 1] - No transformation matrices needed ### Shader Hot-Reloading Currently **not implemented**. Shader changes require application restart. The architecture would support adding this via file watching. ### Multi-Pass Rendering Single-pass only. To add multi-pass (BufferA/B/C like Shadertoy): - Create FBOs and textures per buffer - Render buffers in dependency order - Pass textures as `iChannel0-3` uniforms - Use ping-pong for feedback loops ### OpenGL Context Created via SDL3 with core profile (no deprecated functions). Context version: 3.3 core.