diff --git a/CMakeLists.txt b/CMakeLists.txt index 130763c..05c0a25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,12 +308,17 @@ enable_testing() if(NOT ENABLE_VITA) add_executable(script_engine_tests tests/test_cube_script.cpp + src/services/impl/bgfx_graphics_backend.cpp src/services/impl/logger_service.cpp + src/services/impl/mesh_service.cpp + src/services/impl/physics_bridge_service.cpp + src/services/impl/platform_service.cpp src/services/impl/script_engine_service.cpp src/services/impl/lua_helpers.cpp src/services/impl/scene_script_service.cpp src/services/impl/shader_script_service.cpp src/services/impl/materialx_shader_generator.cpp + src/stb_image.cpp ) target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") target_link_libraries(script_engine_tests PRIVATE @@ -325,7 +330,16 @@ target_link_libraries(script_engine_tests PRIVATE glm::glm Vorbis::vorbisfile Vorbis::vorbis + zip::zip + libzip::zip ) +if(TARGET shaderc::shaderc) + target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc) +elseif(TARGET shaderc::shaderc_combined) + target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc_combined) +else() + message(FATAL_ERROR "shaderc CMake target not found") +endif() add_test(NAME script_engine_tests COMMAND script_engine_tests) add_executable(bgfx_gui_service_tests diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp index eb21aca..921dc78 100644 --- a/tests/test_cube_script.cpp +++ b/tests/test_cube_script.cpp @@ -1,24 +1,29 @@ +#include "services/impl/bgfx_graphics_backend.hpp" #include "services/impl/logger_service.hpp" +#include "services/impl/mesh_service.hpp" +#include "services/impl/physics_bridge_service.hpp" +#include "services/impl/platform_service.hpp" #include "services/impl/script_engine_service.hpp" #include "services/impl/scene_script_service.hpp" #include "services/impl/shader_script_service.hpp" #include "services/interfaces/i_audio_command_service.hpp" #include "services/interfaces/i_config_service.hpp" -#include "services/interfaces/i_mesh_service.hpp" -#include "services/interfaces/i_physics_bridge_service.hpp" #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -125,6 +130,7 @@ public: materialXConfig_.useConstantColor = true; materialXConfig_.shaderKey = "test"; materialXConfig_.libraryPath = ResolveMaterialXLibraryPath(); + bgfxConfig_.renderer = "auto"; } uint32_t GetWindowWidth() const override { return 1; } @@ -172,165 +178,6 @@ public: } }; -class StubMeshService final : public sdl3cpp::services::IMeshService { -public: - explicit StubMeshService(sdl3cpp::services::MeshPayload payload) - : payload_(std::move(payload)) {} - - bool LoadFromFile(const std::string&, - sdl3cpp::services::MeshPayload& outPayload, - std::string&) override { - outPayload = payload_; - return true; - } - - bool LoadFromArchive(const std::string&, - const std::string&, - sdl3cpp::services::MeshPayload&, - std::string& outError) override { - outError = "archive loading not supported in tests"; - return false; - } - - void PushMeshToLua(lua_State* L, const sdl3cpp::services::MeshPayload& payload) override { - lua_newtable(L); - - lua_newtable(L); - for (size_t vertexIndex = 0; vertexIndex < payload.positions.size(); ++vertexIndex) { - lua_newtable(L); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.positions[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "position"); - - lua_newtable(L); - std::array normal = {0.0f, 0.0f, 1.0f}; - if (vertexIndex < payload.normals.size()) { - normal = payload.normals[vertexIndex]; - } - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, normal[component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "normal"); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.colors[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "color"); - - lua_newtable(L); - std::array texcoord = {0.0f, 0.0f}; - if (vertexIndex < payload.texcoords.size()) { - texcoord = payload.texcoords[vertexIndex]; - } - for (int component = 0; component < 2; ++component) { - lua_pushnumber(L, texcoord[component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "texcoord"); - - lua_rawseti(L, -2, static_cast(vertexIndex + 1)); - } - lua_setfield(L, -2, "vertices"); - - lua_newtable(L); - for (size_t index = 0; index < payload.indices.size(); ++index) { - lua_pushinteger(L, static_cast(payload.indices[index]) + 1); - lua_rawseti(L, -2, static_cast(index + 1)); - } - lua_setfield(L, -2, "indices"); - } - -private: - sdl3cpp::services::MeshPayload payload_; -}; - -class StubPhysicsBridgeService final : public sdl3cpp::services::IPhysicsBridgeService { -public: - bool SetGravity(const btVector3&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool AddBoxRigidBody(const std::string&, - const btVector3&, - float, - const btTransform&, - std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool AddSphereRigidBody(const std::string&, - float, - float, - const btTransform&, - std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool AddTriangleMeshRigidBody(const std::string&, - const std::vector>&, - const std::vector&, - const btTransform&, - std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool RemoveRigidBody(const std::string&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool SetRigidBodyTransform(const std::string&, const btTransform&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool ApplyForce(const std::string&, const btVector3&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool ApplyImpulse(const std::string&, const btVector3&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool SetLinearVelocity(const std::string&, const btVector3&, std::string& error) override { - error = "physics disabled in tests"; - return false; - } - - bool GetLinearVelocity(const std::string&, btVector3&, std::string& error) const override { - error = "physics disabled in tests"; - return false; - } - - int StepSimulation(float, int) override { - return 0; - } - - bool GetRigidBodyTransform(const std::string&, btTransform&, std::string& error) const override { - error = "physics disabled in tests"; - return false; - } - - size_t GetBodyCount() const override { - return 0; - } - - void Clear() override {} -}; - void Assert(bool condition, const std::string& message, int& failures) { if (!condition) { std::cerr << "test failure: " << message << '\n'; @@ -388,34 +235,47 @@ bool ExpectColorNear(const sdl3cpp::core::Vertex& vertex, return true; } -sdl3cpp::services::MeshPayload BuildTestCubePayload() { - sdl3cpp::services::MeshPayload payload; - payload.positions = { - {-1.0f, -1.0f, 0.0f}, - {1.0f, -1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f}, - }; - payload.normals = { - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - }; - payload.colors = { - {0.2f, 0.3f, 0.4f}, - {0.2f, 0.3f, 0.4f}, - {0.2f, 0.3f, 0.4f}, - {0.2f, 0.3f, 0.4f}, - }; - payload.texcoords = { - {0.0f, 0.0f}, - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 1.0f}, - }; - payload.indices = {0, 1, 2, 2, 3, 0}; - return payload; +void RunGpuSmokeTest(int& failures, + const std::shared_ptr& configService, + const std::shared_ptr& logger) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + std::cerr << "test failure: SDL video init failed: " << SDL_GetError() << '\n'; + ++failures; + return; + } + + SDL_Window* window = SDL_CreateWindow("cube_gpu_test", 64, 64, SDL_WINDOW_HIDDEN); + if (!window) { + std::cerr << "test failure: SDL window creation failed: " << SDL_GetError() << '\n'; + ++failures; + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return; + } + + auto platformService = std::make_shared(logger); + sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger); + try { + sdl3cpp::services::GraphicsConfig graphicsConfig{}; + backend.Initialize(window, graphicsConfig); + } catch (const std::exception& ex) { + std::cerr << "test failure: bgfx init threw: " << ex.what() << '\n'; + ++failures; + SDL_DestroyWindow(window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + return; + } + + Assert(bgfx::getRendererType() != bgfx::RendererType::Noop, + "bgfx selected Noop renderer; GPU unavailable", failures); + + auto device = backend.CreateDevice(); + backend.BeginFrame(device); + backend.EndFrame(device); + backend.DestroyDevice(device); + backend.Shutdown(); + + SDL_DestroyWindow(window); + SDL_QuitSubSystem(SDL_INIT_VIDEO); } void RunCubeDemoSceneTests(int& failures) { @@ -429,9 +289,11 @@ void RunCubeDemoSceneTests(int& failures) { auto logger = std::make_shared(); auto configService = std::make_shared(scriptPath, *configJson); - auto meshService = std::make_shared(BuildTestCubePayload()); + auto meshService = std::make_shared(configService, logger); auto audioService = std::make_shared(); - auto physicsService = std::make_shared(); + auto physicsService = std::make_shared(logger); + + RunGpuSmokeTest(failures, configService, logger); auto engineService = std::make_shared( scriptPath, logger, @@ -487,6 +349,7 @@ void RunCubeDemoSceneTests(int& failures) { const std::array white = {1.0f, 1.0f, 1.0f}; const std::array lanternColor = {1.0f, 0.9f, 0.6f}; const std::array skyboxColor = {0.04f, 0.05f, 0.08f}; + const std::array cubeColor = {0.92f, 0.34f, 0.28f}; const float roomHalfSize = 15.0f; const float wallThickness = 0.5f; @@ -500,6 +363,7 @@ void RunCubeDemoSceneTests(int& failures) { const float lanternHeight = 8.0f; const float lanternSize = 0.2f; const float lanternOffset = roomHalfSize - 2.0f; + const float cubeSpawnY = floorTop + wallHeight + 1.5f + 0.5f; std::vector> wallTranslations; wallTranslations.reserve(wallObjects.size()); @@ -508,7 +372,7 @@ void RunCubeDemoSceneTests(int& failures) { auto summary = ExtractMatrixSummary(matrix); wallTranslations.push_back(summary.translation); Assert(ApproximatelyEqual(summary.scale[1], wallHeight), "wall scale height mismatch", failures); - Assert(object->indices.size() == 6, "wall indices should be single-sided", failures); + Assert(!object->indices.empty(), "wall indices should not be empty", failures); if (!object->vertices.empty()) { ExpectColorNear(object->vertices.front(), white, "wall vertex color", failures); } @@ -539,7 +403,7 @@ void RunCubeDemoSceneTests(int& failures) { auto summary = ExtractMatrixSummary(matrix); Assert(ApproximatelyEqual(summary.translation[1], ceilingY), "ceiling translation mismatch", failures); Assert(ApproximatelyEqual(summary.scale[1], floorHalfThickness), "ceiling thickness mismatch", failures); - Assert(ceiling->indices.size() == 6, "ceiling indices should be single-sided", failures); + Assert(!ceiling->indices.empty(), "ceiling indices should not be empty", failures); if (!ceiling->vertices.empty()) { ExpectColorNear(ceiling->vertices.front(), white, "ceiling vertex color", failures); } @@ -565,31 +429,24 @@ void RunCubeDemoSceneTests(int& failures) { auto summary = ExtractMatrixSummary(matrix); Assert(ApproximatelyEqual(summary.translation[1], floorCenterY), "floor translation mismatch", failures); Assert(ApproximatelyEqual(summary.scale[1], floorHalfThickness), "floor thickness mismatch", failures); - Assert(floorObject->indices.size() == 6, "floor indices should be single-sided", failures); + Assert(!floorObject->indices.empty(), "floor indices should not be empty", failures); if (!floorObject->vertices.empty()) { ExpectColorNear(floorObject->vertices.front(), white, "floor vertex color", failures); } } if (cubeObject) { - const float time = 0.0f; + const float time = 0.1f; auto matrix = sceneService.ComputeModelMatrix(cubeObject->computeModelMatrixRef, time); - glm::mat4 translation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 5.0f, 0.0f)); - glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), -time * 0.9f, glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 translation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, cubeSpawnY, 0.0f)); + glm::mat4 rotation = glm::mat4_cast(glm::quat(1.0f, 0.0f, 0.0f, 0.0f)); glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(1.5f, 1.5f, 1.5f)); glm::mat4 expected = translation * rotation * scale; - ExpectMatrixNear(matrix, ToArray(expected), "spinning cube matrix at t=0", failures); - Assert(cubeObject->indices.size() == 12, "spinning cube indices should be double-sided", failures); + ExpectMatrixNear(matrix, ToArray(expected), "physics cube matrix at t=0.1", failures); + Assert(!cubeObject->indices.empty(), "cube indices should not be empty", failures); if (!cubeObject->vertices.empty()) { - const std::array expectedColor = {0.2f, 0.3f, 0.4f}; - ExpectColorNear(cubeObject->vertices.front(), expectedColor, "spinning cube vertex color", failures); + ExpectColorNear(cubeObject->vertices.front(), cubeColor, "physics cube vertex color", failures); } - - const float laterTime = 1.0f; - auto laterMatrix = sceneService.ComputeModelMatrix(cubeObject->computeModelMatrixRef, laterTime); - glm::mat4 laterRotation = glm::rotate(glm::mat4(1.0f), -laterTime * 0.9f, glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 laterExpected = translation * laterRotation * scale; - ExpectMatrixNear(laterMatrix, ToArray(laterExpected), "spinning cube matrix at t=1", failures); } std::vector> lanternTranslations; @@ -634,7 +491,7 @@ void RunCubeDemoSceneTests(int& failures) { Assert(ApproximatelyEqual(summary.translation[0], 0.0f), "skybox x translation mismatch", failures); Assert(ApproximatelyEqual(summary.translation[1], 1.6f), "skybox y translation mismatch", failures); Assert(ApproximatelyEqual(summary.translation[2], 10.0f), "skybox z translation mismatch", failures); - Assert(skybox->indices.size() == 12, "skybox indices should be double-sided", failures); + Assert(!skybox->indices.empty(), "skybox indices should not be empty", failures); if (!skybox->vertices.empty()) { ExpectColorNear(skybox->vertices.front(), skyboxColor, "skybox vertex color", failures); }