diff --git a/CMakeLists.txt b/CMakeLists.txt index 2776e25..7b21d16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.24) project(SDL3App LANGUAGES CXX) +option(BUILD_SDL3_APP "Build the SDL3 Vulkan demo" ON) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -9,23 +10,37 @@ if(EXISTS "${CMAKE_BINARY_DIR}/conan_toolchain.cmake") include("${CMAKE_BINARY_DIR}/conan_toolchain.cmake") endif() -find_package(SDL3 CONFIG REQUIRED) -find_package(Vulkan REQUIRED) +if(BUILD_SDL3_APP) + find_package(SDL3 CONFIG REQUIRED) + find_package(Vulkan REQUIRED) +endif() find_package(lua CONFIG REQUIRED) -add_executable(sdl3_app - src/main.cpp - src/app/sdl3_app_core.cpp - src/app/sdl3_app_device.cpp - src/app/sdl3_app_swapchain.cpp - src/app/sdl3_app_pipeline.cpp - src/app/sdl3_app_build.cpp - src/app/sdl3_app_buffers.cpp - src/app/sdl3_app_render.cpp +if(BUILD_SDL3_APP) + add_executable(sdl3_app + src/main.cpp + src/app/sdl3_app_core.cpp + src/app/sdl3_app_device.cpp + src/app/sdl3_app_swapchain.cpp + src/app/sdl3_app_pipeline.cpp + src/app/sdl3_app_build.cpp + src/app/sdl3_app_buffers.cpp + src/app/sdl3_app_render.cpp + src/script/cube_script.cpp + ) + target_link_libraries(sdl3_app PRIVATE SDL3::SDL3 Vulkan::Vulkan lua::lua) + target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED) + + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/shaders" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/scripts" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +endif() + +enable_testing() + +add_executable(cube_script_tests + tests/test_cube_script.cpp src/script/cube_script.cpp ) -target_link_libraries(sdl3_app PRIVATE SDL3::SDL3 Vulkan::Vulkan lua::lua) -target_compile_definitions(sdl3_app PRIVATE SDL_MAIN_HANDLED) - -file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/shaders" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") -file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/scripts" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") +target_include_directories(cube_script_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_link_libraries(cube_script_tests PRIVATE lua::lua) +add_test(NAME cube_script_tests COMMAND cube_script_tests) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..0c47d7a --- /dev/null +++ b/conanfile.py @@ -0,0 +1,20 @@ +from conan import ConanFile + +class SDL3CppConan(ConanFile): + name = "sdl3cpp" + version = "0.1" + settings = "os", "arch", "compiler", "build_type" + options = {"build_app": [True, False]} + default_options = { + "build_app": True, + "lua/*:shared": False, + "lua/*:fPIC": True, + "lua/*:compile_as_cpp": False, + "lua/*:with_tools": False, + } + generators = "CMakeDeps", "CMakeToolchain", "VirtualRunEnv" + + def requirements(self): + self.requires("lua/5.4.6") + if self.options.build_app: + self.requires("vulkan-loader/1.4.313.0") diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 986e397..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,14 +0,0 @@ -[requires] -lua/5.4.6 -vulkan-loader/1.4.313.0 - -[options] -lua:shared=False -lua:fPIC=True -lua:compile_as_cpp=False -lua:with_tools=False - -[generators] -CMakeDeps -CMakeToolchain -VirtualRunEnv diff --git a/tests/scripts/unit_cube_logic.lua b/tests/scripts/unit_cube_logic.lua new file mode 100644 index 0000000..17a304c --- /dev/null +++ b/tests/scripts/unit_cube_logic.lua @@ -0,0 +1,42 @@ +local function identity_matrix() + return { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + } +end + +function get_scene_objects() + return { + { + vertices = { + { position = {0.0, 0.0, 0.0}, color = {1.0, 0.0, 0.0} }, + { position = {1.0, 0.0, 0.0}, color = {0.0, 1.0, 0.0} }, + { position = {0.0, 1.0, 0.0}, color = {0.0, 0.0, 1.0} }, + }, + indices = {1, 2, 3}, + compute_model_matrix = function(time) + return identity_matrix() + end, + shader_key = "test", + }, + } +end + +function get_shader_paths() + return { + test = { + vertex = "shaders/test.vert.spv", + fragment = "shaders/test.frag.spv", + }, + } +end + +function get_view_projection(aspect) + return identity_matrix() +end + +function compute_model_matrix(time) + return identity_matrix() +end diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp new file mode 100644 index 0000000..d8b4c4f --- /dev/null +++ b/tests/test_cube_script.cpp @@ -0,0 +1,97 @@ +#include "script/cube_script.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { +constexpr std::array kIdentityMatrix = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, +}; + +bool ApproximatelyEqual(float a, float b, float eps = 1e-5f) { + return std::fabs(a - b) <= eps; +} + +bool ExpectIdentity(const std::array& actual, const std::string& label, int& failures) { + for (size_t i = 0; i < actual.size(); ++i) { + if (!ApproximatelyEqual(actual[i], kIdentityMatrix[i])) { + std::cerr << label << " differs at index " << i << " (" << actual[i] << " vs " + << kIdentityMatrix[i] << ")\n"; + ++failures; + return false; + } + } + return true; +} + +std::filesystem::path GetTestScriptPath() { + auto testDir = std::filesystem::path(__FILE__).parent_path(); + return testDir / "scripts" / "unit_cube_logic.lua"; +} + +void Assert(bool condition, const std::string& message, int& failures) { + if (!condition) { + std::cerr << "test failure: " << message << '\n'; + ++failures; + } +} +} // namespace + +int main() { + int failures = 0; + auto scriptPath = GetTestScriptPath(); + std::cout << "Loading Lua fixture: " << scriptPath << '\n'; + + try { + sdl3cpp::script::CubeScript cubeScript(scriptPath); + auto objects = cubeScript.LoadSceneObjects(); + Assert(objects.size() == 1, "expected exactly one scene object", failures); + if (!objects.empty()) { + const auto& object = objects.front(); + Assert(object.vertices.size() == 3, "scene object should yield three vertices", failures); + Assert(object.indices.size() == 3, "scene object should yield three indices", failures); + Assert(object.shaderKey == "test", "shader key should match fixture", failures); + const std::vector expectedIndices{0, 1, 2}; + Assert(object.indices == expectedIndices, "indices should be zero-based", failures); + Assert(object.computeModelMatrixRef != LUA_REFNIL, + "vertex object must keep a Lua reference", failures); + + auto objectMatrix = cubeScript.ComputeModelMatrix(object.computeModelMatrixRef, 0.5f); + ExpectIdentity(objectMatrix, "object compute_model_matrix", failures); + } + + auto fallbackMatrix = cubeScript.ComputeModelMatrix(LUA_REFNIL, 1.0f); + ExpectIdentity(fallbackMatrix, "global compute_model_matrix", failures); + + auto viewProjection = cubeScript.GetViewProjectionMatrix(1.33f); + ExpectIdentity(viewProjection, "view_projection matrix", failures); + + auto shaderMap = cubeScript.LoadShaderPathsMap(); + Assert(shaderMap.size() == 1, "expected a single shader variant", failures); + auto testEntry = shaderMap.find("test"); + Assert(testEntry != shaderMap.end(), "shader map missing " + "test entry", failures); + if (testEntry != shaderMap.end()) { + Assert(testEntry->second.vertex == "shaders/test.vert.spv", "vertex shader path", failures); + Assert(testEntry->second.fragment == "shaders/test.frag.spv", "fragment shader path", failures); + } + } catch (const std::exception& ex) { + std::cerr << "exception during tests: " << ex.what() << '\n'; + return 1; + } + + if (failures == 0) { + std::cout << "cube_script_tests: PASSED\n"; + } else { + std::cerr << "cube_script_tests: FAILED (" << failures << " errors)\n"; + } + + return failures; +}