summaryrefslogtreecommitdiff
path: root/src/gpu/effects
diff options
context:
space:
mode:
Diffstat (limited to 'src/gpu/effects')
-rw-r--r--src/gpu/effects/circle_mask_effect.cc189
-rw-r--r--src/gpu/effects/circle_mask_effect.h50
-rw-r--r--src/gpu/effects/distort_effect.cc28
-rw-r--r--src/gpu/effects/rotating_cube_effect.cc181
-rw-r--r--src/gpu/effects/rotating_cube_effect.h55
-rw-r--r--src/gpu/effects/shaders.cc4
-rw-r--r--src/gpu/effects/shaders.h1
-rw-r--r--src/gpu/effects/vignette_effect.cc36
8 files changed, 536 insertions, 8 deletions
diff --git a/src/gpu/effects/circle_mask_effect.cc b/src/gpu/effects/circle_mask_effect.cc
new file mode 100644
index 0000000..226b603
--- /dev/null
+++ b/src/gpu/effects/circle_mask_effect.cc
@@ -0,0 +1,189 @@
+// 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 = 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);
+
+ 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;
+ 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 = 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");
+ 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, 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/distort_effect.cc b/src/gpu/effects/distort_effect.cc
index b7e27a7..589cdff 100644
--- a/src/gpu/effects/distort_effect.cc
+++ b/src/gpu/effects/distort_effect.cc
@@ -5,21 +5,33 @@
#include "gpu/gpu.h"
// --- DistortEffect ---
-DistortEffect::DistortEffect(const GpuContext& ctx) : PostProcessEffect(ctx) {
- uniforms_ =
- gpu_create_buffer(ctx_.device, sizeof(float) * 6,
- WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
+DistortEffect::DistortEffect(const GpuContext& ctx)
+ : DistortEffect(ctx, DistortParams()) {}
+
+DistortEffect::DistEffect(const GpuContext& ctx, const DistortParams& params)
+ : PostProcessEffect(ctx), params_(params) {
+ uniforms_ = gpu_create_buffer(ctx_.device, sizeof(DistortUniforms),
+ WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
pipeline_ = create_post_process_pipeline(ctx_.device, ctx_.format,
distort_shader_wgsl);
}
+
void DistortEffect::render(WGPURenderPassEncoder pass, float t, float b,
float i, float a) {
- struct {
- float t, b, i, a, w, h;
- } u = {t, b, i, a, (float)width_, (float)height_};
+ DistortUniforms u = {
+ .time = t,
+ .beat = b,
+ .intensity = i,
+ .aspect_ratio = a,
+ .width = (float)width_,
+ .height = (float)height_,
+ .strength = params_.strength,
+ .speed = params_.speed,
+ };
wgpuQueueWriteBuffer(ctx_.queue, uniforms_.buffer, 0, &u, sizeof(u));
PostProcessEffect::render(pass, t, b, i, a);
}
+
void DistortEffect::update_bind_group(WGPUTextureView v) {
pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, v, uniforms_);
-}
+} \ No newline at end of file
diff --git a/src/gpu/effects/rotating_cube_effect.cc b/src/gpu/effects/rotating_cube_effect.cc
new file mode 100644
index 0000000..7f590c5
--- /dev/null
+++ b/src/gpu/effects/rotating_cube_effect.cc
@@ -0,0 +1,181 @@
+// 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";
+ 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);
+
+ 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_},
+ };
+
+ const WGPUBindGroupDescriptor bg_desc_0 = {
+ .layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0),
+ .entryCount = 4,
+ .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..89b3fa6
--- /dev/null
+++ b/src/gpu/effects/rotating_cube_effect.h
@@ -0,0 +1,55 @@
+// 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"
+#include <string>
+
+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;
+
+ // Store composed shader to keep it alive for WebGPU
+ std::string composed_shader_;
+};
+
+#endif /* ROTATING_CUBE_EFFECT_H_ */
diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc
index ce60a74..2e1cfe5 100644
--- a/src/gpu/effects/shaders.cc
+++ b/src/gpu/effects/shaders.cc
@@ -98,3 +98,7 @@ const char* distort_shader_wgsl =
const char* chroma_aberration_shader_wgsl =
SafeGetAsset(AssetId::ASSET_SHADER_CHROMA_ABERRATION);
+
+const char* vignette_shader_wgsl =
+
+ SafeGetAsset(AssetId::ASSET_SHADER_VIGNETTE);
diff --git a/src/gpu/effects/shaders.h b/src/gpu/effects/shaders.h
index f8e45ba..50b4f32 100644
--- a/src/gpu/effects/shaders.h
+++ b/src/gpu/effects/shaders.h
@@ -17,3 +17,4 @@ extern const char* gaussian_blur_shader_wgsl;
extern const char* solarize_shader_wgsl;
extern const char* distort_shader_wgsl;
extern const char* chroma_aberration_shader_wgsl;
+extern const char* vignette_shader_wgsl;
diff --git a/src/gpu/effects/vignette_effect.cc b/src/gpu/effects/vignette_effect.cc
new file mode 100644
index 0000000..d3572a3
--- /dev/null
+++ b/src/gpu/effects/vignette_effect.cc
@@ -0,0 +1,36 @@
+// This file is part of the 64k demo project.
+// It implements the VignetteEffect.
+
+#include "gpu/demo_effects.h"
+#include "gpu/gpu.h"
+
+VignetteEffect::VignetteEffect(const GpuContext& ctx)
+ : VignetteEffect(ctx, VignetteParams()) {}
+
+VignetteEffect::VignetteEffect(const GpuContext& ctx,
+ const VignetteParams& params)
+ : PostProcessEffect(ctx), params_(params) {
+ uniforms_.init(ctx_.device);
+ pipeline_ = create_post_process_pipeline(ctx_.device, ctx_.format,
+ vignette_shader_wgsl);
+}
+
+void VignetteEffect::render(WGPURenderPassEncoder pass, float t, float b,
+ float i, float a) {
+ VignetteUniforms u = {
+ .time = t,
+ .beat = b,
+ .intensity = i,
+ .aspect_ratio = a,
+ .width = (float)width_,
+ .height = (float)height_,
+ .radius = params_.radius,
+ .softness = params_.softness,
+ };
+ uniforms_.update(ctx_.queue, u);
+ PostProcessEffect::render(pass, t, b, i, a);
+}
+
+void VignetteEffect::update_bind_group(WGPUTextureView v) {
+ pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, v, uniforms_.get());
+}