// 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. #include "effect_test_helpers.h" #include "offscreen_render_target.h" #include "webgpu_test_fixture.h" #include "gpu/demo_effects.h" #include "gpu/effect.h" #include #include #include // Test 1: WebGPU fixture initialization static void test_webgpu_fixture() { fprintf(stdout, "Testing WebGPU fixture...\n"); WebGPUTestFixture fixture; const bool init_success = fixture.init(); if (!init_success) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } assert(fixture.is_initialized() && "Fixture should be initialized"); assert(fixture.device() != nullptr && "Device should be valid"); assert(fixture.queue() != nullptr && "Queue should be valid"); fprintf(stdout, " ✓ WebGPU fixture initialized successfully\n"); fixture.shutdown(); assert(!fixture.is_initialized() && "Fixture should be shutdown"); fprintf(stdout, " ✓ WebGPU fixture shutdown successfully\n"); } // Test 2: Offscreen render target creation static void test_offscreen_render_target() { fprintf(stdout, "Testing offscreen render target...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } OffscreenRenderTarget target(fixture.instance(), fixture.device(), 256, 256); assert(target.texture() != nullptr && "Texture should be valid"); assert(target.view() != nullptr && "Texture view should be valid"); assert(target.width() == 256 && "Width should be 256"); assert(target.height() == 256 && "Height should be 256"); fprintf(stdout, " ✓ Offscreen render target created (256x256)\n"); // Test pixel readback (should initially be all zeros or uninitialized) const std::vector pixels = target.read_pixels(); assert(pixels.size() == 256 * 256 * 4 && "Pixel buffer size should match"); fprintf(stdout, " ✓ Pixel readback succeeded (%zu bytes)\n", pixels.size()); } // Test 3: Effect construction static void test_effect_construction() { fprintf(stdout, "Testing effect construction...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } // Create FlashEffect (simple post-process effect) auto effect = std::make_shared( fixture.ctx()); assert(!effect->is_initialized && "Effect should not be initialized yet"); fprintf(stdout, " ✓ FlashEffect constructed (not initialized)\n"); } // Test 4: Effect initialization via Sequence static void test_effect_initialization() { fprintf(stdout, "Testing effect initialization...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } // Create MainSequence (use init_test for test environment) MainSequence main_seq; main_seq.init_test(fixture.ctx()); // Create FlashEffect auto effect = std::make_shared( fixture.ctx()); assert(!effect->is_initialized && "Effect should not be initialized yet"); // Add effect to sequence auto seq = std::make_shared(); seq->add_effect(effect, 0.0f, 10.0f, 0); // 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"); } // Test 5: Sequence add_effect static void test_sequence_add_effect() { fprintf(stdout, "Testing Sequence::add_effect...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); return; } MainSequence main_seq; main_seq.init_test(fixture.ctx()); // Create sequence auto seq = std::make_shared(); // Create effect auto effect = std::make_shared( fixture.ctx()); assert(!effect->is_initialized && "Effect should not be initialized before Sequence::init()"); // Add effect to sequence (time range: 0.0 - 10.0, priority 0) seq->add_effect(effect, 0.0f, 10.0f, 0); // Initialize sequence (this should initialize the effect) seq->init(&main_seq); assert(effect->is_initialized && "Effect should be initialized after Sequence::init()"); fprintf(stdout, " ✓ Effect added to sequence and initialized (time=0.0-10.0, priority=0)\n"); } // Test 6: Sequence activation logic static void test_sequence_activation() { fprintf(stdout, "Testing sequence activation logic...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n"); 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"); // 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"); fprintf(stdout, " ✓ Effect active at start time (t=5.0)\n"); // 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"); fprintf(stdout, " ✓ Effect active during period (t=7.5)\n"); // 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"); fprintf(stdout, " ✓ Effect not active after end time (t=11.0)\n"); } // Test 7: Pixel validation helpers static void test_pixel_helpers() { fprintf(stdout, "Testing pixel validation helpers...\n"); // Test has_rendered_content (should detect non-black pixels) std::vector black_frame(256 * 256 * 4, 0); assert(!has_rendered_content(black_frame, 256, 256) && "Black frame should have no content"); std::vector colored_frame(256 * 256 * 4, 0); colored_frame[0] = 255; // Set one red pixel assert(has_rendered_content(colored_frame, 256, 256) && "Colored frame should have content"); fprintf(stdout, " ✓ has_rendered_content() works correctly\n"); // Test all_pixels_match_color std::vector red_frame(256 * 256 * 4, 0); for (size_t i = 0; i < 256 * 256; ++i) { red_frame[i * 4 + 2] = 255; // BGRA: Red in position 2 } assert(all_pixels_match_color(red_frame, 256, 256, 255, 0, 0, 5) && "Red frame should match red color"); fprintf(stdout, " ✓ all_pixels_match_color() works correctly\n"); // Test hash_pixels const uint64_t hash1 = hash_pixels(black_frame); const uint64_t hash2 = hash_pixels(colored_frame); assert(hash1 != hash2 && "Different frames should have different hashes"); fprintf(stdout, " ✓ hash_pixels() produces unique hashes\n"); } int main() { fprintf(stdout, "=== Effect Base Tests ===\n"); test_webgpu_fixture(); test_offscreen_render_target(); test_effect_construction(); test_effect_initialization(); test_sequence_add_effect(); test_sequence_activation(); test_pixel_helpers(); fprintf(stdout, "=== All Effect Base Tests Passed ===\n"); return 0; }