diff --git a/config/engine_tester_runtime.json b/config/engine_tester_runtime.json index d889026..a5005f6 100644 --- a/config/engine_tester_runtime.json +++ b/config/engine_tester_runtime.json @@ -47,6 +47,34 @@ "type": "luma_range", "min_luma": 0.01, "max_luma": 0.95 + }, + { + "type": "mean_color", + "color": [0.4, 0.45, 0.5], + "tolerance": 0.25 + }, + { + "type": "sample_points", + "points": [ + { + "x": 0.25, + "y": 0.25, + "color": [0.2, 0.3, 0.4], + "tolerance": 0.35 + }, + { + "x": 0.5, + "y": 0.5, + "color": [0.1, 0.2, 0.6], + "tolerance": 0.4 + }, + { + "x": 0.75, + "y": 0.75, + "color": [0.8, 0.85, 0.9], + "tolerance": 0.3 + } + ] } ] } diff --git a/docs/PROMPT.md b/docs/PROMPT.md index 17a0825..75084c8 100644 --- a/docs/PROMPT.md +++ b/docs/PROMPT.md @@ -49,3 +49,7 @@ Avoid stagnation; improve structure, clarity, and expressiveness over time. Roadmap discipline. If you run out of ROADMAP.md tasks, stop and report. We should reassess priorities before inventing work. + +Prefer JSON workflows over LUA - Steps can be written in host language. + +Vulkan can be a bit sketchy at first, very cryptic crashes. Try OpenGL. diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp index 9f69caf..7c7eb8a 100644 --- a/tests/test_cube_script.cpp +++ b/tests/test_cube_script.cpp @@ -74,6 +74,19 @@ std::filesystem::path GetSeedConfigPath() { return repoRoot / "config" / "seed_runtime.json"; } +std::string DeterminePreferredRenderer() { + if (const char* envRenderer = std::getenv("BGFX_RENDERER")) { + return envRenderer; + } + if (const char* videoDriver = std::getenv("SDL_VIDEODRIVER")) { + std::string driver(videoDriver); + if (driver == "offscreen" || driver == "dummy") { + return "opengl"; + } + } + return "opengl"; +} + std::optional ReadFileContents(const std::filesystem::path& path) { std::ifstream input(path); if (!input) { @@ -385,154 +398,146 @@ bool RunGpuRenderTest(int& failures, if (success) { std::cout << "GPU render test: Validating full render pipeline with scene geometry\n"; - + // Load and render the actual cube scene to catch color, geometry, and animation issues auto scriptPath = GetCubeScriptPath(); - auto configPath = GetSeedConfigPath(); - auto configJson = ReadFileContents(configPath); - if (!configJson) { - std::cerr << "GPU render test failed: could not load scene config\n"; + auto sceneConfigService = configService; + auto meshService = std::make_shared(sceneConfigService, logger); + auto audioService = std::make_shared(); + auto physicsService = std::make_shared(logger); + + auto engineService = std::make_shared( + scriptPath, + logger, + meshService, + audioService, + physicsService, + nullptr, + nullptr, + sceneConfigService, + false); + engineService->Initialize(); + + auto sceneScriptService = std::make_shared(engineService, logger); + auto objects = sceneScriptService->LoadSceneObjects(); + + if (objects.size() != 15) { + std::cerr << "GPU render test: Scene loaded " << objects.size() << " objects, expected 15\n"; ++failures; success = false; - } else { - auto sceneConfigService = std::make_shared(scriptPath, *configJson, "vulkan"); - auto meshService = std::make_shared(sceneConfigService, logger); - auto audioService = std::make_shared(); - auto physicsService = std::make_shared(logger); - - auto engineService = std::make_shared( - scriptPath, - logger, - meshService, - audioService, - physicsService, - nullptr, - nullptr, - sceneConfigService, - false); - engineService->Initialize(); - - auto sceneScriptService = std::make_shared(engineService, logger); - auto objects = sceneScriptService->LoadSceneObjects(); - - if (objects.size() != 15) { - std::cerr << "GPU render test: Scene loaded " << objects.size() << " objects, expected 15\n"; + } + + // Validate all geometry is present + bool hasFloor = false; + bool hasCeiling = false; + int wallCount = 0; + int lanternCount = 0; + bool hasCube = false; + + for (const auto& obj : objects) { + const auto& type = obj.objectType; + + if (type == "floor") { + hasFloor = true; + } else if (type == "ceiling") { + hasCeiling = true; + } else if (type == "wall") { + wallCount++; + } else if (type == "lantern") { + lanternCount++; + } else if (type == "physics_cube" || type == "spinning_cube") { + hasCube = true; + } + } + + Assert(hasFloor, "GPU render test: Missing floor geometry", failures); + Assert(hasCeiling, "GPU render test: Missing ceiling geometry", failures); + Assert(wallCount == 4, "GPU render test: Expected 4 walls, got " + std::to_string(wallCount), failures); + Assert(lanternCount == 8, "GPU render test: Expected 8 lanterns, got " + std::to_string(lanternCount), failures); + Assert(hasCube, "GPU render test: Missing physics cube geometry", failures); + + // Validate all scene objects have valid shader keys (critical for rendering) + for (size_t i = 0; i < objects.size(); ++i) { + const auto& obj = objects[i]; + Assert(!obj.shaderKeys.empty(), + "GPU render test: Object " + std::to_string(i) + " (" + obj.objectType + ") has no shader keys", + failures); + + // Validate room geometry (floor, ceiling, walls) has expected shader keys + if (obj.objectType == "floor") { + Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "floor", + "GPU render test: Floor should have shader key 'floor'", failures); + Assert(obj.vertices.size() >= 100, + "GPU render test: Floor should have tessellated geometry (expected >= 100 vertices, got " + + std::to_string(obj.vertices.size()) + ")", failures); + } else if (obj.objectType == "ceiling") { + Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "ceiling", + "GPU render test: Ceiling should have shader key 'ceiling'", failures); + Assert(obj.vertices.size() >= 100, + "GPU render test: Ceiling should have tessellated geometry (expected >= 100 vertices, got " + + std::to_string(obj.vertices.size()) + ")", failures); + } else if (obj.objectType == "wall") { + Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "wall", + "GPU render test: Wall should have shader key 'wall'", failures); + } + } + + // Create actual render buffers and render multiple frames to test animation + auto ecsService = std::make_shared(logger); + auto sceneService = std::make_shared(sceneScriptService, ecsService, logger); + sceneService->LoadScene(objects); + + auto device = backend.CreateDevice(); + const int testFrames = 5; + std::cout << "GPU render test: Rendering " << testFrames << " frames to validate pipeline\n"; + + for (int frame = 0; frame < testFrames; ++frame) { + float elapsedTime = frame * 0.016f; // ~60 FPS + auto renderCommands = sceneService->GetRenderCommands(elapsedTime); + + if (renderCommands.size() != objects.size()) { + std::cerr << "GPU render test: Frame " << frame << " produced " + << renderCommands.size() << " commands, expected " << objects.size() << '\n'; ++failures; success = false; + break; } - - // Validate all geometry is present - bool hasFloor = false; - bool hasCeiling = false; - int wallCount = 0; - int lanternCount = 0; - bool hasCube = false; - - for (const auto& obj : objects) { - const auto& type = obj.objectType; - - if (type == "floor") { - hasFloor = true; - } else if (type == "ceiling") { - hasCeiling = true; - } else if (type == "wall") { - wallCount++; - } else if (type == "lantern") { - lanternCount++; - } else if (type == "physics_cube" || type == "spinning_cube") { - hasCube = true; - } - } - - Assert(hasFloor, "GPU render test: Missing floor geometry", failures); - Assert(hasCeiling, "GPU render test: Missing ceiling geometry", failures); - Assert(wallCount == 4, "GPU render test: Expected 4 walls, got " + std::to_string(wallCount), failures); - Assert(lanternCount == 8, "GPU render test: Expected 8 lanterns, got " + std::to_string(lanternCount), failures); - Assert(hasCube, "GPU render test: Missing physics cube geometry", failures); - - // Validate all scene objects have valid shader keys (critical for rendering) - for (size_t i = 0; i < objects.size(); ++i) { - const auto& obj = objects[i]; - Assert(!obj.shaderKeys.empty(), - "GPU render test: Object " + std::to_string(i) + " (" + obj.objectType + ") has no shader keys", - failures); - - // Validate room geometry (floor, ceiling, walls) has expected shader keys - if (obj.objectType == "floor") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "floor", - "GPU render test: Floor should have shader key 'floor'", failures); - Assert(obj.vertices.size() >= 100, - "GPU render test: Floor should have tessellated geometry (expected >= 100 vertices, got " + - std::to_string(obj.vertices.size()) + ")", failures); - } else if (obj.objectType == "ceiling") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "ceiling", - "GPU render test: Ceiling should have shader key 'ceiling'", failures); - Assert(obj.vertices.size() >= 100, - "GPU render test: Ceiling should have tessellated geometry (expected >= 100 vertices, got " + - std::to_string(obj.vertices.size()) + ")", failures); - } else if (obj.objectType == "wall") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "wall", - "GPU render test: Wall should have shader key 'wall'", failures); - } - } - - // Create actual render buffers and render multiple frames to test animation - auto ecsService = std::make_shared(logger); - auto sceneService = std::make_shared(sceneScriptService, ecsService, logger); - sceneService->LoadScene(objects); - - auto device = backend.CreateDevice(); - const int testFrames = 5; - std::cout << "GPU render test: Rendering " << testFrames << " frames to validate pipeline\n"; - - for (int frame = 0; frame < testFrames; ++frame) { - float elapsedTime = frame * 0.016f; // ~60 FPS - auto renderCommands = sceneService->GetRenderCommands(elapsedTime); - - if (renderCommands.size() != objects.size()) { - std::cerr << "GPU render test: Frame " << frame << " produced " - << renderCommands.size() << " commands, expected " << objects.size() << '\n'; - ++failures; - success = false; - break; - } - - backend.BeginFrame(device); - - // Validate cube is animating (matrix should change between frames) - if (frame == 0) { - for (size_t i = 0; i < renderCommands.size(); ++i) { - if (objects[i].shaderKeys.empty()) continue; - const auto& key = objects[i].shaderKeys.front(); - if (key == "floor" && objects[i].vertices.size() < 100) { - // This is the cube - verify it has non-identity matrix (spinning) - bool hasRotation = false; - const auto& matrix = renderCommands[i].modelMatrix; - // Check off-diagonal elements for rotation - if (std::abs(matrix[1]) > 0.01f || std::abs(matrix[2]) > 0.01f || - std::abs(matrix[4]) > 0.01f || std::abs(matrix[6]) > 0.01f || - std::abs(matrix[8]) > 0.01f || std::abs(matrix[9]) > 0.01f) { - hasRotation = true; - } - if (!hasRotation) { - std::cerr << "GPU render test: Cube is not spinning (rotation matrix is identity)\n"; - ++failures; - success = false; - } - break; + + backend.BeginFrame(device); + + // Validate cube is animating (matrix should change between frames) + if (frame == 0) { + for (size_t i = 0; i < renderCommands.size(); ++i) { + if (objects[i].shaderKeys.empty()) continue; + const auto& key = objects[i].shaderKeys.front(); + if (key == "floor" && objects[i].vertices.size() < 100) { + // This is the cube - verify it has non-identity matrix (spinning) + bool hasRotation = false; + const auto& matrix = renderCommands[i].modelMatrix; + // Check off-diagonal elements for rotation + if (std::abs(matrix[1]) > 0.01f || std::abs(matrix[2]) > 0.01f || + std::abs(matrix[4]) > 0.01f || std::abs(matrix[6]) > 0.01f || + std::abs(matrix[8]) > 0.01f || std::abs(matrix[9]) > 0.01f) { + hasRotation = true; } + if (!hasRotation) { + std::cerr << "GPU render test: Cube is not spinning (rotation matrix is identity)\n"; + ++failures; + success = false; + } + break; } } - - backend.EndFrame(device); } - - backend.DestroyDevice(device); - sceneService->Shutdown(); - engineService->Shutdown(); - - std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n"; + + backend.EndFrame(device); } + + backend.DestroyDevice(device); + sceneService->Shutdown(); + engineService->Shutdown(); + + std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n"; } backend.Shutdown(); @@ -552,7 +557,8 @@ void RunCubeDemoSceneTests(int& failures) { } auto logger = std::make_shared(); - auto configService = std::make_shared(scriptPath, *configJson, "vulkan"); + const std::string preferredRenderer = DeterminePreferredRenderer(); + auto configService = std::make_shared(scriptPath, *configJson, preferredRenderer); auto meshService = std::make_shared(configService, logger); auto audioService = std::make_shared(); auto physicsService = std::make_shared(logger);