diff options
| -rw-r--r-- | doc/SDF_EFFECT_GUIDE.md | 217 | ||||
| -rw-r--r-- | src/gpu/gpu.h | 2 | ||||
| -rw-r--r-- | src/gpu/sdf_effect.h | 48 | ||||
| -rw-r--r-- | workspaces/main/assets.txt | 1 | ||||
| -rw-r--r-- | workspaces/main/shaders/sdf_test.wgsl | 68 |
5 files changed, 127 insertions, 209 deletions
diff --git a/doc/SDF_EFFECT_GUIDE.md b/doc/SDF_EFFECT_GUIDE.md index fba80e7..06511e5 100644 --- a/doc/SDF_EFFECT_GUIDE.md +++ b/doc/SDF_EFFECT_GUIDE.md @@ -1,86 +1,144 @@ # SDF Effect Guide -Streamlined workflow for SDF raymarching effects using the `SDFEffect` base class. +Workflow for SDF raymarching effects. Canonical example: `Scene1`. --- -## Quick Start +## C++ Pattern + +Inherit from `Effect`. Add `UniformBuffer<CameraParams> camera_params_` as a member, +passed as `effect_params` to `pp_update_bind_group`. ```cpp // src/effects/my_sdf_effect.h -class MySDFEffect : public SDFEffect { - MySDFEffect(const GpuContext& ctx); - void render(WGPURenderPassEncoder pass, - const CommonPostProcessUniforms& uniforms) override; - RenderPass pass_; +#pragma once +#include "gpu/camera_params.h" +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" +#include "gpu/wgpu_resource.h" + +class MySdfEffect : public Effect { + public: + MySdfEffect(const GpuContext& ctx, const std::vector<std::string>& inputs, + const std::vector<std::string>& outputs, float start_time, + float end_time); + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + RenderPipeline pipeline_; + BindGroup bind_group_; + UniformBuffer<CameraParams> camera_params_; }; ``` ```cpp // src/effects/my_sdf_effect.cc #include "effects/my_sdf_effect.h" +#include "effects/shaders.h" #include "gpu/gpu.h" -#include "gpu/shaders.h" +#include "gpu/post_process_helper.h" +#include "util/mini_math.h" + +MySdfEffect::MySdfEffect(const GpuContext& ctx, + const std::vector<std::string>& inputs, + const std::vector<std::string>& outputs, + float start_time, float end_time) + : Effect(ctx, inputs, outputs, start_time, end_time) { + HEADLESS_RETURN_IF_NULL(ctx_.device); + + create_nearest_sampler(); + create_dummy_scene_texture(); + + camera_params_.init(ctx_.device); -MySDFEffect::MySDFEffect(const GpuContext& ctx) : SDFEffect(ctx) { - ResourceBinding bindings[] = { - {uniforms_.get(), WGPUBufferBindingType_Uniform}, - {camera_params_.get(), WGPUBufferBindingType_Uniform}}; - pass_ = gpu_create_render_pass(ctx_.device, ctx_.format, - my_sdf_shader_wgsl, bindings, 2); - pass_.vertex_count = 3; + pipeline_.set(create_post_process_pipeline( + ctx_.device, WGPUTextureFormat_RGBA8Unorm, my_sdf_shader_wgsl)); } -void MySDFEffect::render(WGPURenderPassEncoder pass, - const CommonPostProcessUniforms& uniforms) { - uniforms_.update(ctx_.queue, uniforms); +void MySdfEffect::render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) { + // Update camera (orbiting example) + CameraParams cam; + const float R = 6.0f; + const vec3 ro(R * sin(params.time * 0.5f), 2.0f, R * cos(params.time * 0.5f)); + cam.inv_view = mat4::look_at(ro, vec3(0, 0, 0), vec3(0, 1, 0)).inverse(); + cam.fov = 1.0472f; // 60 degrees + cam.near_plane = 0.1f; + cam.far_plane = 100.0f; + cam.aspect_ratio = 1.0f; // aspect handled in shader + camera_params_.update(ctx_.queue, cam); - // Orbiting camera - vec3 cam_pos(std::cos(uniforms.time * 0.5f) * 5.0f, 2.0f, - std::sin(uniforms.time * 0.5f) * 5.0f); - update_camera(cam_pos, vec3(0, 0, 0), vec3(0, 1, 0), 0.785398f, 0.1f, 100.0f, - uniforms.aspect_ratio); + WGPUTextureView output_view = nodes.get_view(output_nodes_[0]); - wgpuRenderPassEncoderSetPipeline(pass, pass_.pipeline); - wgpuRenderPassEncoderSetBindGroup(pass, 0, pass_.bind_group, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, pass_.vertex_count, 1, 0, 0); + pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(), + dummy_texture_view_.get(), uniforms_buffer_.get(), + camera_params_.get()); + + WGPURenderPassColorAttachment color_attachment = {}; + gpu_init_color_attachment(color_attachment, output_view); + + WGPURenderPassDescriptor pass_desc = {}; + pass_desc.colorAttachmentCount = 1; + pass_desc.colorAttachments = &color_attachment; + + WGPURenderPassEncoder pass = + wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get()); + wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); } ``` +--- + +## WGSL Pattern + +Binding layout (standard post-process): +- `@binding(0)` — sampler (unused for SDF, but layout requires it) +- `@binding(1)` — input texture (unused for SDF) +- `@binding(2)` — `UniformsSequenceParams` +- `@binding(3)` — `CameraParams` + ```wgsl // workspaces/main/shaders/my_sdf.wgsl -#include "common_uniforms" +#include "sequence_uniforms" #include "camera_common" #include "math/sdf_shapes" #include "render/raymarching" -@group(0) @binding(0) var<uniform> uniforms: CommonUniforms; -@group(0) @binding(1) var<uniform> camera: CameraParams; +@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams; +@group(0) @binding(3) var<uniform> camera: CameraParams; -fn df(p: vec3<f32>) -> f32 { +fn df(p: vec3f) -> f32 { return sdSphere(p, 1.0); } @vertex -fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> { +fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f { let x = f32((vid & 1u) << 2u) - 1.0; let y = f32((vid & 2u) << 1u) - 1.0; - return vec4<f32>(x, y, 0.0, 1.0); + return vec4f(x, y, 0.0, 1.0); } @fragment -fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> { - let uv = (pos.xy / uniforms.resolution - 0.5) * 2.0; +fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f { + let uv = (pos.xy / uniforms.resolution - 0.5) * 2.0 + * vec2f(uniforms.aspect_ratio, 1.0); let ray = getCameraRay(camera, uv); let t = rayMarch(ray.origin, ray.direction, 0.0); - var col = vec3<f32>(0.1); + var col = vec3f(0.1); if (t < MAX_RAY_LENGTH) { let hit_pos = ray.origin + ray.direction * t; let n = normal(hit_pos); - col = vec3<f32>(n * 0.5 + 0.5); + col = n * 0.5 + 0.5; } - return vec4<f32>(col, 1.0); + return vec4f(col, 1.0); } ``` @@ -88,77 +146,54 @@ fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> { ## Available Uniforms -### CommonUniforms (binding 0) -- `resolution`: vec2 (screen size) -- `time`: float (physical seconds) -- `beat_time`: float (musical beats) -- `beat_phase`: float (0-1 within beat) -- `audio_intensity`: float (peak) -- `aspect_ratio`: float +### UniformsSequenceParams (binding 2) +- `resolution`: vec2f +- `time`: f32 (physical seconds) +- `beat_time`: f32 (musical beats) +- `beat_phase`: f32 (0–1 within beat) +- `audio_intensity`: f32 +- `aspect_ratio`: f32 -### CameraParams (binding 1) -- `inv_view`: mat4x4 (inverse view matrix) -- `fov`: float (vertical FOV in radians) -- `near_plane`, `far_plane`: float -- `aspect_ratio`: float +### CameraParams (binding 3) +- `inv_view`: mat4x4f (inverse view matrix) +- `fov`: f32 (vertical FOV, radians) +- `near_plane`, `far_plane`: f32 +- `aspect_ratio`: f32 --- ## WGSL Helpers -From `camera_common.wgsl`: - +### `camera_common` ```wgsl -fn getCameraRay(cam: CameraParams, uv: vec2<f32>) -> Ray; -fn getCameraPosition(cam: CameraParams) -> vec3<f32>; -fn getCameraForward(cam: CameraParams) -> vec3<f32>; -fn getCameraUp(cam: CameraParams) -> vec3<f32>; -fn getCameraRight(cam: CameraParams) -> vec3<f32>; +fn getCameraRay(cam: CameraParams, uv: vec2f) -> Ray; +fn getCameraPosition(cam: CameraParams) -> vec3f; +fn getCameraForward(cam: CameraParams) -> vec3f; ``` -From `render/raymarching.wgsl`: - +### `render/raymarching` ```wgsl -fn rayMarch(ro: vec3<f32>, rd: vec3<f32>, initt: f32) -> f32; -fn normal(pos: vec3<f32>) -> vec3<f32>; -fn shadow(lp: vec3<f32>, ld: vec3<f32>, mint: f32, maxt: f32) -> f32; +fn rayMarch(ro: vec3f, rd: vec3f, initt: f32) -> f32; +fn normal(pos: vec3f) -> vec3f; +fn shadow(lp: vec3f, ld: vec3f, mint: f32, maxt: f32) -> f32; ``` -From `math/sdf_shapes.wgsl`: - +### `math/sdf_shapes` ```wgsl -fn sdSphere(p: vec3<f32>, r: float) -> f32; -fn sdBox(p: vec3<f32>, b: vec3<f32>) -> f32; -fn sdTorus(p: vec3<f32>, t: vec2<f32>) -> f32; -fn sdPlane(p: vec3<f32>, n: vec3<f32>, h: f32) -> f32; -``` - ---- - -## Camera Control - -```cpp -// Method 1: Manual values -update_camera(position, target, up, fov, near, far, aspect); - -// Method 2: Camera object -Camera cam; -cam.position = vec3(0, 5, 10); -cam.target = vec3(0, 0, 0); -update_camera(cam, uniforms.aspect_ratio); +fn sdSphere(p: vec3f, r: f32) -> f32; +fn sdBox(p: vec3f, b: vec3f) -> f32; +fn sdTorus(p: vec3f, t: vec2f) -> f32; +fn sdPlane(p: vec3f, n: vec3f, h: f32) -> f32; ``` --- ## Registration Checklist +Follow `doc/EFFECT_WORKFLOW.md`. SDF-specific notes: 1. Add shader to `workspaces/main/assets.txt` -2. Add extern declaration to `src/gpu/shaders.h` -3. Add definition to `src/gpu/shaders.cc` -4. Add `.cc` to `cmake/DemoSourceLists.cmake` (both headless & normal) -5. Include header in `src/gpu/demo_effects.h` -6. Add to `src/tests/gpu/test_demo_effects.cc` - ---- - -## Example: workspaces/main/shaders/sdf_test.wgsl +2. Add `.cc` to `CMakeLists.txt` GPU_SOURCES (**both** headless and normal sections) +3. Include header in `src/gpu/demo_effects.h` +4. Add to `workspaces/main/timeline.seq` with priority modifier (`+`, `=`, or `-`) +5. Add to `src/tests/gpu/test_demo_effects.cc` under `scene_effects` +6. Build: `cmake --build build -j4 && cd build && ./test_demo_effects` diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h index d6c0255..7754b29 100644 --- a/src/gpu/gpu.h +++ b/src/gpu/gpu.h @@ -104,4 +104,4 @@ WGPUSampler gpu_create_linear_sampler(WGPUDevice device); WGPUSampler gpu_create_nearest_sampler(WGPUDevice device); // Dummy 1x1 texture for scene effects (don't need texture input) -TextureWithView gpu_create_dummy_scene_texture(WGPUDevice device);
\ No newline at end of file +TextureWithView gpu_create_dummy_scene_texture(WGPUDevice device); diff --git a/src/gpu/sdf_effect.h b/src/gpu/sdf_effect.h deleted file mode 100644 index 12ea556..0000000 --- a/src/gpu/sdf_effect.h +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of the 64k demo project. -// SDF raymarching effect base class. - -#pragma once - -#include "3d/camera.h" -#include "gpu/camera_params.h" -#include "gpu/effect.h" -#include "gpu/uniform_helper.h" - -// Binding convention: -// @group(0) @binding(2): UniformsSequenceParams -// @group(0) @binding(3): CameraParams -// @group(0) @binding(4+): Per-effect custom parameters -class SDFEffect : public Effect { - public: - SDFEffect(const GpuContext& ctx) : Effect(ctx) { - camera_params_.init(ctx.device); - } - - virtual ~SDFEffect() = default; - - void update_camera(const Camera& camera, float aspect_ratio) { - CameraParams params; - params.inv_view = camera.get_view_matrix().inverse(); - params.fov = camera.fov_y_rad; - params.near_plane = camera.near_plane; - params.far_plane = camera.far_plane; - params.aspect_ratio = aspect_ratio; - camera_params_.update(ctx_.queue, params); - } - - void update_camera(const vec3& position, const vec3& target, const vec3& up, - float fov, float near_plane, float far_plane, - float aspect_ratio) { - mat4 view = mat4::look_at(position, target, up); - CameraParams params; - params.inv_view = view.inverse(); - params.fov = fov; - params.near_plane = near_plane; - params.far_plane = far_plane; - params.aspect_ratio = aspect_ratio; - camera_params_.update(ctx_.queue, params); - } - - protected: - UniformBuffer<CameraParams> camera_params_; -}; diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt index ba28b04..4115397 100644 --- a/workspaces/main/assets.txt +++ b/workspaces/main/assets.txt @@ -54,7 +54,6 @@ SHADER_SOLARIZE, NONE, shaders/solarize.wgsl, "Solarize Shader" SHADER_DISTORT, NONE, shaders/distort.wgsl, "Distort Shader" SHADER_CHROMA_ABERRATION, NONE, shaders/chroma_aberration.wgsl, "Chroma Aberration Shader" SHADER_VISUAL_DEBUG, NONE, shaders/visual_debug.wgsl, "Visual Debug Shader" -SHADER_SDF_TEST, NONE, shaders/sdf_test.wgsl, "SDF test effect demonstrating SDFEffect base class" SHADER_SKYBOX, NONE, ../../common/shaders/skybox.wgsl, "Skybox background shader" SHADER_MATH_SDF_SHAPES, NONE, ../../common/shaders/math/sdf_shapes.wgsl, "SDF Shapes Snippet" SHADER_MATH_SDF_UTILS, NONE, ../../common/shaders/math/sdf_utils.wgsl, "SDF Utils Snippet" diff --git a/workspaces/main/shaders/sdf_test.wgsl b/workspaces/main/shaders/sdf_test.wgsl deleted file mode 100644 index 941481e..0000000 --- a/workspaces/main/shaders/sdf_test.wgsl +++ /dev/null @@ -1,68 +0,0 @@ -// SDF Test Effect - demonstrates SDFEffect base class usage -// Simple scene with a sphere and box - -#include "common_uniforms" -#include "camera_common" -#include "math/sdf_shapes" -#include "render/raymarching" - -@group(0) @binding(0) var<uniform> uniforms: CommonUniforms; -@group(0) @binding(1) var<uniform> camera: CameraParams; - -// Scene distance function -fn df(p: vec3f) -> f32 { - // Animated sphere - let sphere_pos = vec3f(sin(uniforms.beat_time * 0.5) * 2.0, 0.0, 0.0); - let d_sphere = sdSphere(p - sphere_pos, 1.0); - - // Static box - let box_pos = vec3f(0.0, -2.0, 0.0); - let d_box = sdBox(p - box_pos, vec3f(3.0, 0.5, 3.0)); - - return min(d_sphere, d_box); -} - -// Simple lighting -fn shade(pos: vec3f, rd: vec3f) -> vec3f { - let n = normal(pos); - let light_dir = normalize(vec3f(1.0, 1.0, -1.0)); - let diff = max(dot(n, light_dir), 0.0); - let amb = 0.2; - - // Color based on position - let col = mix(vec3f(0.2, 0.6, 0.9), vec3f(0.9, 0.3, 0.2), - smoothstep(-2.0, 2.0, pos.x)); - - return col * (diff + amb); -} - -@vertex -fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f { - // Fullscreen triangle - let x = f32((vid & 1u) << 2u) - 1.0; - let y = f32((vid & 2u) << 1u) - 1.0; - return vec4f(x, y, 0.0, 1.0); -} - -@fragment -fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f { - // UV coordinates (-1 to 1) - let uv = (pos.xy / uniforms.resolution - 0.5) * 2.0; - - // Generate ray - let ray = getCameraRay(camera, uv); - - // Raymarch - let t = rayMarch(ray.origin, ray.direction, 0.0); - - // Background color - var col = vec3f(0.1, 0.1, 0.15); - - // Shade hit point - if (t < MAX_RAY_LENGTH) { - let hit_pos = ray.origin + ray.direction * t; - col = shade(hit_pos, ray.direction); - } - - return vec4f(col, 1.0); -} |
