summaryrefslogtreecommitdiff
path: root/src/effects/circle_mask_effect.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/effects/circle_mask_effect.cc')
-rw-r--r--src/effects/circle_mask_effect.cc219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/effects/circle_mask_effect.cc b/src/effects/circle_mask_effect.cc
new file mode 100644
index 0000000..63c8f68
--- /dev/null
+++ b/src/effects/circle_mask_effect.cc
@@ -0,0 +1,219 @@
+// 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 "effects/circle_mask_effect.h"
+#include "generated/assets.h"
+#include "gpu/bind_group_builder.h"
+#include "gpu/shader_composer.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;
+
+ // Register auxiliary texture (width_/height_ set by resize() before init())
+ demo_->register_auxiliary_texture("circle_mask", width_, height_);
+
+ compute_params_.init(ctx_.device);
+
+ // Initialize uniforms BEFORE bind group creation
+ uniforms_.update(ctx_.queue, get_common_uniforms());
+
+ 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);
+
+ // Compose shaders to resolve #include directives
+ std::string composed_compute = ShaderComposer::Get().Compose({}, compute_shader);
+
+ WGPUShaderSourceWGSL compute_wgsl = {};
+ compute_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL;
+ compute_wgsl.code = str_view(composed_compute.c_str());
+
+ WGPUShaderModuleDescriptor compute_desc = {};
+ compute_desc.nextInChain = &compute_wgsl.chain;
+ WGPUShaderModule compute_module =
+ wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc);
+
+ const WGPUColorTargetState compute_target = {
+ .format = ctx_.format, // Match auxiliary texture format
+ .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.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;
+ 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);
+
+ WGPUBindGroupLayout compute_layout =
+ wgpuRenderPipelineGetBindGroupLayout(compute_pipeline_, 0);
+ compute_bind_group_ =
+ BindGroupBuilder()
+ .buffer(0, uniforms_.get().buffer, sizeof(CommonPostProcessUniforms))
+ .buffer(1, compute_params_.get().buffer, sizeof(CircleMaskParams))
+ .build(ctx_.device, compute_layout);
+ wgpuBindGroupLayoutRelease(compute_layout);
+
+ std::string composed_render = ShaderComposer::Get().Compose({}, render_shader);
+
+ WGPUShaderSourceWGSL render_wgsl = {};
+ render_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL;
+ render_wgsl.code = str_view(composed_render.c_str());
+
+ 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;
+ 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;
+ 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 = uniforms_.get().buffer,
+ .size = sizeof(CommonPostProcessUniforms)},
+ };
+ 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::resize(int width, int height) {
+ if (width == width_ && height == height_)
+ return;
+
+ Effect::resize(width, height);
+
+ if (!demo_)
+ return;
+
+ // Resize auxiliary texture
+ demo_->resize_auxiliary_texture("circle_mask", width, height);
+
+ // Recreate render bind group with new texture view
+ if (render_bind_group_)
+ wgpuBindGroupRelease(render_bind_group_);
+
+ WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask");
+ WGPUBindGroupLayout render_layout =
+ wgpuRenderPipelineGetBindGroupLayout(render_pipeline_, 0);
+ render_bind_group_ =
+ BindGroupBuilder()
+ .texture(0, mask_view)
+ .sampler(1, mask_sampler_)
+ .buffer(2, uniforms_.get().buffer, sizeof(CommonPostProcessUniforms))
+ .build(ctx_.device, render_layout);
+ wgpuBindGroupLayoutRelease(render_layout);
+}
+
+void CircleMaskEffect::compute(WGPUCommandEncoder encoder,
+ const CommonPostProcessUniforms& uniforms) {
+ uniforms_.update(ctx_.queue, uniforms);
+
+ const CircleMaskParams params = {
+ .radius = radius_,
+ };
+ compute_params_.update(ctx_.queue, params);
+
+ WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask");
+ 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_);
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, compute_bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
+ wgpuRenderPassEncoderEnd(pass);
+ wgpuRenderPassEncoderRelease(pass);
+}
+
+void CircleMaskEffect::render(WGPURenderPassEncoder pass,
+ const CommonPostProcessUniforms& uniforms) {
+ uniforms_.update(ctx_.queue, uniforms);
+
+ wgpuRenderPassEncoderSetPipeline(pass, render_pipeline_);
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, render_bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
+}