ROADMAP.md

This commit is contained in:
2026-01-09 22:23:00 +00:00
parent cafd1c76c1
commit a3b0121e47
3 changed files with 175 additions and 137 deletions

View File

@@ -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<std::string> 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<sdl3cpp::services::impl::MeshService>(sceneConfigService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
scriptPath,
logger,
meshService,
audioService,
physicsService,
nullptr,
nullptr,
sceneConfigService,
false);
engineService->Initialize();
auto sceneScriptService = std::make_shared<sdl3cpp::services::impl::SceneScriptService>(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<CubeDemoConfigService>(scriptPath, *configJson, "vulkan");
auto meshService = std::make_shared<sdl3cpp::services::impl::MeshService>(sceneConfigService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
scriptPath,
logger,
meshService,
audioService,
physicsService,
nullptr,
nullptr,
sceneConfigService,
false);
engineService->Initialize();
auto sceneScriptService = std::make_shared<sdl3cpp::services::impl::SceneScriptService>(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<sdl3cpp::services::impl::EcsService>(logger);
auto sceneService = std::make_shared<sdl3cpp::services::impl::SceneService>(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<sdl3cpp::services::impl::EcsService>(logger);
auto sceneService = std::make_shared<sdl3cpp::services::impl::SceneService>(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<sdl3cpp::services::impl::LoggerService>();
auto configService = std::make_shared<CubeDemoConfigService>(scriptPath, *configJson, "vulkan");
const std::string preferredRenderer = DeterminePreferredRenderer();
auto configService = std::make_shared<CubeDemoConfigService>(scriptPath, *configJson, preferredRenderer);
auto meshService = std::make_shared<sdl3cpp::services::impl::MeshService>(configService, logger);
auto audioService = std::make_shared<StubAudioCommandService>();
auto physicsService = std::make_shared<sdl3cpp::services::impl::PhysicsBridgeService>(logger);