diff options
Diffstat (limited to 'src/effects/circle_mask_effect.cc')
| -rw-r--r-- | src/effects/circle_mask_effect.cc | 219 |
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); +} |
