summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt2
-rw-r--r--assets/demo.seq5
-rw-r--r--assets/final/demo_assets.txt3
-rw-r--r--assets/final/shaders/chroma_aberration.wgsl20
-rw-r--r--assets/final/shaders/circle_mask_compute.wgsl22
-rw-r--r--assets/final/shaders/circle_mask_render.wgsl15
-rw-r--r--assets/final/shaders/common_uniforms.wgsl7
-rw-r--r--assets/final/shaders/distort.wgsl12
-rw-r--r--assets/final/shaders/ellipse.wgsl10
-rw-r--r--assets/final/shaders/gaussian_blur.wgsl22
-rw-r--r--assets/final/shaders/main_shader.wgsl14
-rw-r--r--assets/final/shaders/masked_cube.wgsl9
-rw-r--r--assets/final/shaders/particle_compute.wgsl11
-rw-r--r--assets/final/shaders/particle_render.wgsl11
-rw-r--r--assets/final/shaders/particle_spray_compute.wgsl9
-rw-r--r--assets/final/shaders/passthrough.wgsl10
-rw-r--r--assets/final/shaders/solarize.wgsl10
-rw-r--r--assets/final/shaders/vignette.wgsl37
-rw-r--r--assets/final/shaders/visual_debug.wgsl14
-rw-r--r--src/gpu/demo_effects.cc2
-rw-r--r--src/gpu/demo_effects.h2
-rw-r--r--src/gpu/effects/circle_mask_effect.cc33
-rw-r--r--src/gpu/effects/rotating_cube_effect.cc8
-rw-r--r--src/gpu/effects/rotating_cube_effect.h4
-rw-r--r--src/tests/test_demo_effects.cc13
26 files changed, 200 insertions, 106 deletions
diff --git a/.gitignore b/.gitignore
index 83f2931..a9afb7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,7 +56,6 @@ compile_commands.json
*.sublime-workspace
*.gemini
/assets/originals
-/assets/final
/third_party/windows
/archive
src/generated/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea53876..2f939bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -115,6 +115,8 @@ set(GPU_SOURCES
src/gpu/effects/fade_effect.cc
src/gpu/effects/flash_effect.cc
src/gpu/effects/shader_composer.cc
+ src/gpu/effects/circle_mask_effect.cc
+ src/gpu/effects/rotating_cube_effect.cc
src/gpu/texture_manager.cc
)
set(3D_SOURCES
diff --git a/assets/demo.seq b/assets/demo.seq
index 9c55fe5..0dfb108 100644
--- a/assets/demo.seq
+++ b/assets/demo.seq
@@ -29,6 +29,11 @@ SEQUENCE 0b 0
EFFECT + SolarizeEffect 0 4b # Priority 2 (was 3, now contiguous)
EFFECT + VignetteEffect 0 6 radius=0.6 softness=0.1
+SEQUENCE 2.0 0
+ EFFECT + CircleMaskEffect 0.0 2.0 0.35 # Priority 0 (mask generator, radius 0.35)
+ EFFECT + RotatingCubeEffect 0.0 2.0 # Priority 1 (renders inside circle)
+ EFFECT + GaussianBlurEffect 0.0 2.0 strength=2.0 # Priority 2 (post-process blur)
+
SEQUENCE 4b 0
EFFECT - FlashCubeEffect 0.1 3. # Priority -1
EFFECT + FlashEffect 0.0 0.2 # Priority 0 (was 4, now contiguous)
diff --git a/assets/final/demo_assets.txt b/assets/final/demo_assets.txt
index bf39c5d..05eee17 100644
--- a/assets/final/demo_assets.txt
+++ b/assets/final/demo_assets.txt
@@ -52,3 +52,6 @@ SHADER_MESH, NONE, shaders/mesh_render.wgsl, "Mesh Rasterization Shader"
MESH_CUBE, NONE, test_mesh.obj, "A simple cube mesh"
DODECAHEDRON, NONE, dodecahedron.obj, "A dodecahedron mesh"
SHADER_VIGNETTE, NONE, shaders/vignette.wgsl, "Vignette Shader"
+CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask compute shader"
+CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader"
+MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader"
diff --git a/assets/final/shaders/chroma_aberration.wgsl b/assets/final/shaders/chroma_aberration.wgsl
index f1a3034..ca1e17d 100644
--- a/assets/final/shaders/chroma_aberration.wgsl
+++ b/assets/final/shaders/chroma_aberration.wgsl
@@ -1,18 +1,20 @@
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d<f32>;
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- width: f32,
- height: f32,
+ audio_intensity: f32,
+};
+struct EffectParams {
offset_scale: f32,
angle: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> common: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: EffectParams;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
@@ -24,11 +26,11 @@ struct Uniforms {
}
@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
- let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height);
+ let uv = p.xy / common.resolution;
// Compute offset magnitude and direction
- let offset_mag = uniforms.offset_scale * uniforms.intensity;
- let offset_dir = vec2<f32>(cos(uniforms.angle), sin(uniforms.angle));
+ let offset_mag = params.offset_scale * common.audio_intensity;
+ let offset_dir = vec2<f32>(cos(params.angle), sin(params.angle));
let offset = offset_mag * offset_dir;
// Sample RGB channels with chromatic aberration
diff --git a/assets/final/shaders/circle_mask_compute.wgsl b/assets/final/shaders/circle_mask_compute.wgsl
index 610ee67..9bb03ff 100644
--- a/assets/final/shaders/circle_mask_compute.wgsl
+++ b/assets/final/shaders/circle_mask_compute.wgsl
@@ -1,14 +1,20 @@
// Circle mask compute shader
// Generates a circular mask (1.0 inside, 0.0 outside)
-struct Uniforms {
- radius: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
aspect_ratio: f32,
- width: f32,
- height: f32,
+ time: f32,
+ beat: f32,
+ audio_intensity: f32,
+};
+struct EffectParams {
+ radius: f32,
+ _pad: vec3<f32>,
};
-@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(0) var<uniform> common: CommonUniforms;
+@group(0) @binding(1) var<uniform> params: EffectParams;
struct VSOutput {
@builtin(position) position: vec4<f32>,
@@ -21,13 +27,13 @@ struct VSOutput {
}
@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
- let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height);
+ let uv = p.xy / common.resolution;
let center = vec2<f32>(0.5, 0.5);
- let aspect_corrected_uv = (uv - center) * vec2<f32>(uniforms.aspect_ratio, 1.0);
+ let aspect_corrected_uv = (uv - center) * vec2<f32>(common.aspect_ratio, 1.0);
let dist = length(aspect_corrected_uv);
let edge_width = 0.01;
- let mask = smoothstep(uniforms.radius + edge_width, uniforms.radius - edge_width, dist);
+ let mask = smoothstep(params.radius + edge_width, params.radius - edge_width, dist);
return vec4<f32>(mask, mask, mask, 1.0);
}
diff --git a/assets/final/shaders/circle_mask_render.wgsl b/assets/final/shaders/circle_mask_render.wgsl
index 902600e..6855c95 100644
--- a/assets/final/shaders/circle_mask_render.wgsl
+++ b/assets/final/shaders/circle_mask_render.wgsl
@@ -4,14 +4,15 @@
@group(0) @binding(0) var mask_tex: texture_2d<f32>;
@group(0) @binding(1) var mask_sampler: sampler;
-struct Uniforms {
- width: f32,
- height: f32,
- _pad1: f32,
- _pad2: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
+ time: f32,
+ beat: f32,
+ audio_intensity: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
struct VSOutput {
@builtin(position) position: vec4<f32>,
@@ -24,7 +25,7 @@ struct VSOutput {
}
@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
- let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height);
+ let uv = p.xy / uniforms.resolution;
let mask_value = textureSample(mask_tex, mask_sampler, uv).r;
if (mask_value > 0.5) {
diff --git a/assets/final/shaders/common_uniforms.wgsl b/assets/final/shaders/common_uniforms.wgsl
index 4b5cf33..1e0e242 100644
--- a/assets/final/shaders/common_uniforms.wgsl
+++ b/assets/final/shaders/common_uniforms.wgsl
@@ -1,3 +1,10 @@
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
+ time: f32,
+ beat: f32,
+ audio_intensity: f32,
+};
struct GlobalUniforms {
view_proj: mat4x4<f32>,
inv_view_proj: mat4x4<f32>,
diff --git a/assets/final/shaders/distort.wgsl b/assets/final/shaders/distort.wgsl
index 2d5f276..cca01c4 100644
--- a/assets/final/shaders/distort.wgsl
+++ b/assets/final/shaders/distort.wgsl
@@ -1,15 +1,15 @@
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d<f32>;
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- resolution: vec2<f32>,
+ audio_intensity: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
@@ -22,6 +22,6 @@ struct Uniforms {
@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
let uv = p.xy / uniforms.resolution;
- let dist = 0.1 * uniforms.intensity * sin(uv.y * 20.0 + uniforms.time * 5.0);
+ let dist = 0.1 * uniforms.audio_intensity * sin(uv.y * 20.0 + uniforms.time * 5.0);
return textureSample(txt, smplr, uv + vec2<f32>(dist, 0.0));
}
diff --git a/assets/final/shaders/ellipse.wgsl b/assets/final/shaders/ellipse.wgsl
index be236c8..1b6df0b 100644
--- a/assets/final/shaders/ellipse.wgsl
+++ b/assets/final/shaders/ellipse.wgsl
@@ -1,12 +1,12 @@
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- resolution: vec2<f32>,
+ audio_intensity: f32,
};
-@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(0) var<uniform> uniforms: CommonUniforms;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
diff --git a/assets/final/shaders/gaussian_blur.wgsl b/assets/final/shaders/gaussian_blur.wgsl
index e848c6b..39cbf54 100644
--- a/assets/final/shaders/gaussian_blur.wgsl
+++ b/assets/final/shaders/gaussian_blur.wgsl
@@ -1,18 +1,20 @@
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d<f32>;
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- width: f32,
- height: f32,
+ audio_intensity: f32,
+};
+struct EffectParams {
strength: f32,
_pad: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> common: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: EffectParams;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
@@ -24,16 +26,16 @@ struct Uniforms {
}
@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> {
- let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height);
+ let uv = p.xy / common.resolution;
var res = vec4<f32>(0.0);
// Parameterized strength + dramatic beat pulsation
- let pulse = 0.5 + uniforms.intensity * 2.0; // Pulsate between 0.5x and 2.5x with beat
- let size = uniforms.strength * pulse;
+ let pulse = 0.5 + common.audio_intensity * 2.0; // Pulsate between 0.5x and 2.5x with beat
+ let size = params.strength * pulse;
for (var x: f32 = -2.0; x <= 2.0; x += 1.0) {
for (var y: f32 = -2.0; y <= 2.0; y += 1.0) {
- res += textureSample(txt, smplr, uv + vec2<f32>(x, y) * size / uniforms.width);
+ res += textureSample(txt, smplr, uv + vec2<f32>(x, y) * size / common.resolution.x);
}
}
return res / 25.0;
diff --git a/assets/final/shaders/main_shader.wgsl b/assets/final/shaders/main_shader.wgsl
index 7011159..5bb7d46 100644
--- a/assets/final/shaders/main_shader.wgsl
+++ b/assets/final/shaders/main_shader.wgsl
@@ -1,15 +1,17 @@
-struct Uniforms {
- audio_peak: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
aspect_ratio: f32,
time: f32,
+ beat: f32,
+ audio_intensity: f32,
};
-@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(0) var<uniform> uniforms: CommonUniforms;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
let PI = 3.14159265;
let num_sides = 7.0;
- let scale = 0.5 + 0.3 * uniforms.audio_peak;
+ let scale = 0.5 + 0.3 * uniforms.audio_intensity;
let tri_idx = f32(i / 3u);
let sub_idx = i % 3u;
if (sub_idx == 0u) {
@@ -20,10 +22,10 @@ struct Uniforms {
}
@fragment fn fs_main() -> @location(0) vec4<f32> {
- let h = uniforms.time * 2.0 + uniforms.audio_peak * 3.0;
+ let h = uniforms.time * 2.0 + uniforms.audio_intensity * 3.0;
let r = sin(h) * 0.5 + 0.5;
let g = sin(h + 2.0) * 0.9 + 0.3;
let b = sin(h + 4.0) * 0.5 + 0.5;
- let boost = uniforms.audio_peak * 0.5;
+ let boost = uniforms.audio_intensity * 0.5;
return vec4<f32>(r + boost, g + boost, b + boost, 1.0);
}
diff --git a/assets/final/shaders/masked_cube.wgsl b/assets/final/shaders/masked_cube.wgsl
index 77e2fb9..5e673a3 100644
--- a/assets/final/shaders/masked_cube.wgsl
+++ b/assets/final/shaders/masked_cube.wgsl
@@ -7,7 +7,6 @@
@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer;
@group(0) @binding(3) var noise_tex: texture_2d<f32>;
@group(0) @binding(4) var noise_sampler: sampler;
-@group(0) @binding(5) var sky_tex: texture_2d<f32>;
@group(1) @binding(0) var mask_tex: texture_2d<f32>;
@group(1) @binding(1) var mask_sampler: sampler;
@@ -89,13 +88,13 @@ fn fs_main(in: VertexOutput) -> FragmentOutput {
let local_origin = (inv_model * vec4<f32>(ray_origin, 1.0)).xyz;
let local_dir = normalize((inv_model * vec4<f32>(ray_dir, 0.0)).xyz);
- let t = ray_box(local_origin, local_dir, vec3<f32>(-1.0), vec3<f32>(1.0));
- if (t.y < 0.0) {
+ let bounds = ray_box_intersection(local_origin, local_dir, vec3<f32>(1.0));
+ if (!bounds.hit) {
discard;
}
- let t_start = max(t.x, 0.0);
- let t_end = t.y;
+ let t_start = bounds.t_entry;
+ let t_end = bounds.t_exit;
var t_march = t_start;
let max_steps = 128;
diff --git a/assets/final/shaders/particle_compute.wgsl b/assets/final/shaders/particle_compute.wgsl
index a6c96db..f2f7ae3 100644
--- a/assets/final/shaders/particle_compute.wgsl
+++ b/assets/final/shaders/particle_compute.wgsl
@@ -5,15 +5,16 @@ struct Particle {
color: vec4<f32>,
};
-struct Uniforms {
- audio_peak: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
aspect_ratio: f32,
time: f32,
beat: f32,
+ audio_intensity: f32,
};
@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
-@group(0) @binding(1) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var<uniform> uniforms: CommonUniforms;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
@@ -24,11 +25,11 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
var p = particles[i];
let new_pos = p.pos.xyz + p.vel.xyz * 0.016;
p.pos = vec4<f32>(new_pos, p.pos.w);
- p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_peak * 5.0);
+ p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_intensity * 5.0);
p.rot.x = p.rot.x + p.rot.y * 0.016;
if (p.pos.y < -1.5) {
p.pos.y = 1.5;
- p.pos.x = (f32(i % 100u) / 50.0) - 1.0 + (uniforms.audio_peak * 0.5);
+ p.pos.x = (f32(i % 100u) / 50.0) - 1.0 + (uniforms.audio_intensity * 0.5);
p.vel.y = 0.0;
}
particles[i] = p;
diff --git a/assets/final/shaders/particle_render.wgsl b/assets/final/shaders/particle_render.wgsl
index 6a955f0..a0740dc 100644
--- a/assets/final/shaders/particle_render.wgsl
+++ b/assets/final/shaders/particle_render.wgsl
@@ -5,15 +5,16 @@ struct Particle {
color: vec4<f32>,
};
-struct Uniforms {
- audio_peak: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
aspect_ratio: f32,
time: f32,
beat: f32,
+ audio_intensity: f32,
};
@group(0) @binding(0) var<storage, read> particles: array<Particle>;
-@group(0) @binding(1) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var<uniform> uniforms: CommonUniforms;
struct VSOut {
@builtin(position) pos: vec4<f32>,
@@ -23,7 +24,7 @@ struct VSOut {
@vertex fn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
let p = particles[ii];
- let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_peak * 0.02;
+ let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_intensity * 0.02;
var offsets = array<vec2<f32>, 6>(
vec2<f32>(-1, -1),
vec2<f32>(1, -1),
@@ -40,7 +41,7 @@ struct VSOut {
// Fade based on lifetime (p.pos.w goes from 1.0 to 0.0)
let lifetime_fade = p.pos.w;
- let color_with_fade = vec4<f32>(p.color.rgb * (0.5 + 0.5 * uniforms.audio_peak), p.color.a * lifetime_fade);
+ let color_with_fade = vec4<f32>(p.color.rgb * (0.5 + 0.5 * uniforms.audio_intensity), p.color.a * lifetime_fade);
return VSOut(vec4<f32>(pos, 0.0, 1.0), color_with_fade, offset);
}
diff --git a/assets/final/shaders/particle_spray_compute.wgsl b/assets/final/shaders/particle_spray_compute.wgsl
index 55fa8e9..f1a4d43 100644
--- a/assets/final/shaders/particle_spray_compute.wgsl
+++ b/assets/final/shaders/particle_spray_compute.wgsl
@@ -5,15 +5,16 @@ struct Particle {
color: vec4<f32>,
};
-struct Uniforms {
- intensity: f32,
+struct CommonUniforms {
+ resolution: vec2<f32>,
aspect_ratio: f32,
time: f32,
beat: f32,
+ audio_intensity: f32,
};
@group(0) @binding(0) var<storage, read_write> particles: array<Particle>;
-@group(0) @binding(1) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var<uniform> uniforms: CommonUniforms;
fn hash(p: f32) -> f32 {
return fract(sin(p) * 43758.5453);
@@ -30,7 +31,7 @@ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
let r = hash(f32(i) + uniforms.time);
let angle = r * 6.28318;
p.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
- p.vel = vec4<f32>(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.intensity * 2.0);
+ p.vel = vec4<f32>(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.audio_intensity * 2.0);
p.color = vec4<f32>(hash(r + 0.1), hash(r + 0.2), 1.0, 1.0);
}
let new_pos = p.pos.xyz + p.vel.xyz * 0.016;
diff --git a/assets/final/shaders/passthrough.wgsl b/assets/final/shaders/passthrough.wgsl
index aa4de1c..c1ac60a 100644
--- a/assets/final/shaders/passthrough.wgsl
+++ b/assets/final/shaders/passthrough.wgsl
@@ -1,14 +1,14 @@
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d<f32>;
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- resolution: vec2<f32>,
+ audio_intensity: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
diff --git a/assets/final/shaders/solarize.wgsl b/assets/final/shaders/solarize.wgsl
index fcb9d80..c9f19a2 100644
--- a/assets/final/shaders/solarize.wgsl
+++ b/assets/final/shaders/solarize.wgsl
@@ -1,15 +1,15 @@
@group(0) @binding(0) var smplr: sampler;
@group(0) @binding(1) var txt: texture_2d<f32>;
-struct Uniforms {
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
time: f32,
beat: f32,
- intensity: f32,
- aspect_ratio: f32,
- resolution: vec2<f32>,
+ audio_intensity: f32,
};
-@group(0) @binding(2) var<uniform> uniforms: Uniforms;
+@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
var pos = array<vec2<f32>, 3>(
diff --git a/assets/final/shaders/vignette.wgsl b/assets/final/shaders/vignette.wgsl
new file mode 100644
index 0000000..9b7f43f
--- /dev/null
+++ b/assets/final/shaders/vignette.wgsl
@@ -0,0 +1,37 @@
+@group(0) @binding(0) var input_sampler: sampler;
+@group(0) @binding(1) var input_tex: texture_2d<f32>;
+struct CommonUniforms {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
+ time: f32,
+ beat: f32,
+ audio_intensity: f32,
+};
+struct EffectParams {
+ radius: f32,
+ softness: f32,
+};
+
+@group(0) @binding(2) var<uniform> common: CommonUniforms;
+@group(0) @binding(3) var<uniform> params: EffectParams;
+
+@vertex
+fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(3.0, -1.0),
+ vec2<f32>(-1.0, 3.0)
+ );
+ return vec4<f32>(pos[vertex_idx], 0.0, 1.0);
+}
+
+@fragment
+fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
+ let uv = pos.xy / common.resolution;
+ let color = textureSample(input_tex, input_sampler, uv);
+
+ let d = distance(uv, vec2<f32>(0.5, 0.5));
+ let vignette = smoothstep(params.radius, params.radius - params.softness, d);
+
+ return vec4<f32>(color.rgb * mix(1.0, vignette, common.audio_intensity), color.a);
+} \ No newline at end of file
diff --git a/assets/final/shaders/visual_debug.wgsl b/assets/final/shaders/visual_debug.wgsl
index e91c1a9..63e1f13 100644
--- a/assets/final/shaders/visual_debug.wgsl
+++ b/assets/final/shaders/visual_debug.wgsl
@@ -1,7 +1,11 @@
-struct Uniforms {
- viewProj : mat4x4<f32>,
-}
-@group(0) @binding(0) var<uniform> uniforms : Uniforms;
+struct GlobalUniforms {
+ view_proj: mat4x4<f32>,
+ inv_view_proj: mat4x4<f32>,
+ camera_pos_time: vec4<f32>,
+ params: vec4<f32>,
+ resolution: vec2<f32>,
+};
+@group(0) @binding(0) var<uniform> uniforms : GlobalUniforms;
struct VertexInput {
@location(0) position : vec3<f32>,
@@ -16,7 +20,7 @@ struct VertexOutput {
@vertex
fn vs_main(in : VertexInput) -> VertexOutput {
var out : VertexOutput;
- out.position = uniforms.viewProj * vec4<f32>(in.position, 1.0);
+ out.position = uniforms.view_proj * vec4<f32>(in.position, 1.0);
out.color = in.color;
return out;
}
diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc
index 36fd16e..069d36c 100644
--- a/src/gpu/demo_effects.cc
+++ b/src/gpu/demo_effects.cc
@@ -3,6 +3,8 @@
// Its content has been split into individual effect files and helper files.
#include "gpu/demo_effects.h"
+#include "gpu/effects/circle_mask_effect.h"
+#include "gpu/effects/rotating_cube_effect.h"
// Auto-generated function to populate the timeline
void LoadTimeline(MainSequence& main_seq, WGPUDevice device, WGPUQueue queue,
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index 82700cd..fabfbd2 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -9,6 +9,8 @@
#include "gpu/effects/flash_effect.h" // FlashEffect with params support
#include "gpu/effects/post_process_helper.h"
#include "gpu/effects/shaders.h"
+#include "gpu/effects/circle_mask_effect.h"
+#include "gpu/effects/rotating_cube_effect.h"
#include "gpu/gpu.h"
#include "gpu/texture_manager.h"
#include "gpu/uniform_helper.h"
diff --git a/src/gpu/effects/circle_mask_effect.cc b/src/gpu/effects/circle_mask_effect.cc
index 55bcb90..226b603 100644
--- a/src/gpu/effects/circle_mask_effect.cc
+++ b/src/gpu/effects/circle_mask_effect.cc
@@ -51,7 +51,7 @@ void CircleMaskEffect::init(MainSequence* demo) {
WGPUShaderModule compute_module = wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc);
const WGPUColorTargetState compute_target = {
- .format = WGPUTextureFormat_RGBA8Unorm,
+ .format = ctx_.format, // Match auxiliary texture format
.writeMask = WGPUColorWriteMask_All,
};
WGPUFragmentState compute_frag = {};
@@ -60,6 +60,7 @@ void CircleMaskEffect::init(MainSequence* demo) {
compute_frag.targetCount = 1;
compute_frag.targets = &compute_target;
WGPURenderPipelineDescriptor compute_pipeline_desc = {};
+ compute_pipeline_desc.label = label_view("CircleMaskEffect_compute");
compute_pipeline_desc.vertex.module = compute_module;
compute_pipeline_desc.vertex.entryPoint = str_view("vs_main");
compute_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
@@ -98,11 +99,19 @@ void CircleMaskEffect::init(MainSequence* demo) {
render_frag.entryPoint = str_view("fs_main");
render_frag.targetCount = 1;
render_frag.targets = &render_target;
+ const WGPUDepthStencilState depth_stencil = {
+ .format = WGPUTextureFormat_Depth24Plus,
+ .depthWriteEnabled = WGPUOptionalBool_False, // Don't write depth
+ .depthCompare = WGPUCompareFunction_Always, // Always pass
+ };
+
WGPURenderPipelineDescriptor render_pipeline_desc = {};
+ render_pipeline_desc.label = label_view("CircleMaskEffect_render");
render_pipeline_desc.vertex.module = render_module;
render_pipeline_desc.vertex.entryPoint = str_view("vs_main");
render_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
render_pipeline_desc.primitive.cullMode = WGPUCullMode_None;
+ render_pipeline_desc.depthStencil = &depth_stencil;
render_pipeline_desc.multisample.count = 1;
render_pipeline_desc.multisample.mask = 0xFFFFFFFF;
render_pipeline_desc.fragment = &render_frag;
@@ -139,16 +148,18 @@ void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time,
compute_uniforms_.update(ctx_.queue, uniforms);
WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask");
- const WGPURenderPassColorAttachment color_attachment = {
- .view = mask_view,
- .loadOp = WGPULoadOp_Clear,
- .storeOp = WGPUStoreOp_Store,
- .clearValue = {0.0, 0.0, 0.0, 1.0},
- };
- const WGPURenderPassDescriptor pass_desc = {
- .colorAttachmentCount = 1,
- .colorAttachments = &color_attachment,
- };
+ WGPURenderPassColorAttachment color_attachment = {};
+ color_attachment.view = mask_view;
+ color_attachment.loadOp = WGPULoadOp_Clear;
+ color_attachment.storeOp = WGPUStoreOp_Store;
+ color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0};
+#if !defined(DEMO_CROSS_COMPILE_WIN32)
+ color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+#endif
+
+ WGPURenderPassDescriptor pass_desc = {};
+ pass_desc.colorAttachmentCount = 1;
+ pass_desc.colorAttachments = &color_attachment;
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
wgpuRenderPassEncoderSetPipeline(pass, compute_pipeline_);
diff --git a/src/gpu/effects/rotating_cube_effect.cc b/src/gpu/effects/rotating_cube_effect.cc
index b4f3d3e..7f590c5 100644
--- a/src/gpu/effects/rotating_cube_effect.cc
+++ b/src/gpu/effects/rotating_cube_effect.cc
@@ -63,12 +63,12 @@ void RotatingCubeEffect::init(MainSequence* demo) {
ShaderComposer::CompositionMap composition_map;
composition_map["render/scene_query_mode"] = "render/scene_query_linear";
- std::string composed_shader = ShaderComposer::Get().Compose(
+ composed_shader_ = ShaderComposer::Get().Compose(
{}, std::string(shader_code, shader_size), composition_map);
WGPUShaderSourceWGSL wgsl_src = {};
wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
- wgsl_src.code = str_view(composed_shader.c_str());
+ wgsl_src.code = str_view(composed_shader_.c_str());
WGPUShaderModuleDescriptor shader_desc = {};
shader_desc.nextInChain = &wgsl_src.chain;
@@ -105,18 +105,16 @@ void RotatingCubeEffect::init(MainSequence* demo) {
pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc);
wgpuShaderModuleRelease(shader_module);
- WGPUTextureView dummy_sky = noise_view_;
const WGPUBindGroupEntry entries_0[] = {
{.binding = 0, .buffer = uniform_buffer_.buffer, .size = sizeof(Uniforms)},
{.binding = 1, .buffer = object_buffer_.buffer, .size = sizeof(ObjectData)},
{.binding = 3, .textureView = noise_view_},
{.binding = 4, .sampler = noise_sampler_},
- {.binding = 5, .textureView = dummy_sky},
};
const WGPUBindGroupDescriptor bg_desc_0 = {
.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0),
- .entryCount = 5,
+ .entryCount = 4,
.entries = entries_0,
};
bind_group_0_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc_0);
diff --git a/src/gpu/effects/rotating_cube_effect.h b/src/gpu/effects/rotating_cube_effect.h
index 1ce81b7..89b3fa6 100644
--- a/src/gpu/effects/rotating_cube_effect.h
+++ b/src/gpu/effects/rotating_cube_effect.h
@@ -8,6 +8,7 @@
#include "gpu/effect.h"
#include "gpu/gpu.h"
#include "util/mini_math.h"
+#include <string>
class RotatingCubeEffect : public Effect {
public:
@@ -46,6 +47,9 @@ class RotatingCubeEffect : public Effect {
WGPUSampler noise_sampler_ = nullptr;
WGPUSampler mask_sampler_ = nullptr;
float rotation_ = 0.0f;
+
+ // Store composed shader to keep it alive for WebGPU
+ std::string composed_shader_;
};
#endif /* ROTATING_CUBE_EFFECT_H_ */
diff --git a/src/tests/test_demo_effects.cc b/src/tests/test_demo_effects.cc
index cf77c13..25ada59 100644
--- a/src/tests/test_demo_effects.cc
+++ b/src/tests/test_demo_effects.cc
@@ -17,8 +17,9 @@ static constexpr int EXPECTED_POST_PROCESS_COUNT =
// ChromaAberrationEffect, SolarizeEffect, FadeEffect,
// ThemeModulationEffect, VignetteEffect
static constexpr int EXPECTED_SCENE_COUNT =
- 6; // HeptagonEffect, ParticlesEffect, ParticleSprayEffect,
- // MovingEllipseEffect, FlashCubeEffect, Hybrid3DEffect
+ 8; // HeptagonEffect, ParticlesEffect, ParticleSprayEffect,
+ // MovingEllipseEffect, FlashCubeEffect, Hybrid3DEffect,
+ // CircleMaskEffect, RotatingCubeEffect
#include "effect_test_helpers.h"
#include "gpu/demo_effects.h"
@@ -154,6 +155,8 @@ static void test_scene_effects() {
std::make_shared<MovingEllipseEffect>(fixture.ctx())},
{"FlashCubeEffect", std::make_shared<FlashCubeEffect>(fixture.ctx())},
{"Hybrid3DEffect", std::make_shared<Hybrid3DEffect>(fixture.ctx())},
+ {"CircleMaskEffect", std::make_shared<CircleMaskEffect>(fixture.ctx())},
+ {"RotatingCubeEffect", std::make_shared<RotatingCubeEffect>(fixture.ctx())},
};
int passed = 0;
@@ -163,9 +166,11 @@ static void test_scene_effects() {
assert(!effect->is_post_process() &&
"Scene effect should return false for is_post_process()");
- // FlashCubeEffect and Hybrid3DEffect require full 3D pipeline (Renderer3D)
+ // FlashCubeEffect, Hybrid3DEffect, RotatingCubeEffect, and CircleMaskEffect require full 3D pipeline (Renderer3D) or auxiliary textures
const bool requires_3d = (strcmp(name, "FlashCubeEffect") == 0 ||
- strcmp(name, "Hybrid3DEffect") == 0);
+ strcmp(name, "Hybrid3DEffect") == 0 ||
+ strcmp(name, "RotatingCubeEffect") == 0 ||
+ strcmp(name, "CircleMaskEffect") == 0);
const int result = test_effect_smoke(name, effect, &main_seq, requires_3d);
if (result == 1) {