summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-16 10:03:18 +0100
committerskal <pascal.massimino@gmail.com>2026-02-16 10:03:18 +0100
commit2e33a435d6f1efee5a6c88cbfc99b97241fe2ad3 (patch)
tree6eb1890a738ff9050835cd88bc4cadebeeb390c8
parenta126caf9e52b8f7c5be8e09b303e741aa9a410cb (diff)
feat(sequence): port rotating_cube_effect to v2
- Add RotatingCubeEffectV2 with 3D rendering + depth buffer - Create rotating_cube_v2.wgsl (hardcoded cube geometry) - Simplified: no auxiliary mask texture dependency - Declare depth node via NodeRegistry - Update timeline_v2.seq rotating_cube sequence - Add shader exports to shaders.{h,cc} - All 36 tests passing handoff(Claude): RotatingCube v2 complete, hybrid_3d next
-rw-r--r--cmake/DemoSourceLists.cmake1
-rw-r--r--src/effects/rotating_cube_effect_v2.cc184
-rw-r--r--src/effects/rotating_cube_effect_v2.h49
-rw-r--r--src/gpu/shaders.cc4
-rw-r--r--src/gpu/shaders.h1
-rw-r--r--workspaces/main/assets.txt1
-rw-r--r--workspaces/main/shaders/rotating_cube_v2.wgsl89
-rw-r--r--workspaces/main/timeline_v2.seq7
8 files changed, 332 insertions, 4 deletions
diff --git a/cmake/DemoSourceLists.cmake b/cmake/DemoSourceLists.cmake
index 5325f6a..1b8ad28 100644
--- a/cmake/DemoSourceLists.cmake
+++ b/cmake/DemoSourceLists.cmake
@@ -36,6 +36,7 @@ set(COMMON_GPU_EFFECTS
src/effects/gaussian_blur_effect_v2.cc
src/effects/heptagon_effect_v2.cc
src/effects/particles_effect_v2.cc
+ src/effects/rotating_cube_effect_v2.cc
src/effects/heptagon_effect.cc
src/effects/particles_effect.cc
src/effects/passthrough_effect.cc
diff --git a/src/effects/rotating_cube_effect_v2.cc b/src/effects/rotating_cube_effect_v2.cc
new file mode 100644
index 0000000..02ed2d3
--- /dev/null
+++ b/src/effects/rotating_cube_effect_v2.cc
@@ -0,0 +1,184 @@
+// This file is part of the 64k demo project.
+// It implements RotatingCubeEffectV2 (simplified v2 port).
+
+#include "effects/rotating_cube_effect_v2.h"
+#include "gpu/bind_group_builder.h"
+#include "gpu/gpu.h"
+#include "gpu/shaders.h"
+
+RotatingCubeEffectV2::RotatingCubeEffectV2(const GpuContext& ctx,
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs)
+ : EffectV2(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth") {
+ // Create uniform buffers
+ uniform_buffer_ =
+ gpu_create_buffer(ctx_.device, sizeof(Uniforms),
+ WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
+ object_buffer_ =
+ gpu_create_buffer(ctx_.device, sizeof(ObjectData),
+ WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst);
+
+ // Create bind group layout
+ WGPUBindGroupLayout bgl =
+ BindGroupLayoutBuilder()
+ .uniform(0, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment,
+ sizeof(Uniforms))
+ .storage(1, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment,
+ sizeof(ObjectData))
+ .build(ctx_.device);
+
+ const WGPUBindGroupLayout bgls[] = {bgl};
+ const WGPUPipelineLayoutDescriptor pl_desc = {
+ .bindGroupLayoutCount = 1,
+ .bindGroupLayouts = bgls,
+ };
+ WGPUPipelineLayout pipeline_layout =
+ wgpuDeviceCreatePipelineLayout(ctx_.device, &pl_desc);
+
+ // Load shader (TODO: create rotating_cube_v2.wgsl)
+ WGPUShaderSourceWGSL wgsl_src = {};
+ wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
+ wgsl_src.code = str_view(rotating_cube_v2_wgsl);
+
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader_module =
+ wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc);
+
+ const WGPUColorTargetState color_target = {
+ .format = WGPUTextureFormat_RGBA8Unorm,
+ .writeMask = WGPUColorWriteMask_All,
+ };
+
+ const WGPUDepthStencilState depth_stencil = {
+ .format = WGPUTextureFormat_Depth24Plus,
+ .depthWriteEnabled = WGPUOptionalBool_True,
+ .depthCompare = WGPUCompareFunction_Less,
+ };
+
+ WGPUFragmentState fragment = {};
+ fragment.module = shader_module;
+ fragment.entryPoint = str_view("fs_main");
+ fragment.targetCount = 1;
+ fragment.targets = &color_target;
+
+ WGPURenderPipelineDescriptor pipeline_desc = {};
+ pipeline_desc.layout = pipeline_layout;
+ pipeline_desc.vertex.module = shader_module;
+ pipeline_desc.vertex.entryPoint = str_view("vs_main");
+ pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipeline_desc.primitive.cullMode = WGPUCullMode_Back;
+ pipeline_desc.depthStencil = &depth_stencil;
+ pipeline_desc.multisample.count = 1;
+ pipeline_desc.multisample.mask = 0xFFFFFFFF;
+ pipeline_desc.fragment = &fragment;
+
+ pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc);
+ wgpuShaderModuleRelease(shader_module);
+ wgpuPipelineLayoutRelease(pipeline_layout);
+
+ // Create bind group
+ const WGPUBindGroupEntry entries[] = {
+ {.binding = 0,
+ .buffer = uniform_buffer_.buffer,
+ .size = sizeof(Uniforms)},
+ {.binding = 1,
+ .buffer = object_buffer_.buffer,
+ .size = sizeof(ObjectData)},
+ };
+
+ const WGPUBindGroupDescriptor bg_desc = {
+ .layout = bgl,
+ .entryCount = 2,
+ .entries = entries,
+ };
+ bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc);
+ wgpuBindGroupLayoutRelease(bgl);
+}
+
+RotatingCubeEffectV2::~RotatingCubeEffectV2() {
+ if (bind_group_)
+ wgpuBindGroupRelease(bind_group_);
+ if (pipeline_)
+ wgpuRenderPipelineRelease(pipeline_);
+}
+
+void RotatingCubeEffectV2::declare_nodes(NodeRegistry& registry) {
+ // Declare depth buffer node
+ registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1);
+}
+
+void RotatingCubeEffectV2::render(WGPUCommandEncoder encoder,
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) {
+ rotation_ += 0.016f * 1.5f;
+
+ // Camera setup
+ const vec3 camera_pos = vec3(0, 0, 5);
+ const vec3 target = vec3(0, 0, 0);
+ const vec3 up = vec3(0, 1, 0);
+
+ const mat4 view = mat4::look_at(camera_pos, target, up);
+ const float fov = 60.0f * 3.14159f / 180.0f;
+ const mat4 proj = mat4::perspective(fov, params.aspect_ratio, 0.1f, 100.0f);
+ const mat4 view_proj = proj * view;
+
+ // Cube transform
+ const quat rot = quat::from_axis(vec3(0.3f, 1.0f, 0.2f), rotation_);
+ const mat4 T = mat4::translate(vec3(0, 0, 0));
+ const mat4 R = rot.to_mat();
+ const mat4 S = mat4::scale(vec3(1.5f, 1.5f, 1.5f));
+ const mat4 model = T * R * S;
+
+ // Update uniforms
+ const Uniforms uniforms = {
+ .view_proj = view_proj,
+ .inv_view_proj = view_proj.inverse(),
+ .camera_pos_time = vec4(camera_pos.x, camera_pos.y, camera_pos.z, params.time),
+ .params = vec4(1.0f, 0.0f, 0.0f, 0.0f),
+ .resolution = params.resolution,
+ .aspect_ratio = params.aspect_ratio,
+ };
+
+ const ObjectData obj_data = {
+ .model = model,
+ .inv_model = model.inverse(),
+ .color = vec4(0.8f, 0.4f, 0.2f, 1.0f),
+ .params = vec4(1.0f, 0.0f, 0.0f, 0.0f),
+ };
+
+ wgpuQueueWriteBuffer(ctx_.queue, uniform_buffer_.buffer, 0, &uniforms,
+ sizeof(Uniforms));
+ wgpuQueueWriteBuffer(ctx_.queue, object_buffer_.buffer, 0, &obj_data,
+ sizeof(ObjectData));
+
+ // Get output views
+ WGPUTextureView color_view = nodes.get_view(output_nodes_[0]);
+ WGPUTextureView depth_view = nodes.get_view(depth_node_);
+
+ // Render pass with depth
+ WGPURenderPassColorAttachment color_attachment = {
+ .view = color_view,
+ .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED,
+ .loadOp = WGPULoadOp_Clear,
+ .storeOp = WGPUStoreOp_Store,
+ .clearValue = {0.0, 0.0, 0.0, 1.0}};
+
+ WGPURenderPassDepthStencilAttachment depth_attachment = {
+ .view = depth_view,
+ .depthLoadOp = WGPULoadOp_Clear,
+ .depthStoreOp = WGPUStoreOp_Discard,
+ .depthClearValue = 1.0f};
+
+ WGPURenderPassDescriptor pass_desc = {
+ .colorAttachmentCount = 1,
+ .colorAttachments = &color_attachment,
+ .depthStencilAttachment = &depth_attachment};
+
+ WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderDraw(pass, 36, 1, 0, 0); // 36 vertices for cube
+ wgpuRenderPassEncoderEnd(pass);
+ wgpuRenderPassEncoderRelease(pass);
+}
diff --git a/src/effects/rotating_cube_effect_v2.h b/src/effects/rotating_cube_effect_v2.h
new file mode 100644
index 0000000..19ef410
--- /dev/null
+++ b/src/effects/rotating_cube_effect_v2.h
@@ -0,0 +1,49 @@
+// This file is part of the 64k demo project.
+// It declares RotatingCubeEffectV2 (simplified v2 port).
+
+#pragma once
+
+#include "gpu/effect_v2.h"
+#include "gpu/gpu.h"
+#include "gpu/uniform_helper.h"
+#include "util/mini_math.h"
+
+class RotatingCubeEffectV2 : public EffectV2 {
+ public:
+ RotatingCubeEffectV2(const GpuContext& ctx,
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs);
+ ~RotatingCubeEffectV2() override;
+
+ void declare_nodes(NodeRegistry& registry) override;
+ void render(WGPUCommandEncoder encoder,
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) override;
+
+ private:
+ struct Uniforms {
+ mat4 view_proj;
+ mat4 inv_view_proj;
+ vec4 camera_pos_time;
+ vec4 params;
+ vec2 resolution;
+ float aspect_ratio;
+ float _pad;
+ };
+ static_assert(sizeof(Uniforms) == 176, "Uniforms size mismatch");
+
+ struct ObjectData {
+ mat4 model;
+ mat4 inv_model;
+ vec4 color;
+ vec4 params;
+ };
+ static_assert(sizeof(ObjectData) == 160, "ObjectData size mismatch");
+
+ WGPURenderPipeline pipeline_ = nullptr;
+ WGPUBindGroup bind_group_ = nullptr;
+ GpuBuffer uniform_buffer_;
+ GpuBuffer object_buffer_;
+ float rotation_ = 0.0f;
+ std::string depth_node_;
+};
diff --git a/src/gpu/shaders.cc b/src/gpu/shaders.cc
index bea1eb9..9c8beca 100644
--- a/src/gpu/shaders.cc
+++ b/src/gpu/shaders.cc
@@ -181,3 +181,7 @@ const char* particle_compute_v2_wgsl =
const char* particle_render_v2_wgsl =
SafeGetAsset(AssetId::ASSET_SHADER_PARTICLE_RENDER_V2);
+
+const char* rotating_cube_v2_wgsl =
+
+ SafeGetAsset(AssetId::ASSET_SHADER_ROTATING_CUBE_V2);
diff --git a/src/gpu/shaders.h b/src/gpu/shaders.h
index b7ee226..ab5ddf2 100644
--- a/src/gpu/shaders.h
+++ b/src/gpu/shaders.h
@@ -35,3 +35,4 @@ extern const char* gaussian_blur_v2_shader_wgsl;
extern const char* heptagon_v2_shader_wgsl;
extern const char* particle_compute_v2_wgsl;
extern const char* particle_render_v2_wgsl;
+extern const char* rotating_cube_v2_wgsl;
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index fe15cdf..9d15213 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -35,6 +35,7 @@ SHADER_PARTICLE_COMPUTE, NONE, shaders/particle_compute.wgsl, "Particle Compute
SHADER_PARTICLE_RENDER, NONE, shaders/particle_render.wgsl, "Particle Render Shader"
SHADER_PARTICLE_COMPUTE_V2, NONE, shaders/particle_compute_v2.wgsl, "Particle Compute Shader V2"
SHADER_PARTICLE_RENDER_V2, NONE, shaders/particle_render_v2.wgsl, "Particle Render Shader V2"
+SHADER_ROTATING_CUBE_V2, NONE, shaders/rotating_cube_v2.wgsl, "Rotating Cube Shader V2"
SHADER_PASSTHROUGH, NONE, ../../common/shaders/passthrough.wgsl, "Passthrough Shader"
SHADER_ELLIPSE, NONE, shaders/ellipse.wgsl, "Ellipse Shader"
SHADER_PARTICLE_SPRAY_COMPUTE, NONE, shaders/particle_spray_compute.wgsl, "Particle Spray Compute"
diff --git a/workspaces/main/shaders/rotating_cube_v2.wgsl b/workspaces/main/shaders/rotating_cube_v2.wgsl
new file mode 100644
index 0000000..d7e4cae
--- /dev/null
+++ b/workspaces/main/shaders/rotating_cube_v2.wgsl
@@ -0,0 +1,89 @@
+// Rotating cube shader v2 (simplified, no masking)
+
+struct Uniforms {
+ view_proj: mat4x4<f32>,
+ inv_view_proj: mat4x4<f32>,
+ camera_pos_time: vec4<f32>,
+ params: vec4<f32>,
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
+ _pad: f32,
+};
+
+struct ObjectData {
+ model: mat4x4<f32>,
+ inv_model: mat4x4<f32>,
+ color: vec4<f32>,
+ params: vec4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> uniforms: Uniforms;
+@group(0) @binding(1) var<storage, read> object: ObjectData;
+
+struct VSOut {
+ @builtin(position) pos: vec4<f32>,
+ @location(0) world_pos: vec3<f32>,
+ @location(1) normal: vec3<f32>,
+};
+
+// Cube vertices (hardcoded)
+fn get_cube_vertex(vid: u32) -> vec3<f32> {
+ let positions = array<vec3<f32>, 36>(
+ // Front face
+ vec3<f32>(-1, -1, 1), vec3<f32>( 1, -1, 1), vec3<f32>( 1, 1, 1),
+ vec3<f32>(-1, -1, 1), vec3<f32>( 1, 1, 1), vec3<f32>(-1, 1, 1),
+ // Back face
+ vec3<f32>( 1, -1, -1), vec3<f32>(-1, -1, -1), vec3<f32>(-1, 1, -1),
+ vec3<f32>( 1, -1, -1), vec3<f32>(-1, 1, -1), vec3<f32>( 1, 1, -1),
+ // Right face
+ vec3<f32>( 1, -1, 1), vec3<f32>( 1, -1, -1), vec3<f32>( 1, 1, -1),
+ vec3<f32>( 1, -1, 1), vec3<f32>( 1, 1, -1), vec3<f32>( 1, 1, 1),
+ // Left face
+ vec3<f32>(-1, -1, -1), vec3<f32>(-1, -1, 1), vec3<f32>(-1, 1, 1),
+ vec3<f32>(-1, -1, -1), vec3<f32>(-1, 1, 1), vec3<f32>(-1, 1, -1),
+ // Top face
+ vec3<f32>(-1, 1, 1), vec3<f32>( 1, 1, 1), vec3<f32>( 1, 1, -1),
+ vec3<f32>(-1, 1, 1), vec3<f32>( 1, 1, -1), vec3<f32>(-1, 1, -1),
+ // Bottom face
+ vec3<f32>(-1, -1, -1), vec3<f32>( 1, -1, -1), vec3<f32>( 1, -1, 1),
+ vec3<f32>(-1, -1, -1), vec3<f32>( 1, -1, 1), vec3<f32>(-1, -1, 1)
+ );
+ return positions[vid];
+}
+
+fn get_cube_normal(vid: u32) -> vec3<f32> {
+ let face_id = vid / 6u;
+ let normals = array<vec3<f32>, 6>(
+ vec3<f32>( 0, 0, 1), // Front
+ vec3<f32>( 0, 0, -1), // Back
+ vec3<f32>( 1, 0, 0), // Right
+ vec3<f32>(-1, 0, 0), // Left
+ vec3<f32>( 0, 1, 0), // Top
+ vec3<f32>( 0, -1, 0) // Bottom
+ );
+ return normals[face_id];
+}
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VSOut {
+ let local_pos = get_cube_vertex(vid);
+ let local_normal = get_cube_normal(vid);
+
+ let world_pos = object.model * vec4<f32>(local_pos, 1.0);
+ let world_normal = normalize((object.model * vec4<f32>(local_normal, 0.0)).xyz);
+
+ let clip_pos = uniforms.view_proj * world_pos;
+
+ return VSOut(clip_pos, world_pos.xyz, world_normal);
+}
+
+@fragment fn fs_main(@location(0) world_pos: vec3<f32>, @location(1) normal: vec3<f32>) -> @location(0) vec4<f32> {
+ let N = normalize(normal);
+ let light_dir = normalize(vec3<f32>(1.0, 1.0, 1.0));
+ let diffuse = max(dot(N, light_dir), 0.0);
+
+ let ambient = 0.3;
+ let lighting = ambient + diffuse * 0.7;
+
+ let color = object.color.rgb * lighting;
+ return vec4<f32>(color, 1.0);
+}
diff --git a/workspaces/main/timeline_v2.seq b/workspaces/main/timeline_v2.seq
index 2dc1720..3c97f7b 100644
--- a/workspaces/main/timeline_v2.seq
+++ b/workspaces/main/timeline_v2.seq
@@ -7,10 +7,9 @@ SEQUENCE 0.00 0 "intro"
EFFECT + PlaceholderEffect temp1 -> sink 0.00 4.00
SEQUENCE 4.00 0 "rotating_cube"
- # CircleMask (placeholder) -> RotatingCube (placeholder) -> Blur -> sink
- EFFECT + PlaceholderEffect source -> temp1 0.00 4.00
- EFFECT + PlaceholderEffect temp1 -> temp2 0.00 4.00
- EFFECT + GaussianBlurEffect temp2 -> sink 1.00 4.00
+ # RotatingCube -> Blur -> sink
+ EFFECT + RotatingCubeEffectV2 source -> temp1 0.00 4.00
+ EFFECT + GaussianBlurEffect temp1 -> sink 1.00 4.00
SEQUENCE 8.00 0 "flash_cube"
# FlashCube (placeholder) -> Flash (placeholder) -> sink