summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/DemoTests.cmake16
-rw-r--r--common/shaders/gaussian_blur_v2.wgsl45
-rw-r--r--common/shaders/heptagon_v2.wgsl46
-rw-r--r--common/shaders/passthrough_v2.wgsl24
-rw-r--r--common/shaders/sequence_v2_uniforms.wgsl12
-rw-r--r--src/effects/gaussian_blur_effect_v2.cc3
-rw-r--r--src/effects/heptagon_effect_v2.cc2
-rw-r--r--src/effects/passthrough_effect_v2.cc44
-rw-r--r--src/effects/passthrough_effect_v2.h2
-rw-r--r--src/gpu/shaders.cc15
-rw-r--r--src/gpu/shaders.h5
-rw-r--r--src/tests/gpu/test_sequence_v2_e2e.cc114
-rw-r--r--workspaces/main/assets.txt6
-rw-r--r--workspaces/test/assets.txt6
14 files changed, 302 insertions, 38 deletions
diff --git a/cmake/DemoTests.cmake b/cmake/DemoTests.cmake
index dbb7816..2f7cdd1 100644
--- a/cmake/DemoTests.cmake
+++ b/cmake/DemoTests.cmake
@@ -243,14 +243,14 @@ add_demo_test(test_sequence_v2 SequenceV2Test gpu
target_link_libraries(test_sequence_v2 PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
demo_add_asset_deps(test_sequence_v2 shaders)
-# Sequence v2 End-to-End Test (disabled: shader compatibility pending)
-# add_demo_test(test_sequence_v2_e2e SequenceV2E2ETest gpu
-# src/tests/gpu/test_sequence_v2_e2e.cc
-# src/tests/common/webgpu_test_fixture.cc
-# ${PLATFORM_SOURCES}
-# ${GEN_DEMO_CC})
-# target_link_libraries(test_sequence_v2_e2e PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
-# demo_add_asset_deps(test_sequence_v2_e2e shaders)
+# Sequence v2 End-to-End Test
+add_demo_test(test_sequence_v2_e2e SequenceV2E2ETest gpu
+ src/tests/gpu/test_sequence_v2_e2e.cc
+ src/tests/common/webgpu_test_fixture.cc
+ ${PLATFORM_SOURCES}
+ ${GEN_DEMO_CC})
+target_link_libraries(test_sequence_v2_e2e PRIVATE 3d gpu audio procedural util ${DEMO_LIBS})
+demo_add_asset_deps(test_sequence_v2_e2e shaders)
# Subsystem test targets
add_custom_target(run_audio_tests
diff --git a/common/shaders/gaussian_blur_v2.wgsl b/common/shaders/gaussian_blur_v2.wgsl
new file mode 100644
index 0000000..0f29140
--- /dev/null
+++ b/common/shaders/gaussian_blur_v2.wgsl
@@ -0,0 +1,45 @@
+// Gaussian blur shader for Sequence v2
+#include "sequence_v2_uniforms"
+
+@group(0) @binding(0) var input_sampler: sampler;
+@group(0) @binding(1) var input_texture: texture_2d<f32>;
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+struct GaussianBlurParams {
+ direction: vec2<f32>,
+ radius: f32,
+ _pad: f32,
+};
+@group(0) @binding(3) var<uniform> params: GaussianBlurParams;
+
+struct VertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) uv: vec2<f32>,
+};
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput {
+ var out: VertexOutput;
+ let x = f32((vid & 1u) << 1u);
+ let y = f32((vid & 2u));
+ out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
+ out.uv = vec2<f32>(x, y);
+ return out;
+}
+
+@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+ let texel_size = 1.0 / uniforms.resolution;
+ let offset = params.direction * texel_size;
+
+ var color = vec4<f32>(0.0);
+ let kernel_size = i32(params.radius);
+ var weight_sum = 0.0;
+
+ for (var i = -kernel_size; i <= kernel_size; i++) {
+ let sample_offset = f32(i) * offset;
+ let weight = exp(-f32(i * i) / (2.0 * params.radius * params.radius));
+ color += textureSample(input_texture, input_sampler, in.uv + sample_offset) * weight;
+ weight_sum += weight;
+ }
+
+ return color / weight_sum;
+}
diff --git a/common/shaders/heptagon_v2.wgsl b/common/shaders/heptagon_v2.wgsl
new file mode 100644
index 0000000..9d0761c
--- /dev/null
+++ b/common/shaders/heptagon_v2.wgsl
@@ -0,0 +1,46 @@
+// Heptagon shader for Sequence v2
+#include "sequence_v2_uniforms"
+
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+struct VertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) uv: vec2<f32>,
+};
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput {
+ var out: VertexOutput;
+ let x = f32((vid & 1u) << 1u);
+ let y = f32((vid & 2u));
+ out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
+ out.uv = vec2<f32>(x, y);
+ return out;
+}
+
+fn sdf_heptagon(p: vec2<f32>, r: f32) -> f32 {
+ let k = vec3<f32>(0.868516, -0.495754, 0.357407);
+ var p_abs = abs(p);
+ p_abs -= 2.0 * k.xy * min(dot(k.xy, p_abs), 0.0);
+ p_abs -= 2.0 * vec2<f32>(-k.x, k.y) * min(dot(vec2<f32>(-k.x, k.y), p_abs), 0.0);
+ p_abs -= vec2<f32>(clamp(p_abs.x, -k.z * r, k.z * r), r);
+ return length(p_abs) * sign(p_abs.y);
+}
+
+@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+ let aspect = uniforms.aspect_ratio;
+ let uv = (in.uv * 2.0 - 1.0) * vec2<f32>(aspect, 1.0);
+
+ let rotation = uniforms.beat_time * 0.5;
+ let c = cos(rotation);
+ let s = sin(rotation);
+ let rot_uv = vec2<f32>(
+ uv.x * c - uv.y * s,
+ uv.x * s + uv.y * c
+ );
+
+ let dist = sdf_heptagon(rot_uv, 0.5);
+ let color = mix(vec3<f32>(0.2, 0.4, 0.8), vec3<f32>(1.0, 0.8, 0.2),
+ smoothstep(0.01, -0.01, dist));
+
+ return vec4<f32>(color, 1.0);
+}
diff --git a/common/shaders/passthrough_v2.wgsl b/common/shaders/passthrough_v2.wgsl
new file mode 100644
index 0000000..e2fdc25
--- /dev/null
+++ b/common/shaders/passthrough_v2.wgsl
@@ -0,0 +1,24 @@
+// Passthrough shader for Sequence v2
+#include "sequence_v2_uniforms"
+
+@group(0) @binding(0) var input_sampler: sampler;
+@group(0) @binding(1) var input_texture: texture_2d<f32>;
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+struct VertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) uv: vec2<f32>,
+};
+
+@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput {
+ var out: VertexOutput;
+ let x = f32((vid & 1u) << 1u);
+ let y = f32((vid & 2u));
+ out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
+ out.uv = vec2<f32>(x, y);
+ return out;
+}
+
+@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+ return textureSample(input_texture, input_sampler, in.uv);
+}
diff --git a/common/shaders/sequence_v2_uniforms.wgsl b/common/shaders/sequence_v2_uniforms.wgsl
new file mode 100644
index 0000000..b302329
--- /dev/null
+++ b/common/shaders/sequence_v2_uniforms.wgsl
@@ -0,0 +1,12 @@
+// Sequence v2 uniform structure for WGSL shaders
+// Matches UniformsSequenceParams in sequence_v2.h
+
+struct UniformsSequenceParams {
+ resolution: vec2<f32>,
+ aspect_ratio: f32,
+ time: f32,
+ beat_time: f32,
+ beat_phase: f32,
+ audio_intensity: f32,
+ _pad: f32,
+};
diff --git a/src/effects/gaussian_blur_effect_v2.cc b/src/effects/gaussian_blur_effect_v2.cc
index 6b37f0b..f87de8b 100644
--- a/src/effects/gaussian_blur_effect_v2.cc
+++ b/src/effects/gaussian_blur_effect_v2.cc
@@ -11,7 +11,7 @@ GaussianBlurEffectV2::GaussianBlurEffectV2(const GpuContext& ctx,
sampler_(nullptr) {
// Create pipeline
pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm,
- gaussian_blur_shader_wgsl);
+ gaussian_blur_v2_shader_wgsl);
// Create sampler
WGPUSamplerDescriptor sampler_desc = {};
@@ -19,6 +19,7 @@ GaussianBlurEffectV2::GaussianBlurEffectV2(const GpuContext& ctx,
sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
sampler_desc.magFilter = WGPUFilterMode_Linear;
sampler_desc.minFilter = WGPUFilterMode_Linear;
+ sampler_desc.maxAnisotropy = 1;
sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
// Init uniform buffers
diff --git a/src/effects/heptagon_effect_v2.cc b/src/effects/heptagon_effect_v2.cc
index 4478327..3512ec7 100644
--- a/src/effects/heptagon_effect_v2.cc
+++ b/src/effects/heptagon_effect_v2.cc
@@ -14,7 +14,7 @@ HeptagonEffectV2::HeptagonEffectV2(const GpuContext& ctx,
// Create render pass using helper
ResourceBinding bindings[] = {{uniforms_buffer_.get(), WGPUBufferBindingType_Uniform}};
RenderPass pass = gpu_create_render_pass(ctx_.device, WGPUTextureFormat_RGBA8Unorm,
- main_shader_wgsl, bindings, 1);
+ heptagon_v2_shader_wgsl, bindings, 1);
pipeline_ = pass.pipeline;
bind_group_ = pass.bind_group;
}
diff --git a/src/effects/passthrough_effect_v2.cc b/src/effects/passthrough_effect_v2.cc
index d98315f..5203f97 100644
--- a/src/effects/passthrough_effect_v2.cc
+++ b/src/effects/passthrough_effect_v2.cc
@@ -9,9 +9,11 @@ PassthroughEffectV2::PassthroughEffectV2(const GpuContext& ctx,
const std::vector<std::string>& outputs)
: EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr),
sampler_(nullptr) {
+ // Init uniform buffer
+ uniforms_buffer_.init(ctx_.device);
// Create pipeline
pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm,
- passthrough_shader_wgsl);
+ passthrough_v2_shader_wgsl);
// Create sampler
WGPUSamplerDescriptor sampler_desc = {};
@@ -21,6 +23,7 @@ PassthroughEffectV2::PassthroughEffectV2(const GpuContext& ctx,
sampler_desc.magFilter = WGPUFilterMode_Linear;
sampler_desc.minFilter = WGPUFilterMode_Linear;
sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
+ sampler_desc.maxAnisotropy = 1;
sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
}
@@ -31,36 +34,21 @@ void PassthroughEffectV2::render(WGPUCommandEncoder encoder,
WGPUTextureView input_view = nodes.get_view(input_nodes_[0]);
WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
- // Update bind group (recreate each frame for simplicity)
- WGPUBindGroupEntry entries[3] = {};
+ // Update uniforms
+ uniforms_buffer_.update(ctx_.queue, params);
- entries[0].binding = PP_BINDING_SAMPLER;
- entries[0].sampler = sampler_;
-
- entries[1].binding = PP_BINDING_TEXTURE;
- entries[1].textureView = input_view;
-
- // Uniforms (binding 2) - use empty buffer for now
- entries[2].binding = PP_BINDING_UNIFORMS;
- entries[2].buffer = nullptr;
- entries[2].size = 0;
-
- WGPUBindGroupDescriptor bg_desc = {};
- bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0);
- bg_desc.entryCount = 2; // Only sampler and texture, no uniforms
- bg_desc.entries = entries;
-
- if (bind_group_) {
- wgpuBindGroupRelease(bind_group_);
- }
- bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc);
+ // Update bind group using helper (handles dummy buffers)
+ pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view,
+ uniforms_buffer_.get(), {nullptr, 0});
// Render pass
- WGPURenderPassColorAttachment color_attachment = {};
- color_attachment.view = output_view;
- color_attachment.loadOp = WGPULoadOp_Clear;
- color_attachment.storeOp = WGPUStoreOp_Store;
- color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0};
+ WGPURenderPassColorAttachment color_attachment = {
+ .view = output_view,
+ .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED,
+ .loadOp = WGPULoadOp_Clear,
+ .storeOp = WGPUStoreOp_Store,
+ .clearValue = {0.0, 0.0, 0.0, 1.0}
+ };
WGPURenderPassDescriptor pass_desc = {};
pass_desc.colorAttachmentCount = 1;
diff --git a/src/effects/passthrough_effect_v2.h b/src/effects/passthrough_effect_v2.h
index 813361e..a272b87 100644
--- a/src/effects/passthrough_effect_v2.h
+++ b/src/effects/passthrough_effect_v2.h
@@ -3,6 +3,7 @@
#pragma once
#include "gpu/effect_v2.h"
+#include "gpu/uniform_helper.h"
class PassthroughEffectV2 : public EffectV2 {
public:
@@ -16,4 +17,5 @@ class PassthroughEffectV2 : public EffectV2 {
WGPURenderPipeline pipeline_;
WGPUBindGroup bind_group_;
WGPUSampler sampler_;
+ UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
};
diff --git a/src/gpu/shaders.cc b/src/gpu/shaders.cc
index 30bbb0c..d768cef 100644
--- a/src/gpu/shaders.cc
+++ b/src/gpu/shaders.cc
@@ -31,6 +31,8 @@ void InitShaderComposer() {
};
register_if_exists("common_uniforms", AssetId::ASSET_SHADER_COMMON_UNIFORMS);
+ register_if_exists("sequence_v2_uniforms",
+ AssetId::ASSET_SHADER_SEQUENCE_V2_UNIFORMS);
register_if_exists("camera_common", AssetId::ASSET_SHADER_CAMERA_COMMON);
register_if_exists("math/sdf_shapes", AssetId::ASSET_SHADER_MATH_SDF_SHAPES);
register_if_exists("math/sdf_utils", AssetId::ASSET_SHADER_MATH_SDF_UTILS);
@@ -156,3 +158,16 @@ const char* gen_mask_compute_wgsl =
const char* vignette_shader_wgsl =
SafeGetAsset(AssetId::ASSET_SHADER_VIGNETTE);
+
+// Sequence v2 shaders
+const char* passthrough_v2_shader_wgsl =
+
+ SafeGetAsset(AssetId::ASSET_SHADER_PASSTHROUGH_V2);
+
+const char* gaussian_blur_v2_shader_wgsl =
+
+ SafeGetAsset(AssetId::ASSET_SHADER_GAUSSIAN_BLUR_V2);
+
+const char* heptagon_v2_shader_wgsl =
+
+ SafeGetAsset(AssetId::ASSET_SHADER_HEPTAGON_V2);
diff --git a/src/gpu/shaders.h b/src/gpu/shaders.h
index 03263db..d1658fa 100644
--- a/src/gpu/shaders.h
+++ b/src/gpu/shaders.h
@@ -28,3 +28,8 @@ extern const char* gen_grid_compute_wgsl;
extern const char* gen_blend_compute_wgsl;
extern const char* gen_mask_compute_wgsl;
#endif
+
+// Sequence v2 shaders
+extern const char* passthrough_v2_shader_wgsl;
+extern const char* gaussian_blur_v2_shader_wgsl;
+extern const char* heptagon_v2_shader_wgsl;
diff --git a/src/tests/gpu/test_sequence_v2_e2e.cc b/src/tests/gpu/test_sequence_v2_e2e.cc
new file mode 100644
index 0000000..0c7c619
--- /dev/null
+++ b/src/tests/gpu/test_sequence_v2_e2e.cc
@@ -0,0 +1,114 @@
+// End-to-end test for Sequence v2 system
+// Tests compiler output instantiation and execution
+
+#include "gpu/sequence_v2.h"
+#include "gpu/effect_v2.h"
+#include "effects/gaussian_blur_effect_v2.h"
+#include "effects/heptagon_effect_v2.h"
+#include "effects/passthrough_effect_v2.h"
+#include "gpu/shaders.h"
+#include "tests/common/webgpu_test_fixture.h"
+#include <cassert>
+#include <cstdio>
+
+// Manually transcribed generated sequence (simulates compiler output)
+// Simple 2-effect chain to validate DAG execution
+class SimpleTestSequence : public SequenceV2 {
+ public:
+ SimpleTestSequence(const GpuContext& ctx, int width, int height)
+ : SequenceV2(ctx, width, height) {
+ // Node declarations (including source/sink for testing)
+ nodes_.declare_node("source", NodeType::U8X4_NORM, width_, height_);
+ nodes_.declare_node("temp", NodeType::U8X4_NORM, width_, height_);
+ nodes_.declare_node("sink", NodeType::U8X4_NORM, width_, height_);
+
+ // Effect DAG construction (2 effects: source->temp->sink)
+ effect_dag_.push_back({
+ .effect = std::make_shared<PassthroughEffectV2>(ctx,
+ std::vector<std::string>{"source"},
+ std::vector<std::string>{"temp"}),
+ .input_nodes = {"source"},
+ .output_nodes = {"temp"},
+ .execution_order = 0
+ });
+ effect_dag_.push_back({
+ .effect = std::make_shared<PassthroughEffectV2>(ctx,
+ std::vector<std::string>{"temp"},
+ std::vector<std::string>{"sink"}),
+ .input_nodes = {"temp"},
+ .output_nodes = {"sink"},
+ .execution_order = 1
+ });
+ }
+};
+
+// Test: Instantiate and run v2 sequence
+void test_sequence_v2_instantiation() {
+ WebGPUTestFixture fixture;
+ if (!fixture.init()) {
+ fprintf(stderr, "Skipping test (no GPU)\n");
+ return;
+ }
+
+ // Initialize shader composer with snippets
+ InitShaderComposer();
+
+ // Create sequence
+ SimpleTestSequence seq(fixture.ctx(), 1280, 720);
+
+ // Preprocess
+ seq.preprocess(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Create command encoder
+ WGPUCommandEncoderDescriptor enc_desc = {};
+ WGPUCommandEncoder encoder =
+ wgpuDeviceCreateCommandEncoder(fixture.ctx().device, &enc_desc);
+
+ // Execute DAG (should not crash)
+ seq.render_effects(encoder);
+
+ // Cleanup
+ WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, nullptr);
+ wgpuCommandBufferRelease(cmd);
+ wgpuCommandEncoderRelease(encoder);
+
+ printf("PASS: Sequence v2 instantiation and execution\n");
+}
+
+// Test: Verify DAG execution order
+void test_dag_execution_order() {
+ WebGPUTestFixture fixture;
+ if (!fixture.init()) {
+ fprintf(stderr, "Skipping test (no GPU)\n");
+ return;
+ }
+
+ // Initialize shader composer with snippets
+ InitShaderComposer();
+
+ SimpleTestSequence seq(fixture.ctx(), 1280, 720);
+
+ // Verify effects are in correct order
+ const auto& dag = seq.get_effect_dag();
+ assert(dag.size() == 2);
+ assert(dag[0].execution_order == 0);
+ assert(dag[1].execution_order == 1);
+
+ // Verify node routing
+ assert(dag[0].input_nodes[0] == "source");
+ assert(dag[0].output_nodes[0] == "temp");
+ assert(dag[1].input_nodes[0] == "temp");
+ assert(dag[1].output_nodes[0] == "sink");
+
+ printf("PASS: DAG execution order validated\n");
+}
+
+int main() {
+ printf("Running Sequence v2 end-to-end tests...\n");
+
+ test_sequence_v2_instantiation();
+ test_dag_execution_order();
+
+ printf("All Sequence v2 e2e tests passed!\n");
+ return 0;
+}
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index 189e965..0558ae1 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -78,3 +78,9 @@ CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask
CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader"
MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader"
SHADER_SCENE1, NONE, shaders/scene1.wgsl, "Scene1 effect shader"
+
+# --- Sequence v2 Shaders ---
+SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_v2_uniforms.wgsl, "Sequence v2 Uniforms Snippet"
+SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough_v2.wgsl, "Passthrough Shader (v2)"
+SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur_v2.wgsl, "Gaussian Blur Shader (v2)"
+SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon_v2.wgsl, "Heptagon Shader (v2)"
diff --git a/workspaces/test/assets.txt b/workspaces/test/assets.txt
index 8eb692e..5ed8af6 100644
--- a/workspaces/test/assets.txt
+++ b/workspaces/test/assets.txt
@@ -68,6 +68,12 @@ CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask
CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader"
MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader"
+# --- Sequence v2 Shaders ---
+SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_v2_uniforms.wgsl, "Sequence v2 Uniforms Snippet"
+SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough_v2.wgsl, "Passthrough Shader (v2)"
+SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur_v2.wgsl, "Gaussian Blur Shader (v2)"
+SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon_v2.wgsl, "Heptagon Shader (v2)"
+
# --- Test Assets (for test_assets.cc) ---
TEST_ASSET_1, NONE, test_assets/test_asset_1.txt, "Test static asset"
TEST_IMAGE, NONE, test_assets/test_image.png, "Test 2x2 RGBA PNG image"