From 1e3e3f54a4f80bd05d168c9ae749ed32e1275868 Mon Sep 17 00:00:00 2001 From: skal Date: Fri, 6 Feb 2026 14:08:09 +0100 Subject: fix(shaders): Resolve WGSL validation errors and add shader compilation tests Fixed three critical WGSL shader issues causing demo64k and test_3d_render to crash: 1. **renderer_3d.wgsl**: Removed dead code using non-existent `inverse()` function - WGSL doesn't have `inverse()` for matrices - Dead code was unreachable but still validated by shader compiler - Also removed reference to undefined `in.normal` vertex input 2. **sdf_utils.wgsl & lighting.wgsl**: Fixed `get_normal_basic()` signature mismatch - Changed parameter from `obj_type: f32` to `obj_params: vec4` - Now correctly matches `get_dist()` function signature 3. **scene_query_linear.wgsl**: Fixed incorrect BVH binding declaration - Linear mode was incorrectly declaring binding 2 (BVH buffer) - Replaced BVH traversal with simple linear object loop - Root cause: Both BVH and Linear shaders were identical (copy-paste error) Added comprehensive shader compilation test (test_shader_compilation.cc): - Tests all production shaders compile successfully through WebGPU - Validates both BVH and Linear composition modes - Catches WGSL syntax errors, binding mismatches, and type errors - Would have caught all three bugs fixed in this commit Why tests didn't catch this: - Existing test_shader_assets only checked for keywords, not compilation - No test actually created WebGPU shader modules from composed code - New test fills this gap with real GPU validation Results: - demo64k runs without WebGPU errors - test_3d_render no longer crashes - All 22/23 tests pass (FftTest unrelated issue from FFT Phase 1) Co-Authored-By: Claude Sonnet 4.5 --- src/tests/test_shader_compilation.cc | 232 +++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/tests/test_shader_compilation.cc (limited to 'src/tests/test_shader_compilation.cc') diff --git a/src/tests/test_shader_compilation.cc b/src/tests/test_shader_compilation.cc new file mode 100644 index 0000000..8b3b5f5 --- /dev/null +++ b/src/tests/test_shader_compilation.cc @@ -0,0 +1,232 @@ +// This file is part of the 64k demo project. +// It validates that all production shaders compile successfully with WebGPU. +// This catches issues like: +// - Invalid WGSL syntax (e.g., undefined functions like inverse()) +// - Missing binding declarations +// - Type mismatches + +#include "generated/assets.h" +#include "gpu/effects/shader_composer.h" +#include "gpu/effects/shaders.h" +#include "platform.h" +#include +#include +#include +#include + +static WGPUDevice g_device = nullptr; + +// Initialize minimal WebGPU for shader compilation testing +static bool init_wgpu() { + WGPUInstance instance = wgpuCreateInstance(nullptr); + if (!instance) { + fprintf(stderr, "Failed to create WGPU instance.\n"); + return false; + } + + WGPURequestAdapterOptions adapter_opts = {}; + adapter_opts.powerPreference = WGPUPowerPreference_HighPerformance; + + WGPUAdapter adapter = nullptr; + +#if defined(DEMO_CROSS_COMPILE_WIN32) + auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter a, + const char* message, void* userdata) { + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = a; + } + }; + wgpuInstanceRequestAdapter(instance, &adapter_opts, on_adapter, &adapter); +#else + auto on_adapter = [](WGPURequestAdapterStatus status, WGPUAdapter a, + WGPUStringView message, void* userdata, void* user2) { + (void)user2; + (void)message; + if (status == WGPURequestAdapterStatus_Success) { + *(WGPUAdapter*)userdata = a; + } + }; + WGPURequestAdapterCallbackInfo adapter_cb = {}; + adapter_cb.mode = WGPUCallbackMode_WaitAnyOnly; + adapter_cb.callback = on_adapter; + adapter_cb.userdata1 = &adapter; + wgpuInstanceRequestAdapter(instance, &adapter_opts, adapter_cb); +#endif + + // Try to wait for adapter (may not work on all platforms) + for (int i = 0; i < 100 && !adapter; ++i) { + wgpuInstanceProcessEvents(instance); + } + + if (!adapter) { + fprintf(stderr, + "Warning: Could not get WGPU adapter (GPU compilation tests " + "skipped)\n"); + return false; + } + + WGPUDeviceDescriptor device_desc = {}; + +#if defined(DEMO_CROSS_COMPILE_WIN32) + auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice d, + const char* message, void* userdata) { + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = d; + } + }; + wgpuAdapterRequestDevice(adapter, &device_desc, on_device, &g_device); +#else + auto on_device = [](WGPURequestDeviceStatus status, WGPUDevice d, + WGPUStringView message, void* userdata, void* user2) { + (void)user2; + (void)message; + if (status == WGPURequestDeviceStatus_Success) { + *(WGPUDevice*)userdata = d; + } + }; + WGPURequestDeviceCallbackInfo device_cb = {}; + device_cb.mode = WGPUCallbackMode_WaitAnyOnly; + device_cb.callback = on_device; + device_cb.userdata1 = &g_device; + wgpuAdapterRequestDevice(adapter, &device_desc, device_cb); +#endif + + // Try to wait for device (may not work on all platforms) + for (int i = 0; i < 100 && !g_device; ++i) { + wgpuInstanceProcessEvents(instance); + } + + if (!g_device) { + fprintf(stderr, + "Warning: Could not get WGPU device (GPU compilation tests " + "skipped)\n"); + return false; + } + + return true; +} + +// Test shader compilation +static bool test_shader_compilation(const char* name, + const char* shader_code) { + printf("Testing compilation: %s...\n", name); + + if (!g_device) { + printf("SKIPPED: %s (no GPU device)\n", name); + return true; // Not a failure, just skipped + } + +#if defined(DEMO_CROSS_COMPILE_WIN32) + WGPUShaderModuleWGSLDescriptor wgsl_desc = {}; + wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; + wgsl_desc.code = shader_code; + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; +#else + WGPUShaderSourceWGSL wgsl_desc = {}; + wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_desc.code = str_view(shader_code); + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; +#endif + + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(g_device, &shader_desc); + + if (!shader_module) { + printf("FAILED: %s - shader compilation failed!\n", name); + return false; + } + + wgpuShaderModuleRelease(shader_module); + printf("PASSED: %s\n", name); + return true; +} + +// Test composed shader with different modes +static bool test_composed_shader(const char* base_name, AssetId asset_id, + bool with_bvh) { + const char* mode_name = with_bvh ? "BVH" : "Linear"; + char test_name[128]; + snprintf(test_name, sizeof(test_name), "%s (%s mode)", base_name, mode_name); + + const char* shader_asset = (const char*)GetAsset(asset_id); + std::string main_code = shader_asset; + + ShaderComposer::CompositionMap composition_map; + if (with_bvh) { + composition_map["render/scene_query_mode"] = "render/scene_query_bvh"; + } else { + composition_map["render/scene_query_mode"] = "render/scene_query_linear"; + } + + std::string composed_shader = + ShaderComposer::Get().Compose({}, main_code, composition_map); + + return test_shader_compilation(test_name, composed_shader.c_str()); +} + +int main() { + printf("===========================================\n"); + printf("Shader Compilation Test Suite\n"); + printf("===========================================\n\n"); + + bool gpu_available = init_wgpu(); + if (!gpu_available) { + printf( + "Note: GPU not available - running composition-only tests\n\n"); + } + + // Initialize shader composer + InitShaderComposer(); + + bool all_passed = true; + + // Test 1: Simple shaders that don't need composition + printf("\n--- Test 1: Simple Shaders ---\n"); + all_passed &= test_shader_compilation( + "Passthrough", (const char*)GetAsset(AssetId::ASSET_SHADER_PASSTHROUGH)); + all_passed &= test_shader_compilation( + "Ellipse", (const char*)GetAsset(AssetId::ASSET_SHADER_ELLIPSE)); + all_passed &= test_shader_compilation( + "Gaussian Blur", + (const char*)GetAsset(AssetId::ASSET_SHADER_GAUSSIAN_BLUR)); + all_passed &= test_shader_compilation( + "Solarize", (const char*)GetAsset(AssetId::ASSET_SHADER_SOLARIZE)); + + // Test 2: Composed shaders (both BVH and Linear modes) + printf("\n--- Test 2: Composed Shaders (BVH Mode) ---\n"); + all_passed &= test_composed_shader("Renderer 3D", + AssetId::ASSET_SHADER_RENDERER_3D, true); + all_passed &= + test_composed_shader("Mesh Render", AssetId::ASSET_SHADER_MESH, true); + + printf("\n--- Test 3: Composed Shaders (Linear Mode) ---\n"); + all_passed &= test_composed_shader("Renderer 3D", + AssetId::ASSET_SHADER_RENDERER_3D, false); + all_passed &= + test_composed_shader("Mesh Render", AssetId::ASSET_SHADER_MESH, false); + + // Test 3: Compute shaders + printf("\n--- Test 4: Compute Shaders ---\n"); + all_passed &= test_shader_compilation( + "Particle Compute", + (const char*)GetAsset(AssetId::ASSET_SHADER_PARTICLE_COMPUTE)); + all_passed &= test_shader_compilation( + "Particle Spray Compute", + (const char*)GetAsset(AssetId::ASSET_SHADER_PARTICLE_SPRAY_COMPUTE)); + + printf("\n===========================================\n"); + if (all_passed) { + printf("All shader compilation tests PASSED ✓\n"); + } else { + printf("Some shader compilation tests FAILED ✗\n"); + } + printf("===========================================\n"); + + if (g_device) { + wgpuDeviceRelease(g_device); + } + + return all_passed ? 0 : 1; +} -- cgit v1.2.3