From 2e33a435d6f1efee5a6c88cbfc99b97241fe2ad3 Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 16 Feb 2026 10:03:18 +0100 Subject: 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 --- src/effects/rotating_cube_effect_v2.cc | 184 +++++++++++++++++++++++++++++++++ src/effects/rotating_cube_effect_v2.h | 49 +++++++++ src/gpu/shaders.cc | 4 + src/gpu/shaders.h | 1 + 4 files changed, 238 insertions(+) create mode 100644 src/effects/rotating_cube_effect_v2.cc create mode 100644 src/effects/rotating_cube_effect_v2.h (limited to 'src') 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& inputs, + const std::vector& 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& inputs, + const std::vector& 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; -- cgit v1.2.3