summaryrefslogtreecommitdiff
path: root/src/tests/test_demo_effects.cc
blob: 3292c9cf5791e486bbdfdc029a08bd69ce7c56db (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
// 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.
//
// 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)
// 2. Update EXPECTED_POST_PROCESS_COUNT or EXPECTED_SCENE_COUNT below
// 3. Run test to verify: ./build/test_demo_effects
// 4. If the effect requires Renderer3D, add it to requires_3d check in test_scene_effects()

#if !defined(STRIP_ALL)  // Test code only - zero size impact on final binary

// Expected effect counts - UPDATE THESE when adding new effects!
static constexpr int EXPECTED_POST_PROCESS_COUNT = 8;  // FlashEffect, PassthroughEffect, GaussianBlurEffect, ChromaAberrationEffect, DistortEffect, SolarizeEffect, FadeEffect, ThemeModulationEffect
static constexpr int EXPECTED_SCENE_COUNT = 6;  // HeptagonEffect, ParticlesEffect, ParticleSprayEffect, MovingEllipseEffect, FlashCubeEffect, Hybrid3DEffect

#include "effect_test_helpers.h"
#include "webgpu_test_fixture.h"
#include "gpu/demo_effects.h"
#include "gpu/effect.h"
#include <cassert>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>

// 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> effect,
                              MainSequence* main_seq,
                              bool requires_3d = false) {
  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<Sequence>();
  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
}

// Test 1: Post-process effects
static void test_post_process_effects() {
  fprintf(stdout, "Testing post-process effects...\n");

  WebGPUTestFixture fixture;
  if (!fixture.init()) {
    fprintf(stdout, "  ⚠ WebGPU unavailable - skipping test\n");
    return;
  }

  MainSequence main_seq;
  main_seq.init_test(fixture.device(), fixture.queue(), fixture.format());

  // Test each post-process effect
  std::vector<std::pair<const char*, std::shared_ptr<Effect>>> effects = {
      {"FlashEffect",
       std::make_shared<FlashEffect>(fixture.device(), fixture.queue(),
                                      fixture.format())},
      {"PassthroughEffect",
       std::make_shared<PassthroughEffect>(fixture.device(), fixture.queue(),
                                            fixture.format())},
      {"GaussianBlurEffect",
       std::make_shared<GaussianBlurEffect>(fixture.device(), fixture.queue(),
                                             fixture.format())},
      {"ChromaAberrationEffect",
       std::make_shared<ChromaAberrationEffect>(
           fixture.device(), fixture.queue(), fixture.format())},
      {"DistortEffect",
       std::make_shared<DistortEffect>(fixture.device(), fixture.queue(),
                                        fixture.format())},
      {"SolarizeEffect",
       std::make_shared<SolarizeEffect>(fixture.device(), fixture.queue(),
                                         fixture.format())},
      {"FadeEffect",
       std::make_shared<FadeEffect>(fixture.device(), fixture.queue(),
                                     fixture.format())},
      {"ThemeModulationEffect",
       std::make_shared<ThemeModulationEffect>(
           fixture.device(), fixture.queue(), fixture.format())},
  };

  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;
    }
  }

  fprintf(stdout, "  ✓ %d/%zu post-process effects tested\n", passed,
          effects.size());

  // Validation: Ensure test coverage matches expected count
  const int tested_count = static_cast<int>(effects.size());
  if (tested_count != EXPECTED_POST_PROCESS_COUNT) {
    fprintf(stderr, "  ✗ COVERAGE ERROR: Expected %d post-process effects, but only tested %d!\n",
            EXPECTED_POST_PROCESS_COUNT, tested_count);
    fprintf(stderr, "  ✗ Did you add a new post-process effect without updating the test?\n");
    fprintf(stderr, "  ✗ Update EXPECTED_POST_PROCESS_COUNT in test_demo_effects.cc\n");
    assert(false && "Post-process effect count mismatch - update test!");
  }
}

// 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.device(), fixture.queue(), fixture.format());

  // Test each scene effect
  std::vector<std::pair<const char*, std::shared_ptr<Effect>>> effects = {
      {"HeptagonEffect",
       std::make_shared<HeptagonEffect>(fixture.device(), fixture.queue(),
                                         fixture.format())},
      {"ParticlesEffect",
       std::make_shared<ParticlesEffect>(fixture.device(), fixture.queue(),
                                          fixture.format())},
      {"ParticleSprayEffect",
       std::make_shared<ParticleSprayEffect>(fixture.device(), fixture.queue(),
                                              fixture.format())},
      {"MovingEllipseEffect",
       std::make_shared<MovingEllipseEffect>(fixture.device(), fixture.queue(),
                                              fixture.format())},
      {"FlashCubeEffect",
       std::make_shared<FlashCubeEffect>(fixture.device(), fixture.queue(),
                                          fixture.format())},
      {"Hybrid3DEffect",
       std::make_shared<Hybrid3DEffect>(fixture.device(), fixture.queue(),
                                         fixture.format())},
  };

  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 and Hybrid3DEffect require full 3D pipeline (Renderer3D)
    const bool requires_3d = (strcmp(name, "FlashCubeEffect") == 0 ||
                              strcmp(name, "Hybrid3DEffect") == 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);

  // Validation: Ensure test coverage matches expected count
  const int tested_count = static_cast<int>(effects.size());
  if (tested_count != EXPECTED_SCENE_COUNT) {
    fprintf(stderr, "  ✗ COVERAGE ERROR: Expected %d scene effects, but only tested %d!\n",
            EXPECTED_SCENE_COUNT, tested_count);
    fprintf(stderr, "  ✗ Did you add a new scene effect without updating the test?\n");
    fprintf(stderr, "  ✗ Update EXPECTED_SCENE_COUNT in test_demo_effects.cc\n");
    assert(false && "Scene effect count mismatch - update test!");
  }
}

// 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<FlashEffect>(
      fixture.device(), fixture.queue(), fixture.format());
  assert(flash->is_post_process() && "FlashEffect should be post-process");

  auto blur = std::make_shared<GaussianBlurEffect>(
      fixture.device(), fixture.queue(), fixture.format());
  assert(blur->is_post_process() && "GaussianBlurEffect should be post-process");

  // Scene effects should return false
  auto heptagon = std::make_shared<HeptagonEffect>(
      fixture.device(), fixture.queue(), fixture.format());
  assert(!heptagon->is_post_process() && "HeptagonEffect should NOT be post-process");

  auto particles = std::make_shared<ParticlesEffect>(
      fixture.device(), fixture.queue(), fixture.format());
  assert(!particles->is_post_process() &&
         "ParticlesEffect should NOT be post-process");

  fprintf(stdout, "  ✓ Effect type classification correct\n");
}

int main() {
  fprintf(stdout, "=== Demo Effects Tests ===\n");

  test_post_process_effects();
  test_scene_effects();
  test_effect_type_classification();

  fprintf(stdout, "=== All Demo Effects Tests Passed ===\n");
  return 0;
}

#endif  /* !defined(STRIP_ALL) */