summaryrefslogtreecommitdiff
path: root/src/tests/test_effect_base.cc
blob: 8180535007c044a613bcc9433029a1cc584b56c9 (plain)
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
// 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 <cassert>
#include <cstdio>
#include <memory>

// 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<uint8_t> 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<FlashEffect>(
      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<FlashEffect>(
      fixture.ctx());

  assert(!effect->is_initialized && "Effect should not be initialized yet");

  // Add effect to sequence
  auto seq = std::make_shared<Sequence>();
  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<Sequence>();

  // Create effect
  auto effect = std::make_shared<FlashEffect>(
      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<Sequence>();
  auto effect = std::make_shared<FlashEffect>(
      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<SequenceItem*> 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<SequenceItem*> 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<SequenceItem*> 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<SequenceItem*> 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<uint8_t> black_frame(256 * 256 * 4, 0);
  assert(!has_rendered_content(black_frame, 256, 256) &&
         "Black frame should have no content");

  std::vector<uint8_t> 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<uint8_t> 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;
}