1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
// This file is part of the 64k demo project.
// It tests post-processing helper functions (pipeline and bind group creation).
// Validates that helpers can create valid WebGPU resources.
#include "gpu/demo_effects.h"
#include "gpu/gpu.h"
#include "../common/offscreen_render_target.h"
#include "../common/webgpu_test_fixture.h"
#include <cassert>
#include <cstdio>
// External helper functions (defined in post_process_helper.cc)
extern WGPURenderPipeline create_post_process_pipeline(WGPUDevice device,
WGPUTextureFormat format,
const char* shader_code);
extern void pp_update_bind_group(WGPUDevice device, WGPURenderPipeline pipeline,
WGPUBindGroup* bind_group,
WGPUTextureView input_view,
GpuBuffer uniforms, GpuBuffer effect_params);
// Helpers are now in gpu.h (gpu_create_post_process_texture, gpu_create_texture_view_2d)
// Minimal valid post-process shader for testing
static const char* test_shader = R"(
@vertex
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
let x = f32((vid & 1u) << 1u) - 1.0;
let y = f32((vid & 2u) >> 0u) - 1.0;
return vec4<f32>(x, y, 0.0, 1.0);
}
@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: vec4<f32>;
@group(0) @binding(3) var<uniform> effect_params: vec4<f32>; // Dummy for testing
@fragment
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
let uv = pos.xy / vec2<f32>(256.0, 256.0);
return textureSample(input_texture, input_sampler, uv);
}
)";
// Test 1: Pipeline creation
static void test_pipeline_creation(WebGPUTestFixture& fixture) {
fprintf(stdout, "Testing post-process pipeline creation...\n");
WGPURenderPipeline pipeline = create_post_process_pipeline(
fixture.device(), fixture.format(), test_shader);
assert(pipeline != nullptr && "Pipeline should be created successfully");
fprintf(stdout, " ✓ Pipeline created successfully\n");
// Cleanup
wgpuRenderPipelineRelease(pipeline);
fprintf(stdout, " ✓ Pipeline released\n");
}
// Test 2: Bind group creation
static void test_bind_group_creation(WebGPUTestFixture& fixture) {
fprintf(stdout, "Testing post-process bind group creation...\n");
// Create pipeline
WGPURenderPipeline pipeline = create_post_process_pipeline(
fixture.device(), fixture.format(), test_shader);
assert(pipeline != nullptr && "Pipeline required for bind group test");
// Create input texture with TEXTURE_BINDING usage
TextureWithView input =
gpu_create_post_process_texture(fixture.device(), 256, 256, fixture.format());
// Create uniform buffers using gpu_create_buffer
GpuBuffer uniforms = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
assert(uniforms.buffer != nullptr && "Uniform buffer should be created");
// Dummy effect params buffer for testing (matches vec4<f32>)
GpuBuffer dummy_effect_params_buffer = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
// Test bind group creation
WGPUBindGroup bind_group = nullptr;
pp_update_bind_group(fixture.device(), pipeline, &bind_group, input.view,
uniforms, dummy_effect_params_buffer);
assert(bind_group != nullptr && "Bind group should be created successfully");
fprintf(stdout, " ✓ Bind group created successfully\n");
// Cleanup
wgpuBindGroupRelease(bind_group);
wgpuTextureViewRelease(input.view);
wgpuTextureRelease(input.texture);
wgpuBufferRelease(uniforms.buffer);
wgpuBufferRelease(dummy_effect_params_buffer.buffer);
wgpuRenderPipelineRelease(pipeline);
fprintf(stdout, " ✓ Resources released\n");
}
// Test 3: Bind group update (replacement)
static void test_bind_group_update(WebGPUTestFixture& fixture) {
fprintf(stdout, "Testing post-process bind group update...\n");
WGPURenderPipeline pipeline = create_post_process_pipeline(
fixture.device(), fixture.format(), test_shader);
TextureWithView texture1 =
gpu_create_post_process_texture(fixture.device(), 256, 256, fixture.format());
TextureWithView texture2 =
gpu_create_post_process_texture(fixture.device(), 512, 512, fixture.format());
GpuBuffer uniforms = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
// Dummy effect params buffer for testing (matches vec4<f32>)
GpuBuffer dummy_effect_params_buffer = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
// Create initial bind group
WGPUBindGroup bind_group = nullptr;
pp_update_bind_group(fixture.device(), pipeline, &bind_group, texture1.view,
uniforms, dummy_effect_params_buffer);
assert(bind_group != nullptr && "Initial bind group should be created");
fprintf(stdout, " ✓ Initial bind group created\n");
// Update bind group (should release old and create new)
pp_update_bind_group(fixture.device(), pipeline, &bind_group, texture2.view,
uniforms, dummy_effect_params_buffer);
assert(bind_group != nullptr && "Updated bind group should be created");
fprintf(stdout, " ✓ Bind group updated successfully\n");
// Cleanup
wgpuBindGroupRelease(bind_group);
wgpuTextureViewRelease(texture1.view);
wgpuTextureRelease(texture1.texture);
wgpuTextureViewRelease(texture2.view);
wgpuTextureRelease(texture2.texture);
wgpuBufferRelease(uniforms.buffer);
wgpuBufferRelease(dummy_effect_params_buffer.buffer);
wgpuRenderPipelineRelease(pipeline);
fprintf(stdout, " ✓ Resources released\n");
}
// Test 4: Full post-process setup (pipeline + bind group)
static void test_full_setup(WebGPUTestFixture& fixture) {
fprintf(stdout, "Testing full post-process setup...\n");
// Create pipeline
WGPURenderPipeline pipeline = create_post_process_pipeline(
fixture.device(), fixture.format(), test_shader);
assert(pipeline != nullptr && "Pipeline creation failed");
// Create input texture (with TEXTURE_BINDING usage)
TextureWithView input =
gpu_create_post_process_texture(fixture.device(), 256, 256, fixture.format());
// Create output texture (can use OffscreenRenderTarget for this)
OffscreenRenderTarget output_target(fixture.instance(), fixture.device(), 256,
256);
GpuBuffer uniforms = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
// Dummy effect params buffer for testing (matches vec4<f32>)
GpuBuffer dummy_effect_params_buffer = gpu_create_buffer(
fixture.device(), 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
// Create bind group
WGPUBindGroup bind_group = nullptr;
pp_update_bind_group(fixture.device(), pipeline, &bind_group, input.view,
uniforms, dummy_effect_params_buffer);
assert(bind_group != nullptr && "Bind group creation failed");
fprintf(stdout, " ✓ Pipeline and bind group ready\n");
// Test render pass setup (smoke test - just verify we can create a pass)
const WGPUCommandEncoderDescriptor enc_desc = {};
WGPUCommandEncoder encoder =
wgpuDeviceCreateCommandEncoder(fixture.device(), &enc_desc);
WGPURenderPassColorAttachment color_attachment = {};
gpu_init_color_attachment(color_attachment, output_target.view());
WGPURenderPassDescriptor pass_desc = {};
pass_desc.colorAttachmentCount = 1;
pass_desc.colorAttachments = &color_attachment;
WGPURenderPassEncoder pass =
wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
// Set pipeline and bind group
wgpuRenderPassEncoderSetPipeline(pass, pipeline);
wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group, 0, nullptr);
// Draw fullscreen triangle
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(pass);
WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
wgpuQueueSubmit(wgpuDeviceGetQueue(fixture.device()), 1, &commands);
fprintf(stdout, " ✓ Render pass executed successfully\n");
// Cleanup
wgpuCommandBufferRelease(commands);
wgpuRenderPassEncoderRelease(pass);
wgpuCommandEncoderRelease(encoder);
wgpuBindGroupRelease(bind_group);
wgpuTextureViewRelease(input.view);
wgpuTextureRelease(input.texture);
wgpuBufferRelease(uniforms.buffer);
wgpuBufferRelease(dummy_effect_params_buffer.buffer);
wgpuRenderPipelineRelease(pipeline);
fprintf(stdout, " ✓ Full setup test completed\n");
}
int main() {
fprintf(stdout, "=== Post-Process Helper Tests ===\n");
WebGPUTestFixture fixture;
if (!fixture.init()) {
fprintf(stdout, " ⚠ WebGPU unavailable - skipping all tests\n");
return 0;
}
test_pipeline_creation(fixture);
test_bind_group_creation(fixture);
test_bind_group_update(fixture);
test_full_setup(fixture);
fprintf(stdout, "=== All Post-Process Helper Tests Passed ===\n");
return 0;
}
|