// 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. #if !defined(STRIP_ALL) // Test code only - zero size impact on final binary #include "webgpu_test_fixture.h" #include "offscreen_render_target.h" #include "gpu/demo_effects.h" #include "gpu/gpu.h" #include #include // 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); // Helper: Create a texture suitable for post-processing (both render target and texture binding) static WGPUTexture create_post_process_texture(WGPUDevice device, int width, int height, WGPUTextureFormat format) { const WGPUTextureDescriptor texture_desc = { .usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopySrc, .dimension = WGPUTextureDimension_2D, .size = {static_cast(width), static_cast(height), 1}, .format = format, .mipLevelCount = 1, .sampleCount = 1, }; return wgpuDeviceCreateTexture(device, &texture_desc); } // Helper: Create texture view static WGPUTextureView create_texture_view(WGPUTexture texture, WGPUTextureFormat format) { const WGPUTextureViewDescriptor view_desc = { .format = format, .dimension = WGPUTextureViewDimension_2D, .baseMipLevel = 0, .mipLevelCount = 1, .baseArrayLayer = 0, .arrayLayerCount = 1, }; return wgpuTextureCreateView(texture, &view_desc); } // 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 { let x = f32((vid & 1u) << 1u) - 1.0; let y = f32((vid & 2u) >> 0u) - 1.0; return vec4(x, y, 0.0, 1.0); } @group(0) @binding(0) var input_sampler: sampler; @group(0) @binding(1) var input_texture: texture_2d; @group(0) @binding(2) var uniforms: vec4; @fragment fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { let uv = pos.xy / vec2(256.0, 256.0); return textureSample(input_texture, input_sampler, uv); } )"; // Test 1: Pipeline creation static void test_pipeline_creation() { fprintf(stdout, "Testing post-process pipeline creation...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } 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() { fprintf(stdout, "Testing post-process bind group creation...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } // 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 WGPUTexture input_texture = create_post_process_texture( fixture.device(), 256, 256, fixture.format()); WGPUTextureView input_view = create_texture_view(input_texture, fixture.format()); // Create uniform buffer const WGPUBufferDescriptor uniform_desc = { .usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, .size = 16, // vec4 }; WGPUBuffer uniform_buffer = wgpuDeviceCreateBuffer(fixture.device(), &uniform_desc); assert(uniform_buffer != nullptr && "Uniform buffer should be created"); GpuBuffer uniforms = {uniform_buffer, 16}; // Test bind group creation WGPUBindGroup bind_group = nullptr; pp_update_bind_group(fixture.device(), pipeline, &bind_group, input_view, uniforms); 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(uniform_buffer); wgpuRenderPipelineRelease(pipeline); fprintf(stdout, " ✓ Resources released\n"); } // Test 3: Bind group update (replacement) static void test_bind_group_update() { fprintf(stdout, "Testing post-process bind group update...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } WGPURenderPipeline pipeline = create_post_process_pipeline( fixture.device(), fixture.format(), test_shader); WGPUTexture texture1 = create_post_process_texture( fixture.device(), 256, 256, fixture.format()); WGPUTextureView view1 = create_texture_view(texture1, fixture.format()); WGPUTexture texture2 = create_post_process_texture( fixture.device(), 512, 512, fixture.format()); WGPUTextureView view2 = create_texture_view(texture2, fixture.format()); const WGPUBufferDescriptor uniform_desc = { .usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, .size = 16, }; WGPUBuffer uniform_buffer = wgpuDeviceCreateBuffer(fixture.device(), &uniform_desc); GpuBuffer uniforms = {uniform_buffer, 16}; // Create initial bind group WGPUBindGroup bind_group = nullptr; pp_update_bind_group(fixture.device(), pipeline, &bind_group, view1, uniforms); 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, view2, uniforms); assert(bind_group != nullptr && "Updated bind group should be created"); fprintf(stdout, " ✓ Bind group updated successfully\n"); // Cleanup wgpuBindGroupRelease(bind_group); wgpuTextureViewRelease(view1); wgpuTextureRelease(texture1); wgpuTextureViewRelease(view2); wgpuTextureRelease(texture2); wgpuBufferRelease(uniform_buffer); wgpuRenderPipelineRelease(pipeline); fprintf(stdout, " ✓ Resources released\n"); } // Test 4: Full post-process setup (pipeline + bind group) static void test_full_setup() { fprintf(stdout, "Testing full post-process setup...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } // 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) WGPUTexture input_texture = create_post_process_texture( fixture.device(), 256, 256, fixture.format()); WGPUTextureView input_view = create_texture_view(input_texture, fixture.format()); // Create output texture (can use OffscreenRenderTarget for this) OffscreenRenderTarget output_target(fixture.instance(), fixture.device(), 256, 256); const WGPUBufferDescriptor uniform_desc = { .usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, .size = 16, }; WGPUBuffer uniform_buffer = wgpuDeviceCreateBuffer(fixture.device(), &uniform_desc); GpuBuffer uniforms = {uniform_buffer, 16}; // Create bind group WGPUBindGroup bind_group = nullptr; pp_update_bind_group(fixture.device(), pipeline, &bind_group, input_view, uniforms); 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(uniform_buffer); wgpuRenderPipelineRelease(pipeline); fprintf(stdout, " ✓ Full setup test completed\n"); } int main() { fprintf(stdout, "=== Post-Process Helper Tests ===\n"); test_pipeline_creation(); test_bind_group_creation(); test_bind_group_update(); test_full_setup(); fprintf(stdout, "=== All Post-Process Helper Tests Passed ===\n"); return 0; } #endif /* !defined(STRIP_ALL) */