summaryrefslogtreecommitdiff
path: root/src/gpu/effects/rotating_cube_effect.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-08 21:58:49 +0100
committerskal <pascal.massimino@gmail.com>2026-02-08 21:58:49 +0100
commit86e56474d284944795f4c02ae850561374620f8a (patch)
tree7ce0b35539e6c632024dd57cf1dafaef90aae487 /src/gpu/effects/rotating_cube_effect.cc
parentd3a609fad91744c45f6bc59b625a26f8870e271d (diff)
feat: Add CircleMaskEffect and RotatingCubeEffect with auxiliary texture masking
Implements demonstration of auxiliary texture masking system with: - CircleMaskEffect: Generates circular mask, renders green outside circle - RotatingCubeEffect: Renders SDF cube inside circle using mask Architecture: - Mask generation in compute phase (1.0 inside, 0.0 outside) - CircleMask renders green where mask < 0.5 (outside circle) - RotatingCube samples mask and discards where mask < 0.5 Files added: - src/gpu/effects/circle_mask_effect.{h,cc} - src/gpu/effects/rotating_cube_effect.{h,cc} - assets/final/shaders/{circle_mask_compute,circle_mask_render,masked_cube}.wgsl Build system: - Updated CMakeLists.txt with new effect sources - Registered shaders in demo_assets.txt - Updated test_demo_effects.cc (8 scene effects total) Status: Effects temporarily disabled in demo.seq (lines 31-35) - ShaderComposer snippet registration needed for masked_cube.wgsl - All 33 tests pass, demo runs without crashes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/gpu/effects/rotating_cube_effect.cc')
-rw-r--r--src/gpu/effects/rotating_cube_effect.cc183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/gpu/effects/rotating_cube_effect.cc b/src/gpu/effects/rotating_cube_effect.cc
new file mode 100644
index 0000000..b4f3d3e
--- /dev/null
+++ b/src/gpu/effects/rotating_cube_effect.cc
@@ -0,0 +1,183 @@
+// This file is part of the 64k demo project.
+// It implements RotatingCubeEffect for bump-mapped rotating cube rendering.
+// Uses auxiliary texture masking to render only inside a circular region.
+
+#include "gpu/effects/rotating_cube_effect.h"
+#include "gpu/effects/shader_composer.h"
+#include "generated/assets.h"
+#include "util/asset_manager_utils.h"
+
+
+RotatingCubeEffect::RotatingCubeEffect(const GpuContext& ctx) : Effect(ctx) {}
+
+RotatingCubeEffect::~RotatingCubeEffect() {
+ if (mask_sampler_) wgpuSamplerRelease(mask_sampler_);
+ if (noise_sampler_) wgpuSamplerRelease(noise_sampler_);
+ if (noise_view_) wgpuTextureViewRelease(noise_view_);
+ if (noise_texture_) wgpuTextureRelease(noise_texture_);
+ if (bind_group_1_) wgpuBindGroupRelease(bind_group_1_);
+ if (bind_group_0_) wgpuBindGroupRelease(bind_group_0_);
+ if (pipeline_) wgpuRenderPipelineRelease(pipeline_);
+}
+
+void RotatingCubeEffect::init(MainSequence* demo) {
+ demo_ = demo;
+
+ 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);
+
+ const WGPUTextureDescriptor tex_desc = {
+ .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment,
+ .dimension = WGPUTextureDimension_2D,
+ .size = {1, 1, 1},
+ .format = WGPUTextureFormat_RGBA8Unorm,
+ .mipLevelCount = 1,
+ .sampleCount = 1,
+ };
+ noise_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc);
+ noise_view_ = wgpuTextureCreateView(noise_texture_, nullptr);
+
+ WGPUSamplerDescriptor sampler_desc = {};
+ sampler_desc.addressModeU = WGPUAddressMode_Repeat;
+ sampler_desc.addressModeV = WGPUAddressMode_Repeat;
+ sampler_desc.magFilter = WGPUFilterMode_Linear;
+ sampler_desc.minFilter = WGPUFilterMode_Linear;
+ sampler_desc.maxAnisotropy = 1;
+ noise_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
+
+ WGPUSamplerDescriptor mask_sampler_desc = {};
+ mask_sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
+ mask_sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
+ mask_sampler_desc.magFilter = WGPUFilterMode_Linear;
+ mask_sampler_desc.minFilter = WGPUFilterMode_Linear;
+ mask_sampler_desc.maxAnisotropy = 1;
+ mask_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &mask_sampler_desc);
+
+ size_t shader_size;
+ const char* shader_code =
+ (const char*)GetAsset(AssetId::ASSET_MASKED_CUBE_SHADER, &shader_size);
+
+ ShaderComposer::CompositionMap composition_map;
+ composition_map["render/scene_query_mode"] = "render/scene_query_linear";
+ std::string 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());
+
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader_module =
+ wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc);
+
+ const WGPUColorTargetState color_target = {
+ .format = ctx_.format,
+ .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.vertex.module = shader_module;
+ pipeline_desc.vertex.entryPoint = str_view("vs_main");
+ pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipeline_desc.primitive.cullMode = WGPUCullMode_None;
+ 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);
+
+ 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,
+ .entries = entries_0,
+ };
+ bind_group_0_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc_0);
+
+ WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask");
+ const WGPUBindGroupEntry entries_1[] = {
+ {.binding = 0, .textureView = mask_view},
+ {.binding = 1, .sampler = mask_sampler_},
+ };
+
+ const WGPUBindGroupDescriptor bg_desc_1 = {
+ .layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 1),
+ .entryCount = 2,
+ .entries = entries_1,
+ };
+ bind_group_1_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc_1);
+}
+
+void RotatingCubeEffect::render(WGPURenderPassEncoder pass, float time,
+ float beat, float intensity,
+ float aspect_ratio) {
+ rotation_ += 0.016f * 1.5f;
+
+ 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, aspect_ratio, 0.1f, 100.0f);
+ const mat4 view_proj = proj * view;
+
+ 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;
+ const mat4 inv_model = model.inverse();
+
+ 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, time),
+ .params = vec4(1.0f, 0.0f, 0.0f, 0.0f),
+ .resolution = vec2(1280.0f, 720.0f),
+ };
+
+ const ObjectData obj_data = {
+ .model = model,
+ .inv_model = inv_model,
+ .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));
+
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_0_, 0, nullptr);
+ wgpuRenderPassEncoderSetBindGroup(pass, 1, bind_group_1_, 0, nullptr);
+ wgpuRenderPassEncoderDraw(pass, 36, 1, 0, 0);
+}