diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-08 21:58:49 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-08 21:58:49 +0100 |
| commit | 86e56474d284944795f4c02ae850561374620f8a (patch) | |
| tree | 7ce0b35539e6c632024dd57cf1dafaef90aae487 | |
| parent | d3a609fad91744c45f6bc59b625a26f8870e271d (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>
| -rw-r--r-- | assets/final/shaders/circle_mask_compute.wgsl | 33 | ||||
| -rw-r--r-- | assets/final/shaders/circle_mask_render.wgsl | 35 | ||||
| -rw-r--r-- | assets/final/shaders/masked_cube.wgsl | 160 | ||||
| -rw-r--r-- | src/gpu/effects/circle_mask_effect.cc | 178 | ||||
| -rw-r--r-- | src/gpu/effects/circle_mask_effect.h | 50 | ||||
| -rw-r--r-- | src/gpu/effects/rotating_cube_effect.cc | 183 | ||||
| -rw-r--r-- | src/gpu/effects/rotating_cube_effect.h | 51 |
7 files changed, 690 insertions, 0 deletions
diff --git a/assets/final/shaders/circle_mask_compute.wgsl b/assets/final/shaders/circle_mask_compute.wgsl new file mode 100644 index 0000000..610ee67 --- /dev/null +++ b/assets/final/shaders/circle_mask_compute.wgsl @@ -0,0 +1,33 @@ +// Circle mask compute shader +// Generates a circular mask (1.0 inside, 0.0 outside) + +struct Uniforms { + radius: f32, + aspect_ratio: f32, + width: f32, + height: f32, +}; + +@group(0) @binding(0) var<uniform> uniforms: Uniforms; + +struct VSOutput { + @builtin(position) position: vec4<f32>, +}; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VSOutput { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), vec2<f32>(3, -1), vec2<f32>(-1, 3)); + return VSOutput(vec4<f32>(pos[i], 0.0, 1.0)); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height); + let center = vec2<f32>(0.5, 0.5); + let aspect_corrected_uv = (uv - center) * vec2<f32>(uniforms.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); + + 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 new file mode 100644 index 0000000..902600e --- /dev/null +++ b/assets/final/shaders/circle_mask_render.wgsl @@ -0,0 +1,35 @@ +// Circle mask render shader +// Samples mask and draws green outside the circle + +@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, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +struct VSOutput { + @builtin(position) position: vec4<f32>, +}; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VSOutput { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), vec2<f32>(3, -1), vec2<f32>(-1, 3)); + return VSOutput(vec4<f32>(pos[i], 0.0, 1.0)); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / vec2<f32>(uniforms.width, uniforms.height); + let mask_value = textureSample(mask_tex, mask_sampler, uv).r; + + if (mask_value > 0.5) { + discard; + } + + return vec4<f32>(0.0, 1.0, 0.0, 1.0); +} diff --git a/assets/final/shaders/masked_cube.wgsl b/assets/final/shaders/masked_cube.wgsl new file mode 100644 index 0000000..77e2fb9 --- /dev/null +++ b/assets/final/shaders/masked_cube.wgsl @@ -0,0 +1,160 @@ +// Masked cube shader - based on renderer_3d.wgsl with mask sampling +#include "common_uniforms" +#include "math/common_utils" +#include "math/sdf_utils" + +@group(0) @binding(0) var<uniform> globals: GlobalUniforms; +@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; + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) local_pos: vec3<f32>, + @location(1) color: vec4<f32>, + @location(2) @interpolate(flat) instance_index: u32, + @location(3) world_pos: vec3<f32>, + @location(4) transformed_normal: vec3<f32>, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32) -> VertexOutput { + var pos = array<vec3<f32>, 36>( + vec3(-1.0, -1.0, 1.0), vec3( 1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), + vec3(-1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), vec3(-1.0, 1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, -1.0), vec3( 1.0, 1.0, -1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, 1.0, -1.0), vec3( 1.0, -1.0, -1.0), + vec3(-1.0, 1.0, -1.0), vec3(-1.0, 1.0, 1.0), vec3( 1.0, 1.0, 1.0), + vec3(-1.0, 1.0, -1.0), vec3( 1.0, 1.0, 1.0), vec3( 1.0, 1.0, -1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, -1.0, -1.0), vec3( 1.0, -1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3( 1.0, -1.0, 1.0), vec3(-1.0, -1.0, 1.0), + vec3( 1.0, -1.0, -1.0), vec3( 1.0, 1.0, -1.0), vec3( 1.0, 1.0, 1.0), + vec3( 1.0, -1.0, -1.0), vec3( 1.0, 1.0, 1.0), vec3( 1.0, -1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, -1.0, 1.0), vec3(-1.0, 1.0, 1.0), + vec3(-1.0, -1.0, -1.0), vec3(-1.0, 1.0, 1.0), vec3(-1.0, 1.0, -1.0) + ); + + var p = pos[vertex_index]; + let obj = object_data.objects[instance_index]; + let world_pos = obj.model * vec4<f32>(p, 1.0); + let clip_pos = globals.view_proj * world_pos; + + var out: VertexOutput; + out.position = clip_pos; + out.local_pos = p; + out.color = obj.color; + out.instance_index = instance_index; + out.world_pos = world_pos.xyz; + out.transformed_normal = normalize(vec3<f32>(0.0, 1.0, 0.0)); + + return out; +} + +#include "render/scene_query_mode" +#include "render/shadows" +#include "render/lighting_utils" +#include "ray_box" + +struct FragmentOutput { + @location(0) color: vec4<f32>, + @builtin(frag_depth) depth: f32, +}; + +@fragment +fn fs_main(in: VertexOutput) -> FragmentOutput { + let screen_uv = in.position.xy / globals.resolution; + let mask_value = textureSample(mask_tex, mask_sampler, screen_uv).r; + + if (mask_value < 0.5) { + discard; + } + + let obj = object_data.objects[in.instance_index]; + let obj_type = obj.params.x; + + var p: vec3<f32>; + var normal: vec3<f32>; + var base_color = in.color.rgb; + let light_dir = normalize(vec3<f32>(1.0, 1.0, 1.0)); + + let ray_origin = globals.camera_pos_time.xyz; + let ray_dir = normalize(in.world_pos - ray_origin); + let inv_model = obj.inv_model; + + 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) { + discard; + } + + let t_start = max(t.x, 0.0); + let t_end = t.y; + + var t_march = t_start; + let max_steps = 128; + var hit = false; + var local_p = vec3<f32>(0.0); + + for (var step = 0; step < max_steps; step++) { + local_p = local_origin + t_march * local_dir; + let d = sdBox(local_p, vec3<f32>(1.0)); + + if (d < 0.001) { + hit = true; + break; + } + + t_march += max(d * 0.5, 0.001); + if (t_march > t_end) { + break; + } + } + + if (!hit) { + discard; + } + + p = local_p; + let eps = 0.001; + normal = normalize(vec3<f32>( + sdBox(p + vec3<f32>(eps, 0.0, 0.0), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(eps, 0.0, 0.0), vec3<f32>(1.0)), + sdBox(p + vec3<f32>(0.0, eps, 0.0), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(0.0, eps, 0.0), vec3<f32>(1.0)), + sdBox(p + vec3<f32>(0.0, 0.0, eps), vec3<f32>(1.0)) - + sdBox(p - vec3<f32>(0.0, 0.0, eps), vec3<f32>(1.0)) + )); + + let world_p = (obj.model * vec4<f32>(p, 1.0)).xyz; + let world_normal = normalize((obj.model * vec4<f32>(normal, 0.0)).xyz); + + let bump_strength = 0.3; + let bump_scale = 4.0; + let noise_uv = world_p.xy * bump_scale; + let noise_val = textureSample(noise_tex, noise_sampler, noise_uv).r; + let bump_offset = (noise_val - 0.5) * bump_strength; + + let bumped_normal = normalize(world_normal + vec3<f32>(bump_offset)); + + let diffuse = max(dot(bumped_normal, light_dir), 0.0); + let ambient = 0.3; + let lighting = ambient + diffuse * 0.7; + + let final_color = base_color * lighting; + + let clip_p = globals.view_proj * vec4<f32>(world_p, 1.0); + let depth = clip_p.z / clip_p.w; + + var out: FragmentOutput; + out.color = vec4<f32>(final_color, 1.0); + out.depth = depth; + + return out; +} diff --git a/src/gpu/effects/circle_mask_effect.cc b/src/gpu/effects/circle_mask_effect.cc new file mode 100644 index 0000000..55bcb90 --- /dev/null +++ b/src/gpu/effects/circle_mask_effect.cc @@ -0,0 +1,178 @@ +// This file is part of the 64k demo project. +// It implements CircleMaskEffect for auxiliary texture masking demonstration. +// Generates circular mask and renders green background outside circle. + +#include "gpu/effects/circle_mask_effect.h" +#include "generated/assets.h" + +CircleMaskEffect::CircleMaskEffect(const GpuContext& ctx, float radius) + : Effect(ctx), radius_(radius) {} + +CircleMaskEffect::~CircleMaskEffect() { + if (mask_sampler_) wgpuSamplerRelease(mask_sampler_); + if (render_bind_group_) wgpuBindGroupRelease(render_bind_group_); + if (render_pipeline_) wgpuRenderPipelineRelease(render_pipeline_); + if (compute_bind_group_) wgpuBindGroupRelease(compute_bind_group_); + if (compute_pipeline_) wgpuRenderPipelineRelease(compute_pipeline_); +} + +void CircleMaskEffect::init(MainSequence* demo) { + demo_ = demo; + + const uint32_t width = width_; + const uint32_t height = height_; + + demo_->register_auxiliary_texture("circle_mask", width, height); + + compute_uniforms_.init(ctx_.device); + render_uniforms_.init(ctx_.device); + + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear; + sampler_desc.maxAnisotropy = 1; + mask_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); + + size_t compute_size, render_size; + const char* compute_shader = (const char*)GetAsset( + AssetId::ASSET_CIRCLE_MASK_COMPUTE_SHADER, &compute_size); + const char* render_shader = (const char*)GetAsset( + AssetId::ASSET_CIRCLE_MASK_RENDER_SHADER, &render_size); + + WGPUShaderSourceWGSL compute_wgsl = {}; + compute_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + compute_wgsl.code = str_view(compute_shader); + + WGPUShaderModuleDescriptor compute_desc = {}; + compute_desc.nextInChain = &compute_wgsl.chain; + WGPUShaderModule compute_module = wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc); + + const WGPUColorTargetState compute_target = { + .format = WGPUTextureFormat_RGBA8Unorm, + .writeMask = WGPUColorWriteMask_All, + }; + WGPUFragmentState compute_frag = {}; + compute_frag.module = compute_module; + compute_frag.entryPoint = str_view("fs_main"); + compute_frag.targetCount = 1; + compute_frag.targets = &compute_target; + WGPURenderPipelineDescriptor compute_pipeline_desc = {}; + compute_pipeline_desc.vertex.module = compute_module; + compute_pipeline_desc.vertex.entryPoint = str_view("vs_main"); + compute_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + compute_pipeline_desc.primitive.cullMode = WGPUCullMode_None; + compute_pipeline_desc.multisample.count = 1; + compute_pipeline_desc.multisample.mask = 0xFFFFFFFF; + compute_pipeline_desc.fragment = &compute_frag; + compute_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &compute_pipeline_desc); + wgpuShaderModuleRelease(compute_module); + + const WGPUBindGroupEntry compute_entries[] = { + {.binding = 0, .buffer = compute_uniforms_.get().buffer, + .size = sizeof(ComputeUniforms)}, + }; + const WGPUBindGroupDescriptor compute_bg_desc = { + .layout = wgpuRenderPipelineGetBindGroupLayout(compute_pipeline_, 0), + .entryCount = 1, + .entries = compute_entries, + }; + compute_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &compute_bg_desc); + + WGPUShaderSourceWGSL render_wgsl = {}; + render_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + render_wgsl.code = str_view(render_shader); + + WGPUShaderModuleDescriptor render_desc = {}; + render_desc.nextInChain = &render_wgsl.chain; + WGPUShaderModule render_module = wgpuDeviceCreateShaderModule(ctx_.device, &render_desc); + + const WGPUColorTargetState render_target = { + .format = ctx_.format, + .writeMask = WGPUColorWriteMask_All, + }; + WGPUFragmentState render_frag = {}; + render_frag.module = render_module; + render_frag.entryPoint = str_view("fs_main"); + render_frag.targetCount = 1; + render_frag.targets = &render_target; + WGPURenderPipelineDescriptor render_pipeline_desc = {}; + 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.multisample.count = 1; + render_pipeline_desc.multisample.mask = 0xFFFFFFFF; + render_pipeline_desc.fragment = &render_frag; + render_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &render_pipeline_desc); + wgpuShaderModuleRelease(render_module); + + WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); + const WGPUBindGroupEntry render_entries[] = { + {.binding = 0, .textureView = mask_view}, + {.binding = 1, .sampler = mask_sampler_}, + {.binding = 2, .buffer = render_uniforms_.get().buffer, + .size = sizeof(RenderUniforms)}, + }; + const WGPUBindGroupDescriptor render_bg_desc = { + .layout = wgpuRenderPipelineGetBindGroupLayout(render_pipeline_, 0), + .entryCount = 3, + .entries = render_entries, + }; + render_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &render_bg_desc); +} + +void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time, + float beat, float intensity, + float aspect_ratio) { + const uint32_t width = width_; + const uint32_t height = height_; + + const ComputeUniforms uniforms = { + .radius = radius_, + .aspect_ratio = aspect_ratio, + .width = static_cast<float>(width), + .height = static_cast<float>(height), + }; + 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, + }; + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + wgpuRenderPassEncoderSetPipeline(pass, compute_pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, compute_bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} + +void CircleMaskEffect::render(WGPURenderPassEncoder pass, float time, + float beat, float intensity, + float aspect_ratio) { + const uint32_t width = width_; + const uint32_t height = height_; + + const RenderUniforms uniforms = { + .width = static_cast<float>(width), + .height = static_cast<float>(height), + ._pad1 = 0.0f, + ._pad2 = 0.0f, + }; + render_uniforms_.update(ctx_.queue, uniforms); + + wgpuRenderPassEncoderSetPipeline(pass, render_pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, render_bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); +} diff --git a/src/gpu/effects/circle_mask_effect.h b/src/gpu/effects/circle_mask_effect.h new file mode 100644 index 0000000..57f2389 --- /dev/null +++ b/src/gpu/effects/circle_mask_effect.h @@ -0,0 +1,50 @@ +// This file is part of the 64k demo project. +// It defines the CircleMaskEffect class for masking system demonstration. +// Creates a circular mask and renders green outside the circle. + +#ifndef CIRCLE_MASK_EFFECT_H_ +#define CIRCLE_MASK_EFFECT_H_ + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +class CircleMaskEffect : public Effect { + public: + CircleMaskEffect(const GpuContext& ctx, float radius = 0.4f); + ~CircleMaskEffect() override; + + void init(MainSequence* demo) override; + void compute(WGPUCommandEncoder encoder, float time, float beat, + float intensity, float aspect_ratio) override; + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + struct ComputeUniforms { + float radius; + float aspect_ratio; + float width; + float height; + }; + + struct RenderUniforms { + float width; + float height; + float _pad1; + float _pad2; + }; + + MainSequence* demo_ = nullptr; + float radius_; + + WGPURenderPipeline compute_pipeline_ = nullptr; + WGPUBindGroup compute_bind_group_ = nullptr; + UniformBuffer<ComputeUniforms> compute_uniforms_; + + WGPURenderPipeline render_pipeline_ = nullptr; + WGPUBindGroup render_bind_group_ = nullptr; + WGPUSampler mask_sampler_ = nullptr; + UniformBuffer<RenderUniforms> render_uniforms_; +}; + +#endif /* CIRCLE_MASK_EFFECT_H_ */ 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); +} diff --git a/src/gpu/effects/rotating_cube_effect.h b/src/gpu/effects/rotating_cube_effect.h new file mode 100644 index 0000000..1ce81b7 --- /dev/null +++ b/src/gpu/effects/rotating_cube_effect.h @@ -0,0 +1,51 @@ +// This file is part of the 64k demo project. +// It defines RotatingCubeEffect for rendering a bump-mapped rotating cube. +// Uses auxiliary texture masking to render only inside a circular region. + +#ifndef ROTATING_CUBE_EFFECT_H_ +#define ROTATING_CUBE_EFFECT_H_ + +#include "gpu/effect.h" +#include "gpu/gpu.h" +#include "util/mini_math.h" + +class RotatingCubeEffect : public Effect { + public: + RotatingCubeEffect(const GpuContext& ctx); + ~RotatingCubeEffect() override; + + void init(MainSequence* demo) override; + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + struct Uniforms { + mat4 view_proj; + mat4 inv_view_proj; + vec4 camera_pos_time; + vec4 params; + vec2 resolution; + vec2 padding; + }; + + struct ObjectData { + mat4 model; + mat4 inv_model; + vec4 color; + vec4 params; + }; + + MainSequence* demo_ = nullptr; + WGPURenderPipeline pipeline_ = nullptr; + WGPUBindGroup bind_group_0_ = nullptr; + WGPUBindGroup bind_group_1_ = nullptr; + GpuBuffer uniform_buffer_; + GpuBuffer object_buffer_; + WGPUTexture noise_texture_ = nullptr; + WGPUTextureView noise_view_ = nullptr; + WGPUSampler noise_sampler_ = nullptr; + WGPUSampler mask_sampler_ = nullptr; + float rotation_ = 0.0f; +}; + +#endif /* ROTATING_CUBE_EFFECT_H_ */ |
