From f03f3428991499e0701cce5eacc2bb943fde9d8e Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 16 Feb 2026 12:42:57 +0100 Subject: fix(tests): port tests to v2 API, fix FATAL_CHECK logic - Port test_effect_base to EffectV2/SequenceV2 - Port test_demo_effects to v2 effects only - Remove v1 lifecycle helpers from effect_test_helpers - Fix cnn_test to not depend on cnn_v1_effect.h - Fix test_sequence_v2_e2e node redeclaration Known issue: test_sequence_v2_e2e still fails with bind group error (needs source/sink texture views set) Co-Authored-By: Claude Sonnet 4.5 --- src/tests/common/effect_test_helpers.cc | 43 +------ src/tests/common/effect_test_helpers.h | 18 +-- src/tests/gpu/test_demo_effects.cc | 216 +++++++------------------------- src/tests/gpu/test_effect_base.cc | 171 ++++++++++++------------- src/tests/gpu/test_sequence_v2_e2e.cc | 4 +- 5 files changed, 127 insertions(+), 325 deletions(-) (limited to 'src') diff --git a/src/tests/common/effect_test_helpers.cc b/src/tests/common/effect_test_helpers.cc index 9250366..d776609 100644 --- a/src/tests/common/effect_test_helpers.cc +++ b/src/tests/common/effect_test_helpers.cc @@ -1,9 +1,8 @@ // This file is part of the 64k demo project. // It implements reusable test helpers for GPU effect testing. -// Provides pixel validation and lifecycle testing utilities. +// Provides pixel validation utilities. #include "effect_test_helpers.h" -#include "gpu/effect.h" #include // ============================================================================ @@ -68,43 +67,3 @@ uint64_t hash_pixels(const std::vector& pixels) { return hash; } -// ============================================================================ -// Effect Lifecycle Helpers -// ============================================================================ - -bool test_effect_lifecycle(Effect* effect, MainSequence* main_seq) { - assert(effect && "Effect pointer is null"); - assert(main_seq && "MainSequence pointer is null"); - - // Check initial state - if (effect->is_initialized) { - return false; // Should not be initialized yet - } - - // Initialize effect - effect->init(main_seq); - - // Check initialized state - if (!effect->is_initialized) { - return false; // Should be initialized now - } - - return true; // Lifecycle test passed -} - -bool test_effect_render_smoke(Effect* effect) { - assert(effect && "Effect pointer is null"); - - // Smoke test: Just call render with dummy parameters - // If this doesn't crash, consider it a success - // Note: This requires the effect to be initialized first - if (!effect->is_initialized) { - return false; // Cannot render uninitialized effect - } - - // We cannot actually render without a full render pass setup - // This is a placeholder for more sophisticated render testing - // Real render tests should use OffscreenRenderTarget - - return true; // Smoke test passed (no crash) -} diff --git a/src/tests/common/effect_test_helpers.h b/src/tests/common/effect_test_helpers.h index 33355ee..962d1b0 100644 --- a/src/tests/common/effect_test_helpers.h +++ b/src/tests/common/effect_test_helpers.h @@ -1,6 +1,6 @@ // This file is part of the 64k demo project. // It provides reusable test helpers for GPU effect testing. -// Includes lifecycle helpers, pixel validation, and smoke tests. +// Includes pixel validation utilities. #pragma once @@ -8,10 +8,6 @@ #include #include -// Forward declarations -class Effect; -class MainSequence; - // ============================================================================ // Pixel Validation Helpers // ============================================================================ @@ -33,15 +29,3 @@ bool all_pixels_match_color(const std::vector& pixels, int width, // Compute simple hash of pixel data (for deterministic output checks) uint64_t hash_pixels(const std::vector& pixels); - -// ============================================================================ -// Effect Lifecycle Helpers -// ============================================================================ - -// Test that an effect can be constructed and initialized -// Returns true if lifecycle succeeds, false otherwise -bool test_effect_lifecycle(Effect* effect, MainSequence* main_seq); - -// Test that an effect can render without crashing (smoke test) -// Does not validate output, only checks for crashes -bool test_effect_render_smoke(Effect* effect); diff --git a/src/tests/gpu/test_demo_effects.cc b/src/tests/gpu/test_demo_effects.cc index 2a4bdf4..f5cea85 100644 --- a/src/tests/gpu/test_demo_effects.cc +++ b/src/tests/gpu/test_demo_effects.cc @@ -1,71 +1,34 @@ // This file is part of the 64k demo project. -// It tests all demo effect classes for basic construction and initialization. -// Validates that every effect can be instantiated and initialized without -// crashes. +// It tests all v2 demo effect classes for basic construction. +// Validates that every v2 effect can be instantiated without crashes. // -// MAINTENANCE REQUIREMENT: When adding a new effect to demo_effects.h: -// 1. Add it to the appropriate test list (post_process_effects or -// scene_effects) +// MAINTENANCE REQUIREMENT: When adding a new v2 effect to demo_effects.h: +// 1. Add it to the test list below // 2. Run test to verify: ./build/test_demo_effects -// 3. If the effect requires Renderer3D, add it to requires_3d check in -// test_scene_effects() -#include "../common/effect_test_helpers.h" #include "../common/webgpu_test_fixture.h" #include "gpu/demo_effects.h" -// TODO: Re-enable CNN effects once ported to v2 -// #include "../../../cnn_v1/src/cnn_v1_effect.h" #include #include -#include #include #include -// Helper: Test effect construction and initialization -// Returns: 0=failed, 1=passed, 2=skipped (requires full 3D setup) -static int test_effect_smoke(const char* name, std::shared_ptr effect, - MainSequence* main_seq, bool requires_3d = false) { +// Helper: Test v2 effect construction +static int test_effect_v2(const char* name, std::shared_ptr effect) { fprintf(stdout, " Testing %s...\n", name); - // Check construction if (!effect) { fprintf(stderr, " ✗ Construction failed\n"); return 0; } - // Should not be initialized yet - if (effect->is_initialized) { - fprintf(stderr, - " ✗ Should not be initialized before Sequence::init()\n"); - return 0; - } - - // Add to sequence and initialize - auto seq = std::make_shared(); - seq->add_effect(effect, 0.0f, 10.0f, 0); - - // Some effects require full 3D pipeline setup (Renderer3D with shaders) - // These will fail in init_test() environment - skip them gracefully - if (requires_3d) { - fprintf(stdout, " ⚠ Skipped (requires full 3D pipeline setup)\n"); - return 2; // Skipped - } - - seq->init(main_seq); - - // Should be initialized now - if (!effect->is_initialized) { - fprintf(stderr, " ✗ Should be initialized after Sequence::init()\n"); - return 0; - } - - fprintf(stdout, " ✓ %s construction and initialization OK\n", name); - return 1; // Passed + fprintf(stdout, " ✓ %s OK\n", name); + return 1; } -// Test 1: Post-process effects -static void test_post_process_effects() { - fprintf(stdout, "Testing post-process effects...\n"); +// Test all available v2 effects +static void test_v2_effects() { + fprintf(stdout, "Testing V2 effects...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { @@ -73,130 +36,43 @@ static void test_post_process_effects() { return; } - MainSequence main_seq; - main_seq.init_test(fixture.ctx()); - - // Test each post-process effect - std::vector>> effects = { - {"FlashEffect", std::make_shared(fixture.ctx())}, - {"PassthroughEffect", std::make_shared(fixture.ctx())}, - {"GaussianBlurEffect", - std::make_shared(fixture.ctx())}, - {"ChromaAberrationEffect", - std::make_shared(fixture.ctx())}, - {"SolarizeEffect", std::make_shared(fixture.ctx())}, - {"FadeEffect", std::make_shared(fixture.ctx())}, - {"ThemeModulationEffect", - std::make_shared(fixture.ctx())}, - {"VignetteEffect", std::make_shared(fixture.ctx())}, - {"CNNv1Effect", std::make_shared(fixture.ctx())}, - {"CNNv2Effect", std::make_shared(fixture.ctx())}, + std::vector>> effects = { + {"PassthroughEffectV2", + std::make_shared( + fixture.ctx(), std::vector{"source"}, + std::vector{"sink"})}, + {"GaussianBlurEffectV2", + std::make_shared( + fixture.ctx(), std::vector{"source"}, + std::vector{"sink"})}, + {"PlaceholderEffectV2", + std::make_shared( + fixture.ctx(), std::vector{}, + std::vector{"sink"})}, + {"HeptagonEffectV2", + std::make_shared( + fixture.ctx(), std::vector{}, + std::vector{"sink"})}, + {"ParticlesEffectV2", + std::make_shared( + fixture.ctx(), std::vector{}, + std::vector{"sink"})}, + {"RotatingCubeEffectV2", + std::make_shared( + fixture.ctx(), std::vector{}, + std::vector{"sink"})}, + {"Hybrid3DEffectV2", + std::make_shared( + fixture.ctx(), std::vector{"source"}, + std::vector{"sink"})}, }; int passed = 0; for (const auto& [name, effect] : effects) { - // Verify it's marked as post-process - assert(effect->is_post_process() && - "Post-process effect should return true for is_post_process()"); - - const int result = test_effect_smoke(name, effect, &main_seq, false); - if (result == 1) { - ++passed; - } + passed += test_effect_v2(name, effect); } - fprintf(stdout, " ✓ %d/%zu post-process effects tested\n", passed, - effects.size()); -} - -// Test 2: Scene effects -static void test_scene_effects() { - fprintf(stdout, "Testing scene effects...\n"); - - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); - return; - } - - MainSequence main_seq; - main_seq.init_test(fixture.ctx()); - - // Test each scene effect - std::vector>> effects = { - {"HeptagonEffect", std::make_shared(fixture.ctx())}, - {"ParticlesEffect", std::make_shared(fixture.ctx())}, - {"ParticleSprayEffect", - std::make_shared(fixture.ctx())}, - {"MovingEllipseEffect", - std::make_shared(fixture.ctx())}, - {"FlashCubeEffect", std::make_shared(fixture.ctx())}, - {"Hybrid3DEffect", std::make_shared(fixture.ctx())}, - {"CircleMaskEffect", std::make_shared(fixture.ctx())}, - {"RotatingCubeEffect", - std::make_shared(fixture.ctx())}, - {"Scene1Effect", std::make_shared(fixture.ctx())}, - {"SDFTestEffect", std::make_shared(fixture.ctx())}, - }; - - int passed = 0; - int skipped = 0; - for (const auto& [name, effect] : effects) { - // Scene effects should NOT be marked as post-process - assert(!effect->is_post_process() && - "Scene effect should return false for is_post_process()"); - - // FlashCubeEffect, Hybrid3DEffect, RotatingCubeEffect, and CircleMaskEffect - // require full 3D pipeline (Renderer3D) or auxiliary textures - const bool requires_3d = (strcmp(name, "FlashCubeEffect") == 0 || - strcmp(name, "Hybrid3DEffect") == 0 || - strcmp(name, "RotatingCubeEffect") == 0 || - strcmp(name, "CircleMaskEffect") == 0); - - const int result = test_effect_smoke(name, effect, &main_seq, requires_3d); - if (result == 1) { - ++passed; - } else if (result == 2) { - ++skipped; - } - } - - fprintf(stdout, " ✓ %d/%zu scene effects tested (%d skipped)\n", passed, - effects.size(), skipped); -} - -// Test 3: Effect type classification -static void test_effect_type_classification() { - fprintf(stdout, "Testing effect type classification...\n"); - - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); - return; - } - - // Post-process effects should return true - auto flash = std::make_shared(fixture.ctx()); - assert(flash->is_post_process() && "FlashEffect should be post-process"); - - auto blur = std::make_shared(fixture.ctx()); - assert(blur->is_post_process() && - "GaussianBlurEffect should be post-process"); - - auto vignette = std::make_shared(fixture.ctx()); - assert(vignette->is_post_process() && - "VignetteEffect should be post-process"); - - // Scene effects should return false - auto heptagon = std::make_shared(fixture.ctx()); - assert(!heptagon->is_post_process() && - "HeptagonEffect should NOT be post-process"); - - auto particles = std::make_shared(fixture.ctx()); - assert(!particles->is_post_process() && - "ParticlesEffect should NOT be post-process"); - - fprintf(stdout, " ✓ Effect type classification correct\n"); + fprintf(stdout, " ✓ %d/%zu V2 effects tested\n", passed, effects.size()); } int main() { @@ -205,10 +81,8 @@ int main() { extern void InitShaderComposer(); InitShaderComposer(); - test_post_process_effects(); - test_scene_effects(); - test_effect_type_classification(); + test_v2_effects(); - fprintf(stdout, "=== All Demo Effects Tests Passed ===\n"); + fprintf(stdout, "=== All Tests Passed ===\n"); return 0; -} \ No newline at end of file +} diff --git a/src/tests/gpu/test_effect_base.cc b/src/tests/gpu/test_effect_base.cc index f049dff..cd6e2db 100644 --- a/src/tests/gpu/test_effect_base.cc +++ b/src/tests/gpu/test_effect_base.cc @@ -1,12 +1,13 @@ // This file is part of the 64k demo project. -// It tests the Effect/Sequence/MainSequence lifecycle using headless rendering. -// Verifies effect initialization, activation, and basic rendering. +// It tests the EffectV2/SequenceV2 lifecycle using headless rendering. +// Verifies effect initialization and basic rendering. #include "../common/effect_test_helpers.h" #include "../common/offscreen_render_target.h" #include "../common/webgpu_test_fixture.h" -#include "gpu/demo_effects.h" -#include "gpu/effect.h" +#include "effects/passthrough_effect_v2.h" +#include "gpu/effect_v2.h" +#include "gpu/sequence_v2.h" #include #include #include @@ -79,17 +80,19 @@ static void test_effect_construction() { return; } - // Create FlashEffect (simple post-process effect) - auto effect = std::make_shared(fixture.ctx()); + // Create PassthroughEffectV2 (simple effect) + auto effect = std::make_shared( + fixture.ctx(), std::vector{"source"}, + std::vector{"sink"}); - assert(!effect->is_initialized && "Effect should not be initialized yet"); + assert(effect != nullptr && "Effect should be constructed"); - fprintf(stdout, " ✓ FlashEffect constructed (not initialized)\n"); + fprintf(stdout, " ✓ PassthroughEffectV2 constructed\n"); } -// Test 4: Effect initialization via Sequence -static void test_effect_initialization() { - fprintf(stdout, "Testing effect initialization...\n"); +// Test 4: Effect added to sequence DAG +static void test_effect_in_sequence() { + fprintf(stdout, "Testing effect in SequenceV2 DAG...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { @@ -97,31 +100,30 @@ static void test_effect_initialization() { return; } - // Create MainSequence (use init_test for test environment) - MainSequence main_seq; - main_seq.init_test(fixture.ctx()); + // Create minimal sequence with one effect + class TestSequence : public SequenceV2 { + public: + TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { + auto effect = std::make_shared( + ctx, std::vector{"source"}, + std::vector{"sink"}); - // Create FlashEffect - auto effect = std::make_shared(fixture.ctx()); + effect_dag_.push_back({effect, {"source"}, {"sink"}, 0}); + init_effect_nodes(); + } + }; - assert(!effect->is_initialized && "Effect should not be initialized yet"); + auto seq = std::make_unique(fixture.ctx(), 256, 256); - // Add effect to sequence - auto seq = std::make_shared(); - seq->add_effect(effect, 0.0f, 10.0f, 0); + assert(seq->get_effect_dag().size() == 1 && "Should have one effect"); + assert(seq->get_effect_dag()[0].effect != nullptr && "Effect should exist"); - // Initialize sequence (this sets effect->is_initialized) - seq->init(&main_seq); - - assert(effect->is_initialized && - "Effect should be initialized after Sequence::init()"); - - fprintf(stdout, " ✓ FlashEffect initialized via Sequence::init()\n"); + fprintf(stdout, " ✓ Effect added to DAG and initialized\n"); } -// Test 5: Sequence add_effect -static void test_sequence_add_effect() { - fprintf(stdout, "Testing Sequence::add_effect...\n"); +// Test 5: Sequence rendering (smoke test) +static void test_sequence_render() { + fprintf(stdout, "Testing sequence render...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { @@ -129,35 +131,40 @@ static void test_sequence_add_effect() { return; } - MainSequence main_seq; - main_seq.init_test(fixture.ctx()); + OffscreenRenderTarget target(fixture.instance(), fixture.device(), 256, 256); - // Create sequence - auto seq = std::make_shared(); + class TestSequence : public SequenceV2 { + public: + TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { + auto effect = std::make_shared( + ctx, std::vector{"source"}, + std::vector{"sink"}); - // Create effect - auto effect = std::make_shared(fixture.ctx()); + effect_dag_.push_back({effect, {"source"}, {"sink"}, 0}); + init_effect_nodes(); + } + }; - assert(!effect->is_initialized && - "Effect should not be initialized before Sequence::init()"); + auto seq = std::make_unique(fixture.ctx(), 256, 256); + seq->set_sink_view(target.view()); + seq->set_source_view(target.view()); - // Add effect to sequence (time range: 0.0 - 10.0, priority 0) - seq->add_effect(effect, 0.0f, 10.0f, 0); + // Create encoder and attempt render + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder( + fixture.device(), nullptr); - // Initialize sequence (this should initialize the effect) - seq->init(&main_seq); + seq->render_effects(encoder); - assert(effect->is_initialized && - "Effect should be initialized after Sequence::init()"); + WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuQueueSubmit(fixture.queue(), 1, &commands); + wgpuCommandBufferRelease(commands); - fprintf(stdout, - " ✓ Effect added to sequence and initialized (time=0.0-10.0, " - "priority=0)\n"); + fprintf(stdout, " ✓ Sequence rendered without error\n"); } -// Test 6: Sequence activation logic -static void test_sequence_activation() { - fprintf(stdout, "Testing sequence activation logic...\n"); +// Test 6: Sequence time-based parameters +static void test_sequence_time_params() { + fprintf(stdout, "Testing sequence time parameters...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { @@ -165,51 +172,31 @@ static void test_sequence_activation() { return; } - MainSequence main_seq; - main_seq.init_test(fixture.ctx()); - - auto seq = std::make_shared(); - auto effect = std::make_shared(fixture.ctx()); - - // Effect active from 5.0 to 10.0 seconds - seq->add_effect(effect, 5.0f, 10.0f, 0); - seq->init(&main_seq); - - // Before start time: should not be active - seq->update_active_list(-1.0f); - std::vector scene_before, post_before; - seq->collect_active_effects(scene_before, post_before); - assert(scene_before.empty() && post_before.empty() && - "Effect should not be active before start time"); - - fprintf(stdout, " ✓ Effect not active before start time (t=-1.0)\n"); + class TestSequence : public SequenceV2 { + public: + TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { + init_effect_nodes(); + } - // At start time: should be active - seq->update_active_list(5.0f); - std::vector scene_at_start, post_at_start; - seq->collect_active_effects(scene_at_start, post_at_start); - const size_t active_at_start = scene_at_start.size() + post_at_start.size(); - assert(active_at_start == 1 && "Effect should be active at start time"); + void preprocess(float seq_time, float beat_time, float beat_phase, + float audio_intensity) override { + SequenceV2::preprocess(seq_time, beat_time, beat_phase, audio_intensity); + last_time = seq_time; + } - fprintf(stdout, " ✓ Effect active at start time (t=5.0)\n"); + float last_time = -1.0f; + }; - // During active period: should remain active - seq->update_active_list(7.5f); - std::vector scene_during, post_during; - seq->collect_active_effects(scene_during, post_during); - const size_t active_during = scene_during.size() + post_during.size(); - assert(active_during == 1 && "Effect should be active during period"); + auto seq = std::make_unique(fixture.ctx(), 256, 256); - fprintf(stdout, " ✓ Effect active during period (t=7.5)\n"); + // Test different time values + seq->preprocess(0.0f, 0.0f, 0.0f, 0.0f); + assert(seq->last_time == 0.0f && "Time at t=0"); - // After end time: should not be active - seq->update_active_list(11.0f); - std::vector scene_after, post_after; - seq->collect_active_effects(scene_after, post_after); - assert(scene_after.empty() && post_after.empty() && - "Effect should not be active after end time"); + seq->preprocess(5.5f, 10.0f, 0.5f, 0.8f); + assert(seq->last_time == 5.5f && "Time at t=5.5"); - fprintf(stdout, " ✓ Effect not active after end time (t=11.0)\n"); + fprintf(stdout, " ✓ Sequence time parameters updated correctly\n"); } // Test 7: Pixel validation helpers @@ -255,9 +242,9 @@ int main() { test_webgpu_fixture(); test_offscreen_render_target(); test_effect_construction(); - test_effect_initialization(); - test_sequence_add_effect(); - test_sequence_activation(); + test_effect_in_sequence(); + test_sequence_render(); + test_sequence_time_params(); test_pixel_helpers(); fprintf(stdout, "=== All Effect Base Tests Passed ===\n"); diff --git a/src/tests/gpu/test_sequence_v2_e2e.cc b/src/tests/gpu/test_sequence_v2_e2e.cc index 0c7c619..c015e0b 100644 --- a/src/tests/gpu/test_sequence_v2_e2e.cc +++ b/src/tests/gpu/test_sequence_v2_e2e.cc @@ -17,10 +17,8 @@ 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_); + // Node declarations (source/sink already created by NodeRegistry) 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({ -- cgit v1.2.3