summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/SDF_EFFECT_GUIDE.md217
-rw-r--r--src/gpu/gpu.h2
-rw-r--r--src/gpu/sdf_effect.h48
-rw-r--r--workspaces/main/assets.txt1
-rw-r--r--workspaces/main/shaders/sdf_test.wgsl68
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);
-}