mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
ROADMAP.md
This commit is contained in:
@@ -123,7 +123,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT ENABLE_VITA)
|
if(NOT ENABLE_VITA)
|
||||||
find_package(lua CONFIG REQUIRED)
|
|
||||||
find_package(CLI11 CONFIG REQUIRED)
|
find_package(CLI11 CONFIG REQUIRED)
|
||||||
find_package(RapidJSON CONFIG REQUIRED)
|
find_package(RapidJSON CONFIG REQUIRED)
|
||||||
find_package(EnTT CONFIG REQUIRED)
|
find_package(EnTT CONFIG REQUIRED)
|
||||||
@@ -352,16 +351,11 @@ if(BUILD_SDL3_APP)
|
|||||||
src/services/impl/scene/ecs_service.cpp
|
src/services/impl/scene/ecs_service.cpp
|
||||||
src/services/impl/platform/platform_service.cpp
|
src/services/impl/platform/platform_service.cpp
|
||||||
src/services/impl/diagnostics/probe_service.cpp
|
src/services/impl/diagnostics/probe_service.cpp
|
||||||
src/services/impl/script/script_engine_service.cpp
|
|
||||||
src/services/impl/script/lua_helpers.cpp
|
|
||||||
src/services/impl/script/scene_script_service.cpp
|
|
||||||
src/services/impl/script/shader_script_service.cpp
|
|
||||||
src/services/impl/shader/shader_system_registry.cpp
|
src/services/impl/shader/shader_system_registry.cpp
|
||||||
src/services/impl/shader/materialx_shader_system.cpp
|
src/services/impl/shader/materialx_shader_system.cpp
|
||||||
src/services/impl/shader/glsl_shader_system.cpp
|
src/services/impl/shader/glsl_shader_system.cpp
|
||||||
src/services/impl/materialx/materialx_shader_generator.cpp
|
src/services/impl/materialx/materialx_shader_generator.cpp
|
||||||
src/services/impl/shader/shader_pipeline_validator.cpp
|
src/services/impl/shader/shader_pipeline_validator.cpp
|
||||||
src/services/impl/script/gui_script_service.cpp
|
|
||||||
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/gui/bgfx_gui_service.cpp>
|
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/gui/bgfx_gui_service.cpp>
|
||||||
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/graphics/bgfx_shader_compiler.cpp>
|
$<$<NOT:$<BOOL:${ENABLE_VITA}>>:src/services/impl/graphics/bgfx_shader_compiler.cpp>
|
||||||
src/services/impl/audio/audio_command_service.cpp
|
src/services/impl/audio/audio_command_service.cpp
|
||||||
@@ -391,7 +385,6 @@ if(BUILD_SDL3_APP)
|
|||||||
target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
|
target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
|
||||||
target_link_libraries(sdl3_app PRIVATE
|
target_link_libraries(sdl3_app PRIVATE
|
||||||
${SDL_TARGET}
|
${SDL_TARGET}
|
||||||
lua::lua
|
|
||||||
CLI11::CLI11
|
CLI11::CLI11
|
||||||
rapidjson
|
rapidjson
|
||||||
${SDL3CPP_STB_LIBS}
|
${SDL3CPP_STB_LIBS}
|
||||||
@@ -461,60 +454,6 @@ endif()
|
|||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
if(NOT ENABLE_VITA)
|
if(NOT ENABLE_VITA)
|
||||||
add_executable(script_engine_tests
|
|
||||||
tests/test_cube_script.cpp
|
|
||||||
src/services/impl/graphics/bgfx_graphics_backend.cpp
|
|
||||||
src/services/impl/graphics/bgfx_shader_compiler.cpp
|
|
||||||
src/services/impl/diagnostics/logger_service.cpp
|
|
||||||
src/services/impl/scene/mesh_service.cpp
|
|
||||||
src/services/impl/scene/physics_bridge_service.cpp
|
|
||||||
src/services/impl/platform/platform_service.cpp
|
|
||||||
src/services/impl/script/script_engine_service.cpp
|
|
||||||
${MATERIALX_SCRIPT_SOURCES}
|
|
||||||
src/services/impl/script/lua_helpers.cpp
|
|
||||||
src/services/impl/script/scene_script_service.cpp
|
|
||||||
src/services/impl/script/shader_script_service.cpp
|
|
||||||
src/services/impl/shader/shader_system_registry.cpp
|
|
||||||
src/services/impl/shader/materialx_shader_system.cpp
|
|
||||||
src/services/impl/shader/glsl_shader_system.cpp
|
|
||||||
src/services/impl/materialx/materialx_shader_generator.cpp
|
|
||||||
src/services/impl/shader/shader_pipeline_validator.cpp
|
|
||||||
src/services/impl/platform/sdl_window_service.cpp
|
|
||||||
src/services/impl/scene/ecs_service.cpp
|
|
||||||
src/services/impl/scene/scene_service.cpp
|
|
||||||
src/events/event_bus.cpp
|
|
||||||
src/stb_image.cpp
|
|
||||||
src/services/impl/shader/pipeline_compiler_service.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
|
||||||
target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
|
|
||||||
target_link_libraries(script_engine_tests PRIVATE
|
|
||||||
${SDL_TARGET}
|
|
||||||
lua::lua
|
|
||||||
rapidjson
|
|
||||||
${SDL3CPP_RENDER_STACK_LIBS}
|
|
||||||
assimp::assimp
|
|
||||||
Bullet::Bullet
|
|
||||||
glm::glm
|
|
||||||
Vorbis::vorbisfile
|
|
||||||
Vorbis::vorbis
|
|
||||||
zip::zip
|
|
||||||
libzip::zip
|
|
||||||
)
|
|
||||||
if(TARGET shaderc_local)
|
|
||||||
target_link_libraries(script_engine_tests PRIVATE shaderc_local)
|
|
||||||
elseif(TARGET shaderc::shaderc)
|
|
||||||
elseif(TARGET shaderc::shaderc_combined)
|
|
||||||
target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc_combined)
|
|
||||||
else()
|
|
||||||
if(TARGET shaderc_local)
|
|
||||||
target_link_libraries(script_engine_tests PRIVATE shaderc_local)
|
|
||||||
else()
|
|
||||||
message(WARNING "shaderc CMake target not found; skipping link for script_engine_tests.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
add_test(NAME script_engine_tests COMMAND script_engine_tests)
|
|
||||||
|
|
||||||
add_executable(bgfx_gui_service_tests
|
add_executable(bgfx_gui_service_tests
|
||||||
tests/test_bgfx_gui_service.cpp
|
tests/test_bgfx_gui_service.cpp
|
||||||
src/services/impl/gui/bgfx_gui_service.cpp
|
src/services/impl/gui/bgfx_gui_service.cpp
|
||||||
@@ -758,21 +697,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
add_test(NAME bgfx_backend_frame_guard_test COMMAND bgfx_backend_frame_guard_test)
|
add_test(NAME bgfx_backend_frame_guard_test COMMAND bgfx_backend_frame_guard_test)
|
||||||
|
|
||||||
# Test: Gui script service missing fields (logs trace and uses defaults)
|
|
||||||
add_executable(gui_script_service_missing_fields_test
|
|
||||||
tests/gui_script_service_missing_fields_test.cpp
|
|
||||||
src/services/impl/script/gui_script_service.cpp
|
|
||||||
src/services/impl/script/lua_helpers.cpp
|
|
||||||
)
|
|
||||||
target_include_directories(gui_script_service_missing_fields_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
|
||||||
target_link_libraries(gui_script_service_missing_fields_test PRIVATE
|
|
||||||
GTest::gtest
|
|
||||||
GTest::gtest_main
|
|
||||||
lua::lua
|
|
||||||
glm::glm
|
|
||||||
)
|
|
||||||
add_test(NAME gui_script_service_missing_fields_test COMMAND gui_script_service_missing_fields_test)
|
|
||||||
|
|
||||||
# Test: Graphics service buffer lifecycle (TDD guard for VRAM leaks on reupload)
|
# Test: Graphics service buffer lifecycle (TDD guard for VRAM leaks on reupload)
|
||||||
add_executable(graphics_service_buffer_lifecycle_test
|
add_executable(graphics_service_buffer_lifecycle_test
|
||||||
tests/graphics_service_buffer_lifecycle_test.cpp
|
tests/graphics_service_buffer_lifecycle_test.cpp
|
||||||
|
|||||||
@@ -8,14 +8,9 @@ class SDL3CppConan(ConanFile):
|
|||||||
options = {"build_app": [True, False]}
|
options = {"build_app": [True, False]}
|
||||||
default_options = {
|
default_options = {
|
||||||
"build_app": True,
|
"build_app": True,
|
||||||
"lua/*:shared": False,
|
|
||||||
"lua/*:fPIC": True,
|
|
||||||
"lua/*:compile_as_cpp": False,
|
|
||||||
"lua/*:with_tools": False,
|
|
||||||
}
|
}
|
||||||
generators = "CMakeDeps", "VirtualRunEnv"
|
generators = "CMakeDeps", "VirtualRunEnv"
|
||||||
BASE_REQUIRES = (
|
BASE_REQUIRES = (
|
||||||
"lua/5.4.8",
|
|
||||||
"sdl/3.2.20",
|
"sdl/3.2.20",
|
||||||
"shaderc/2023.6",
|
"shaderc/2023.6",
|
||||||
"cpptrace/1.0.4",
|
"cpptrace/1.0.4",
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "assets",
|
"name": "assets",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "assets",
|
"description": "Shared runtime assets (audio, fonts, images) used by demo packages.",
|
||||||
"defaultWorkflow": null,
|
"defaultWorkflow": "workflows/assets_catalog.json",
|
||||||
"workflows": [
|
"workflows": [
|
||||||
|
"workflows/assets_catalog.json"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
],
|
"assets/audio",
|
||||||
"scene": [
|
"assets/fonts",
|
||||||
],
|
"assets/images"
|
||||||
"shaders": [
|
|
||||||
],
|
|
||||||
"dependencies": [
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
41
packages/assets/workflows/assets_catalog.json
Normal file
41
packages/assets/workflows/assets_catalog.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"template": "package.assets",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "asset_roots",
|
||||||
|
"plugin": "list.literal",
|
||||||
|
"position": [0, 0],
|
||||||
|
"outputs": {
|
||||||
|
"list": "assets.roots"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"items": [
|
||||||
|
"assets/audio",
|
||||||
|
"assets/fonts",
|
||||||
|
"assets/images"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "assert_asset_roots",
|
||||||
|
"plugin": "value.assert.type",
|
||||||
|
"position": [260, 0],
|
||||||
|
"inputs": {
|
||||||
|
"value": "assets.roots"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"type": "string_list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"asset_roots": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "assert_asset_roots", "type": "main", "index": 0 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,5 @@
|
|||||||
"workflows": [
|
"workflows": [
|
||||||
"workflows/boot_default.json",
|
"workflows/boot_default.json",
|
||||||
"workflows/frame_default.json"
|
"workflows/frame_default.json"
|
||||||
],
|
]
|
||||||
"assets": [
|
|
||||||
],
|
|
||||||
"scene": [
|
|
||||||
],
|
|
||||||
"dependencies": []
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/materialx/assets/materialx_paths.json
Normal file
5
packages/materialx/assets/materialx_paths.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"libraries": "libraries",
|
||||||
|
"resources": "resources",
|
||||||
|
"documents": "documents"
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "MaterialX",
|
"name": "materialx",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "MaterialX",
|
"description": "MaterialX library bundle (libraries + resources) for shader generation.",
|
||||||
"defaultWorkflow": null,
|
"defaultWorkflow": "workflows/materialx_catalog.json",
|
||||||
"workflows": [
|
"workflows": [
|
||||||
|
"workflows/materialx_catalog.json"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
],
|
"libraries",
|
||||||
"scene": [
|
"resources",
|
||||||
],
|
"documents",
|
||||||
"shaders": [
|
"assets/materialx_paths.json"
|
||||||
],
|
|
||||||
"dependencies": [
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
41
packages/materialx/workflows/materialx_catalog.json
Normal file
41
packages/materialx/workflows/materialx_catalog.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"template": "package.materialx",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "materialx_paths",
|
||||||
|
"plugin": "list.literal",
|
||||||
|
"position": [0, 0],
|
||||||
|
"outputs": {
|
||||||
|
"list": "materialx.paths"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"items": [
|
||||||
|
"libraries",
|
||||||
|
"resources",
|
||||||
|
"documents"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "assert_materialx_paths",
|
||||||
|
"plugin": "value.assert.type",
|
||||||
|
"position": [260, 0],
|
||||||
|
"inputs": {
|
||||||
|
"value": "materialx.paths"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"type": "string_list"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"materialx_paths": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{ "node": "assert_materialx_paths", "type": "main", "index": 0 }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,396 +0,0 @@
|
|||||||
#include "gui_script_service.hpp"
|
|
||||||
|
|
||||||
#include "lua_helpers.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
|
|
||||||
#include <lua.hpp>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
GuiScriptService::GuiScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<ILogger> logger)
|
|
||||||
: engineService_(std::move(engineService)),
|
|
||||||
logger_(std::move(logger)) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "GuiScriptService",
|
|
||||||
"engineService=" + std::string(engineService_ ? "set" : "null"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GuiScriptService::Initialize() {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "Initialize");
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
lua_getglobal(L, "gui_input");
|
|
||||||
if (!lua_isnil(L, -1)) {
|
|
||||||
guiInputRef_ = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
} else {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_getglobal(L, "get_gui_commands");
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
guiCommandsFnRef_ = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
} else {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GuiScriptService::Shutdown() noexcept {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "Shutdown");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!engineService_ || !engineService_->IsInitialized()) {
|
|
||||||
guiInputRef_ = -1;
|
|
||||||
guiCommandsFnRef_ = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_State* L = engineService_->GetLuaState();
|
|
||||||
if (!L) {
|
|
||||||
guiInputRef_ = -1;
|
|
||||||
guiCommandsFnRef_ = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (guiInputRef_ >= 0) {
|
|
||||||
luaL_unref(L, LUA_REGISTRYINDEX, guiInputRef_);
|
|
||||||
}
|
|
||||||
if (guiCommandsFnRef_ >= 0) {
|
|
||||||
luaL_unref(L, LUA_REGISTRYINDEX, guiCommandsFnRef_);
|
|
||||||
}
|
|
||||||
guiInputRef_ = -1;
|
|
||||||
guiCommandsFnRef_ = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<GuiCommand> GuiScriptService::LoadGuiCommands() {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "LoadGuiCommands");
|
|
||||||
}
|
|
||||||
if (guiCommandsFnRef_ < 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
std::vector<GuiCommand> commands;
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, guiCommandsFnRef_);
|
|
||||||
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
|
|
||||||
std::string message = lua::GetLuaError(L);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua get_gui_commands failed: " + message);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua get_gui_commands failed: " + message);
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("'get_gui_commands' did not return a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("'get_gui_commands' did not return a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = lua_rawlen(L, -1);
|
|
||||||
commands.reserve(count);
|
|
||||||
|
|
||||||
for (size_t i = 1; i <= count; ++i) {
|
|
||||||
lua_rawgeti(L, -1, static_cast<int>(i));
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("GUI command at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
int commandIndex = lua_gettop(L);
|
|
||||||
lua_getfield(L, commandIndex, "type");
|
|
||||||
const char* typeName = lua_tostring(L, -1);
|
|
||||||
if (!typeName) {
|
|
||||||
lua_pop(L, 2);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("GUI command at index " + std::to_string(i) + " is missing a type");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type");
|
|
||||||
}
|
|
||||||
GuiCommand command{};
|
|
||||||
if (std::strcmp(typeName, "rect") == 0) {
|
|
||||||
command.type = GuiCommand::Type::Rect;
|
|
||||||
command.rect = ReadRect(L, commandIndex);
|
|
||||||
command.color = ReadColorField(L, commandIndex, "color", GuiColor{0.0f, 0.0f, 0.0f, 1.0f});
|
|
||||||
command.borderColor = ReadColorField(L, commandIndex, "borderColor",
|
|
||||||
GuiColor{0.0f, 0.0f, 0.0f, 0.0f});
|
|
||||||
lua_getfield(L, commandIndex, "borderWidth");
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
command.borderWidth = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
} else if (std::strcmp(typeName, "text") == 0) {
|
|
||||||
command.type = GuiCommand::Type::Text;
|
|
||||||
ReadStringField(L, commandIndex, "text", command.text);
|
|
||||||
bool hasX = false;
|
|
||||||
bool hasY = false;
|
|
||||||
lua_getfield(L, commandIndex, "x");
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
command.rect.x = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
hasX = true;
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
lua_getfield(L, commandIndex, "y");
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
command.rect.y = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
hasY = true;
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_ && (!hasX || !hasY)) {
|
|
||||||
logger_->Trace("GuiScriptService", "LoadGuiCommands",
|
|
||||||
"Text command missing x/y; defaulting to 0");
|
|
||||||
}
|
|
||||||
lua_getfield(L, commandIndex, "fontSize");
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
command.fontSize = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
std::string align;
|
|
||||||
if (ReadStringField(L, commandIndex, "alignX", align)) {
|
|
||||||
command.alignX = align;
|
|
||||||
}
|
|
||||||
if (ReadStringField(L, commandIndex, "alignY", align)) {
|
|
||||||
command.alignY = align;
|
|
||||||
}
|
|
||||||
lua_getfield(L, commandIndex, "clipRect");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
command.clipRect = ReadRect(L, -1);
|
|
||||||
command.hasClipRect = true;
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
lua_getfield(L, commandIndex, "bounds");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
command.bounds = ReadRect(L, -1);
|
|
||||||
command.hasBounds = true;
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
command.color = ReadColorField(L, commandIndex, "color", GuiColor{1.0f, 1.0f, 1.0f, 1.0f});
|
|
||||||
} else if (std::strcmp(typeName, "clip_push") == 0) {
|
|
||||||
command.type = GuiCommand::Type::ClipPush;
|
|
||||||
lua_getfield(L, commandIndex, "rect");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
command.rect = ReadRect(L, -1);
|
|
||||||
} else {
|
|
||||||
command.rect = ReadRect(L, commandIndex);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "LoadGuiCommands",
|
|
||||||
"clipPushFallback=true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
} else if (std::strcmp(typeName, "clip_pop") == 0) {
|
|
||||||
command.type = GuiCommand::Type::ClipPop;
|
|
||||||
} else if (std::strcmp(typeName, "svg") == 0) {
|
|
||||||
command.type = GuiCommand::Type::Svg;
|
|
||||||
ReadStringField(L, commandIndex, "path", command.svgPath);
|
|
||||||
command.rect = ReadRect(L, commandIndex);
|
|
||||||
command.svgTint = ReadColorField(L, commandIndex, "tint", GuiColor{1.0f, 1.0f, 1.0f, 0.0f});
|
|
||||||
command.svgTint = ReadColorField(L, commandIndex, "color", command.svgTint);
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
commands.push_back(std::move(command));
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GuiScriptService::UpdateGuiInput(const GuiInputSnapshot& input) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "UpdateGuiInput",
|
|
||||||
"mouseX=" + std::to_string(input.mouseX) +
|
|
||||||
", mouseY=" + std::to_string(input.mouseY) +
|
|
||||||
", mouseDeltaX=" + std::to_string(input.mouseDeltaX) +
|
|
||||||
", mouseDeltaY=" + std::to_string(input.mouseDeltaY) +
|
|
||||||
", mouseDown=" + std::string(input.mouseDown ? "true" : "false") +
|
|
||||||
", wheel=" + std::to_string(input.wheel) +
|
|
||||||
", textInput.size=" + std::to_string(input.textInput.size()) +
|
|
||||||
", keyStates.size=" + std::to_string(input.keyStates.size()));
|
|
||||||
}
|
|
||||||
if (guiInputRef_ < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, guiInputRef_);
|
|
||||||
int stateIndex = lua_gettop(L);
|
|
||||||
|
|
||||||
lua_getfield(L, stateIndex, "resetTransient");
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_call(L, 1, 0);
|
|
||||||
|
|
||||||
lua_getfield(L, stateIndex, "setMouse");
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_pushnumber(L, input.mouseX);
|
|
||||||
lua_pushnumber(L, input.mouseY);
|
|
||||||
lua_pushboolean(L, input.mouseDown);
|
|
||||||
lua_pushnumber(L, input.mouseDeltaX);
|
|
||||||
lua_pushnumber(L, input.mouseDeltaY);
|
|
||||||
lua_call(L, 6, 0);
|
|
||||||
|
|
||||||
lua_getfield(L, stateIndex, "setWheel");
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_pushnumber(L, input.wheel);
|
|
||||||
lua_call(L, 2, 0);
|
|
||||||
|
|
||||||
lua_getfield(L, stateIndex, "setGamepad");
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_pushboolean(L, input.gamepadConnected);
|
|
||||||
lua_pushnumber(L, input.gamepadLeftX);
|
|
||||||
lua_pushnumber(L, input.gamepadLeftY);
|
|
||||||
lua_pushnumber(L, input.gamepadRightX);
|
|
||||||
lua_pushnumber(L, input.gamepadRightY);
|
|
||||||
lua_pushboolean(L, input.gamepadTogglePressed);
|
|
||||||
lua_call(L, 7, 0);
|
|
||||||
} else {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.textInput.empty()) {
|
|
||||||
lua_getfield(L, stateIndex, "addTextInput");
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_pushstring(L, input.textInput.c_str());
|
|
||||||
lua_call(L, 2, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& [key, pressed] : input.keyStates) {
|
|
||||||
lua_getfield(L, stateIndex, "setKey");
|
|
||||||
lua_pushvalue(L, stateIndex);
|
|
||||||
lua_pushstring(L, key.c_str());
|
|
||||||
lua_pushboolean(L, pressed);
|
|
||||||
lua_call(L, 3, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuiScriptService::HasGuiCommands() const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "HasGuiCommands");
|
|
||||||
}
|
|
||||||
return guiCommandsFnRef_ >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GuiCommand::RectData GuiScriptService::ReadRect(lua_State* L, int index) const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "ReadRect",
|
|
||||||
"index=" + std::to_string(index));
|
|
||||||
}
|
|
||||||
GuiCommand::RectData rect{};
|
|
||||||
if (!lua_istable(L, index)) {
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
auto readField = [&](const char* name, float defaultValue) -> float {
|
|
||||||
lua_getfield(L, absIndex, name);
|
|
||||||
float value = defaultValue;
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
value = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
rect.x = readField("x", rect.x);
|
|
||||||
rect.y = readField("y", rect.y);
|
|
||||||
rect.width = readField("width", rect.width);
|
|
||||||
rect.height = readField("height", rect.height);
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
GuiColor GuiScriptService::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "ReadColor",
|
|
||||||
"index=" + std::to_string(index));
|
|
||||||
}
|
|
||||||
GuiColor color = defaultColor;
|
|
||||||
if (!lua_istable(L, index)) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
for (int component = 0; component < 4; ++component) {
|
|
||||||
lua_rawgeti(L, absIndex, component + 1);
|
|
||||||
if (lua_isnumber(L, -1)) {
|
|
||||||
float value = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
switch (component) {
|
|
||||||
case 0: color.r = value; break;
|
|
||||||
case 1: color.g = value; break;
|
|
||||||
case 2: color.b = value; break;
|
|
||||||
case 3: color.a = value; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
GuiColor GuiScriptService::ReadColorField(lua_State* L, int index, const char* name,
|
|
||||||
const GuiColor& defaultColor) const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "ReadColorField",
|
|
||||||
"index=" + std::to_string(index) +
|
|
||||||
", name=" + std::string(name ? name : ""));
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, index) || !name) {
|
|
||||||
return defaultColor;
|
|
||||||
}
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
lua_getfield(L, absIndex, name);
|
|
||||||
GuiColor color = defaultColor;
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
color = ReadColor(L, -1, defaultColor);
|
|
||||||
} else if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "ReadColorField",
|
|
||||||
"Field not found or not table: " + std::string(name));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuiScriptService::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "ReadStringField",
|
|
||||||
"index=" + std::to_string(index) +
|
|
||||||
", name=" + std::string(name ? name : ""));
|
|
||||||
}
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
lua_getfield(L, absIndex, name);
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
outString = lua_tostring(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_State* GuiScriptService::GetLuaState() const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("GuiScriptService", "GetLuaState");
|
|
||||||
}
|
|
||||||
if (!engineService_) {
|
|
||||||
throw std::runtime_error("GUI script service is missing script engine service");
|
|
||||||
}
|
|
||||||
lua_State* state = engineService_->GetLuaState();
|
|
||||||
if (!state) {
|
|
||||||
throw std::runtime_error("Lua state is not initialized");
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "services/interfaces/i_gui_script_service.hpp"
|
|
||||||
#include "services/interfaces/i_script_engine_service.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
#include "../../../di/lifecycle.hpp"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing GUI service implementation.
|
|
||||||
*/
|
|
||||||
class GuiScriptService : public IGuiScriptService,
|
|
||||||
public di::IInitializable,
|
|
||||||
public di::IShutdownable {
|
|
||||||
public:
|
|
||||||
GuiScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<ILogger> logger);
|
|
||||||
|
|
||||||
void Initialize() override;
|
|
||||||
void Shutdown() noexcept override;
|
|
||||||
|
|
||||||
std::vector<GuiCommand> LoadGuiCommands() override;
|
|
||||||
void UpdateGuiInput(const GuiInputSnapshot& input) override;
|
|
||||||
bool HasGuiCommands() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
lua_State* GetLuaState() const;
|
|
||||||
GuiCommand::RectData ReadRect(lua_State* L, int index) const;
|
|
||||||
float ReadNumberField(lua_State* L, int index, const char* name, float defaultValue) const;
|
|
||||||
GuiColor ReadColor(lua_State* L, int index, const GuiColor& defaultColor) const;
|
|
||||||
GuiColor ReadColorField(lua_State* L, int index, const char* name, const GuiColor& defaultColor) const;
|
|
||||||
bool ReadStringField(lua_State* L, int index, const char* name, std::string& outString) const;
|
|
||||||
|
|
||||||
std::shared_ptr<IScriptEngineService> engineService_;
|
|
||||||
std::shared_ptr<ILogger> logger_;
|
|
||||||
int guiInputRef_ = -1;
|
|
||||||
int guiCommandsFnRef_ = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#include "lua_helpers.hpp"
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
|
||||||
#include <lua.hpp>
|
|
||||||
#include <cmath>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl::lua {
|
|
||||||
|
|
||||||
std::array<float, 3> ReadVector3(lua_State* L, int index) {
|
|
||||||
std::array<float, 3> result{};
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
size_t len = lua_rawlen(L, absIndex);
|
|
||||||
if (len != 3) {
|
|
||||||
throw std::runtime_error("Expected vector with 3 components");
|
|
||||||
}
|
|
||||||
for (size_t i = 1; i <= 3; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_isnumber(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error("Vector component is not a number");
|
|
||||||
}
|
|
||||||
result[i - 1] = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 2> ReadVector2(lua_State* L, int index) {
|
|
||||||
std::array<float, 2> result{};
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
size_t len = lua_rawlen(L, absIndex);
|
|
||||||
if (len != 2) {
|
|
||||||
throw std::runtime_error("Expected vector with 2 components");
|
|
||||||
}
|
|
||||||
for (size_t i = 1; i <= 2; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_isnumber(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error("Vector component is not a number");
|
|
||||||
}
|
|
||||||
result[i - 1] = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 4> ReadQuaternion(lua_State* L, int index) {
|
|
||||||
std::array<float, 4> result{};
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
size_t len = lua_rawlen(L, absIndex);
|
|
||||||
if (len != 4) {
|
|
||||||
throw std::runtime_error("Expected quaternion with 4 components");
|
|
||||||
}
|
|
||||||
for (size_t i = 1; i <= 4; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_isnumber(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error("Quaternion component is not a number");
|
|
||||||
}
|
|
||||||
result[i - 1] = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 16> ReadMatrix(lua_State* L, int index) {
|
|
||||||
std::array<float, 16> result{};
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
size_t len = lua_rawlen(L, absIndex);
|
|
||||||
if (len != 16) {
|
|
||||||
throw std::runtime_error("Expected 4x4 matrix with 16 components");
|
|
||||||
}
|
|
||||||
for (size_t i = 1; i <= 16; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_isnumber(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error("Matrix component is not a number");
|
|
||||||
}
|
|
||||||
result[i - 1] = static_cast<float>(lua_tonumber(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetLuaError(lua_State* L) {
|
|
||||||
const char* message = lua_tostring(L, -1);
|
|
||||||
return message ? message : "unknown lua error";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 16> IdentityMatrix() {
|
|
||||||
return {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};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
glm::vec3 ToVec3(const std::array<float, 3>& value) {
|
|
||||||
return glm::vec3(value[0], value[1], value[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::quat ToQuat(const std::array<float, 4>& value) {
|
|
||||||
return glm::quat(value[3], value[0], value[1], value[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PushMatrix(lua_State* L, const glm::mat4& matrix) {
|
|
||||||
lua_newtable(L);
|
|
||||||
const float* ptr = glm::value_ptr(matrix);
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
|
||||||
lua_pushnumber(L, ptr[i]);
|
|
||||||
lua_rawseti(L, -2, i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int LuaGlmMatrixIdentity(lua_State* L) {
|
|
||||||
glm::mat4 matrix(1.0f);
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixMultiply(lua_State* L) {
|
|
||||||
std::array<float, 16> left = ReadMatrix(L, 1);
|
|
||||||
std::array<float, 16> right = ReadMatrix(L, 2);
|
|
||||||
glm::mat4 leftMat = glm::make_mat4(left.data());
|
|
||||||
glm::mat4 rightMat = glm::make_mat4(right.data());
|
|
||||||
glm::mat4 combined = leftMat * rightMat;
|
|
||||||
PushMatrix(L, combined);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixTranslation(lua_State* L) {
|
|
||||||
float x = static_cast<float>(luaL_checknumber(L, 1));
|
|
||||||
float y = static_cast<float>(luaL_checknumber(L, 2));
|
|
||||||
float z = static_cast<float>(luaL_checknumber(L, 3));
|
|
||||||
glm::mat4 matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z));
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixRotationX(lua_State* L) {
|
|
||||||
float radians = static_cast<float>(luaL_checknumber(L, 1));
|
|
||||||
glm::mat4 matrix = glm::rotate(glm::mat4(1.0f), -radians, glm::vec3(1.0f, 0.0f, 0.0f));
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixRotationY(lua_State* L) {
|
|
||||||
float radians = static_cast<float>(luaL_checknumber(L, 1));
|
|
||||||
glm::mat4 matrix = glm::rotate(glm::mat4(1.0f), -radians, glm::vec3(0.0f, 1.0f, 0.0f));
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixLookAt(lua_State* L) {
|
|
||||||
std::array<float, 3> eye = ReadVector3(L, 1);
|
|
||||||
std::array<float, 3> center = ReadVector3(L, 2);
|
|
||||||
std::array<float, 3> up = ReadVector3(L, 3);
|
|
||||||
glm::mat4 matrix = glm::lookAt(ToVec3(eye), ToVec3(center), ToVec3(up));
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixPerspective(lua_State* L) {
|
|
||||||
float fov = static_cast<float>(luaL_checknumber(L, 1));
|
|
||||||
float aspect = static_cast<float>(luaL_checknumber(L, 2));
|
|
||||||
float zNear = static_cast<float>(luaL_checknumber(L, 3));
|
|
||||||
float zFar = static_cast<float>(luaL_checknumber(L, 4));
|
|
||||||
float tanHalf = std::tan(fov * 0.5f);
|
|
||||||
glm::mat4 matrix(0.0f);
|
|
||||||
matrix[0][0] = 1.0f / (aspect * tanHalf);
|
|
||||||
matrix[1][1] = -1.0f / tanHalf;
|
|
||||||
matrix[2][2] = zFar / (zNear - zFar);
|
|
||||||
matrix[2][3] = -1.0f;
|
|
||||||
matrix[3][2] = (zNear * zFar) / (zNear - zFar);
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaGlmMatrixFromTransform(lua_State* L) {
|
|
||||||
std::array<float, 3> translation = ReadVector3(L, 1);
|
|
||||||
std::array<float, 4> rotation = ReadQuaternion(L, 2);
|
|
||||||
glm::vec3 pos = ToVec3(translation);
|
|
||||||
glm::quat quat = ToQuat(rotation);
|
|
||||||
glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat);
|
|
||||||
PushMatrix(L, matrix);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl::lua
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl::lua {
|
|
||||||
|
|
||||||
std::array<float, 3> ReadVector3(lua_State* L, int index);
|
|
||||||
std::array<float, 2> ReadVector2(lua_State* L, int index);
|
|
||||||
std::array<float, 4> ReadQuaternion(lua_State* L, int index);
|
|
||||||
std::array<float, 16> ReadMatrix(lua_State* L, int index);
|
|
||||||
std::string GetLuaError(lua_State* L);
|
|
||||||
std::array<float, 16> IdentityMatrix();
|
|
||||||
int LuaGlmMatrixIdentity(lua_State* L);
|
|
||||||
int LuaGlmMatrixMultiply(lua_State* L);
|
|
||||||
int LuaGlmMatrixTranslation(lua_State* L);
|
|
||||||
int LuaGlmMatrixRotationX(lua_State* L);
|
|
||||||
int LuaGlmMatrixRotationY(lua_State* L);
|
|
||||||
int LuaGlmMatrixLookAt(lua_State* L);
|
|
||||||
int LuaGlmMatrixPerspective(lua_State* L);
|
|
||||||
int LuaGlmMatrixFromTransform(lua_State* L);
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl::lua
|
|
||||||
@@ -1,428 +0,0 @@
|
|||||||
#include "scene_script_service.hpp"
|
|
||||||
|
|
||||||
#include "lua_helpers.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
|
|
||||||
#include <lua.hpp>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::array<float, 16> MultiplyMatrices(const std::array<float, 16>& left,
|
|
||||||
const std::array<float, 16>& right) {
|
|
||||||
glm::mat4 leftMat = glm::make_mat4(left.data());
|
|
||||||
glm::mat4 rightMat = glm::make_mat4(right.data());
|
|
||||||
glm::mat4 combined = leftMat * rightMat;
|
|
||||||
std::array<float, 16> result{};
|
|
||||||
std::memcpy(result.data(), glm::value_ptr(combined), sizeof(float) * result.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReadMatrixField(lua_State* L, int tableIndex, const char* field, std::array<float, 16>& target) {
|
|
||||||
lua_getfield(L, tableIndex, field);
|
|
||||||
if (lua_isnil(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error(std::string("Field '") + field + "' must be a 4x4 matrix");
|
|
||||||
}
|
|
||||||
target = lua::ReadMatrix(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReadVector3Field(lua_State* L, int tableIndex, const char* field, std::array<float, 3>& target) {
|
|
||||||
lua_getfield(L, tableIndex, field);
|
|
||||||
if (lua_isnil(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
throw std::runtime_error(std::string("Field '") + field + "' must be a vec3");
|
|
||||||
}
|
|
||||||
target = lua::ReadVector3(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<core::Vertex> ReadVertexArray(lua_State* L, int index, const std::shared_ptr<ILogger>& logger) {
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
if (!lua_istable(L, absIndex)) {
|
|
||||||
if (logger) {
|
|
||||||
logger->Error("Expected table for vertex data");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected table for vertex data");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = lua_rawlen(L, absIndex);
|
|
||||||
std::vector<core::Vertex> vertices;
|
|
||||||
vertices.reserve(count);
|
|
||||||
|
|
||||||
for (size_t i = 1; i <= count; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger) {
|
|
||||||
logger->Error("Vertex entry at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
int vertexIndex = lua_gettop(L);
|
|
||||||
core::Vertex vertex{};
|
|
||||||
|
|
||||||
lua_getfield(L, vertexIndex, "position");
|
|
||||||
vertex.position = lua::ReadVector3(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, vertexIndex, "normal");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
vertex.normal = lua::ReadVector3(L, -1);
|
|
||||||
} else {
|
|
||||||
vertex.normal = {0.0f, 0.0f, 1.0f};
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, vertexIndex, "tangent");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
vertex.tangent = lua::ReadVector3(L, -1);
|
|
||||||
} else {
|
|
||||||
vertex.tangent = {1.0f, 0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, vertexIndex, "color");
|
|
||||||
vertex.color = lua::ReadVector3(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_getfield(L, vertexIndex, "texcoord");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
vertex.texcoord = lua::ReadVector2(L, -1);
|
|
||||||
} else {
|
|
||||||
vertex.texcoord = {0.0f, 0.0f};
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
vertices.push_back(vertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return vertices;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint16_t> ReadIndexArray(lua_State* L, int index, const std::shared_ptr<ILogger>& logger) {
|
|
||||||
int absIndex = lua_absindex(L, index);
|
|
||||||
if (!lua_istable(L, absIndex)) {
|
|
||||||
if (logger) {
|
|
||||||
logger->Error("Expected table for index data");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Expected table for index data");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = lua_rawlen(L, absIndex);
|
|
||||||
std::vector<uint16_t> indices;
|
|
||||||
indices.reserve(count);
|
|
||||||
|
|
||||||
for (size_t i = 1; i <= count; ++i) {
|
|
||||||
lua_rawgeti(L, absIndex, static_cast<int>(i));
|
|
||||||
if (!lua_isinteger(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger) {
|
|
||||||
logger->Error("Index entry at position " + std::to_string(i) + " is not an integer");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer");
|
|
||||||
}
|
|
||||||
lua_Integer value = lua_tointeger(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (value < 1) {
|
|
||||||
if (logger) {
|
|
||||||
logger->Error("Index values must be 1 or greater");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Index values must be 1 or greater");
|
|
||||||
}
|
|
||||||
indices.push_back(static_cast<uint16_t>(value - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
SceneScriptService::SceneScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<ILogger> logger)
|
|
||||||
: engineService_(std::move(engineService)),
|
|
||||||
logger_(std::move(logger)) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("SceneScriptService", "SceneScriptService",
|
|
||||||
"engineService=" + std::string(engineService_ ? "set" : "null"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<SceneObject> SceneScriptService::LoadSceneObjects() {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("SceneScriptService", "LoadSceneObjects");
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
lua_getglobal(L, "get_scene_objects");
|
|
||||||
if (!lua_isfunction(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua function 'get_scene_objects' is missing");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua function 'get_scene_objects' is missing");
|
|
||||||
}
|
|
||||||
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
|
|
||||||
std::string message = lua::GetLuaError(L);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua get_scene_objects failed: " + message);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua get_scene_objects failed: " + message);
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("'get_scene_objects' did not return a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("'get_scene_objects' did not return a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = lua_rawlen(L, -1);
|
|
||||||
std::vector<SceneObject> objects;
|
|
||||||
objects.reserve(count);
|
|
||||||
|
|
||||||
for (size_t i = 1; i <= count; ++i) {
|
|
||||||
lua_rawgeti(L, -1, static_cast<int>(i));
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Scene object at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneObject object;
|
|
||||||
lua_getfield(L, -1, "vertices");
|
|
||||||
object.vertices = ReadVertexArray(L, -1, logger_);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (object.vertices.empty()) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Scene object " + std::to_string(i) + " must supply at least one vertex");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex");
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_getfield(L, -1, "indices");
|
|
||||||
object.indices = ReadIndexArray(L, -1, logger_);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (object.indices.empty()) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Scene object " + std::to_string(i) + " must supply indices");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices");
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_getfield(L, -1, "compute_model_matrix");
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
object.computeModelMatrixRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
||||||
} else {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
object.computeModelMatrixRef = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
object.shaderKeys.clear();
|
|
||||||
lua_getfield(L, -1, "shader_keys");
|
|
||||||
if (lua_istable(L, -1)) {
|
|
||||||
const size_t shaderKeyCount = lua_rawlen(L, -1);
|
|
||||||
object.shaderKeys.reserve(shaderKeyCount);
|
|
||||||
for (size_t keyIndex = 1; keyIndex <= shaderKeyCount; ++keyIndex) {
|
|
||||||
lua_rawgeti(L, -1, static_cast<int>(keyIndex));
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
object.shaderKeys.emplace_back(lua_tostring(L, -1));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
if (object.shaderKeys.empty()) {
|
|
||||||
lua_getfield(L, -1, "shader_key");
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
object.shaderKeys.emplace_back(lua_tostring(L, -1));
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_getfield(L, -1, "object_type");
|
|
||||||
if (lua_isstring(L, -1)) {
|
|
||||||
object.objectType = lua_tostring(L, -1);
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
objects.push_back(std::move(object));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 16> SceneScriptService::ComputeModelMatrix(int functionRef, float time) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("SceneScriptService", "ComputeModelMatrix",
|
|
||||||
"functionRef=" + std::to_string(functionRef) +
|
|
||||||
", time=" + std::to_string(time));
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
if (functionRef < 0) {
|
|
||||||
lua_getglobal(L, "compute_model_matrix");
|
|
||||||
if (!lua_isfunction(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return lua::IdentityMatrix();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushnumber(L, time);
|
|
||||||
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
|
|
||||||
std::string message = lua::GetLuaError(L);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua compute_model_matrix failed: " + message);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua compute_model_matrix failed: " + message);
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("'compute_model_matrix' did not return a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("'compute_model_matrix' did not return a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 16> matrix = lua::ReadMatrix(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewState SceneScriptService::GetViewState(float aspect) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("SceneScriptService", "GetViewState", "aspect=" + std::to_string(aspect));
|
|
||||||
}
|
|
||||||
lua_State* L = GetLuaState();
|
|
||||||
|
|
||||||
ViewState state;
|
|
||||||
state.view = lua::IdentityMatrix();
|
|
||||||
state.proj = lua::IdentityMatrix();
|
|
||||||
state.viewProj = lua::IdentityMatrix();
|
|
||||||
state.cameraPosition = {0.0f, 0.0f, 0.0f};
|
|
||||||
|
|
||||||
lua_getglobal(L, "get_view_state");
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
lua_pushnumber(L, aspect);
|
|
||||||
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
|
|
||||||
std::string message = lua::GetLuaError(L);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua get_view_state failed: " + message);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua get_view_state failed: " + message);
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("'get_view_state' did not return a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("'get_view_state' did not return a table");
|
|
||||||
}
|
|
||||||
bool hasView = false;
|
|
||||||
bool hasProj = false;
|
|
||||||
bool hasViewProj = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
hasView = ReadMatrixField(L, -1, "view", state.view);
|
|
||||||
hasProj = ReadMatrixField(L, -1, "proj", state.proj);
|
|
||||||
hasViewProj = ReadMatrixField(L, -1, "view_proj", state.viewProj);
|
|
||||||
if (!hasViewProj) {
|
|
||||||
hasViewProj = ReadMatrixField(L, -1, "viewProj", state.viewProj);
|
|
||||||
}
|
|
||||||
ReadVector3Field(L, -1, "camera_pos", state.cameraPosition);
|
|
||||||
ReadVector3Field(L, -1, "camera_position", state.cameraPosition);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua get_view_state returned invalid data: " + std::string(ex.what()));
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
if (!hasViewProj && hasView && hasProj) {
|
|
||||||
state.viewProj = MultiplyMatrices(state.proj, state.view);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
lua_getglobal(L, "get_view_projection");
|
|
||||||
if (!lua_isfunction(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua function 'get_view_projection' is missing");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua function 'get_view_projection' is missing");
|
|
||||||
}
|
|
||||||
lua_pushnumber(L, aspect);
|
|
||||||
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
|
|
||||||
std::string message = lua::GetLuaError(L);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("Lua get_view_projection failed: " + message);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("Lua get_view_projection failed: " + message);
|
|
||||||
}
|
|
||||||
if (!lua_istable(L, -1)) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Error("'get_view_projection' did not return a table");
|
|
||||||
}
|
|
||||||
throw std::runtime_error("'get_view_projection' did not return a table");
|
|
||||||
}
|
|
||||||
state.viewProj = lua::ReadMatrix(L, -1);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_State* SceneScriptService::GetLuaState() const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("SceneScriptService", "GetLuaState");
|
|
||||||
}
|
|
||||||
if (!engineService_) {
|
|
||||||
throw std::runtime_error("Scene script service is missing script engine service");
|
|
||||||
}
|
|
||||||
lua_State* state = engineService_->GetLuaState();
|
|
||||||
if (!state) {
|
|
||||||
throw std::runtime_error("Lua state is not initialized");
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "services/interfaces/i_scene_script_service.hpp"
|
|
||||||
#include "services/interfaces/i_script_engine_service.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing scene service implementation.
|
|
||||||
*/
|
|
||||||
class SceneScriptService : public ISceneScriptService {
|
|
||||||
public:
|
|
||||||
SceneScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<ILogger> logger);
|
|
||||||
|
|
||||||
std::vector<SceneObject> LoadSceneObjects() override;
|
|
||||||
std::array<float, 16> ComputeModelMatrix(int functionRef, float time) override;
|
|
||||||
ViewState GetViewState(float aspect) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
lua_State* GetLuaState() const;
|
|
||||||
|
|
||||||
std::shared_ptr<IScriptEngineService> engineService_;
|
|
||||||
std::shared_ptr<ILogger> logger_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,127 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "services/interfaces/i_script_engine_service.hpp"
|
|
||||||
#include "services/interfaces/i_audio_command_service.hpp"
|
|
||||||
#include "services/interfaces/i_mesh_service.hpp"
|
|
||||||
#include "services/interfaces/i_physics_bridge_service.hpp"
|
|
||||||
#include "services/interfaces/i_input_service.hpp"
|
|
||||||
#include "services/interfaces/i_window_service.hpp"
|
|
||||||
#include "services/interfaces/i_config_service.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
#include "../../../di/lifecycle.hpp"
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Service that owns the Lua runtime and script bindings.
|
|
||||||
*/
|
|
||||||
class ScriptEngineService : public IScriptEngineService,
|
|
||||||
public di::IInitializable,
|
|
||||||
public di::IShutdownable {
|
|
||||||
public:
|
|
||||||
ScriptEngineService(const std::filesystem::path& scriptPath,
|
|
||||||
std::shared_ptr<ILogger> logger,
|
|
||||||
std::shared_ptr<IMeshService> meshService,
|
|
||||||
std::shared_ptr<IAudioCommandService> audioCommandService,
|
|
||||||
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService,
|
|
||||||
std::shared_ptr<IInputService> inputService,
|
|
||||||
std::shared_ptr<IWindowService> windowService,
|
|
||||||
std::shared_ptr<IConfigService> configService,
|
|
||||||
bool debugEnabled = false);
|
|
||||||
~ScriptEngineService() override;
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
void Initialize() override;
|
|
||||||
void Shutdown() noexcept override;
|
|
||||||
|
|
||||||
// IScriptEngineService interface
|
|
||||||
lua_State* GetLuaState() const override;
|
|
||||||
std::filesystem::path GetScriptDirectory() const override;
|
|
||||||
bool IsInitialized() const override {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("ScriptEngineService", "IsInitialized");
|
|
||||||
}
|
|
||||||
return initialized_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct LuaBindingContext {
|
|
||||||
std::shared_ptr<IMeshService> meshService;
|
|
||||||
std::shared_ptr<IAudioCommandService> audioCommandService;
|
|
||||||
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService;
|
|
||||||
std::shared_ptr<IInputService> inputService;
|
|
||||||
std::shared_ptr<IWindowService> windowService;
|
|
||||||
std::shared_ptr<IConfigService> configService;
|
|
||||||
std::shared_ptr<ILogger> logger;
|
|
||||||
};
|
|
||||||
|
|
||||||
void RegisterBindings(lua_State* L);
|
|
||||||
static int LoadMeshFromFile(lua_State* L);
|
|
||||||
static int LoadMeshFromArchive(lua_State* L);
|
|
||||||
static int PhysicsCreateBox(lua_State* L);
|
|
||||||
static int PhysicsCreateSphere(lua_State* L);
|
|
||||||
static int PhysicsCreateStaticMesh(lua_State* L);
|
|
||||||
static int PhysicsRemoveBody(lua_State* L);
|
|
||||||
static int PhysicsSetTransform(lua_State* L);
|
|
||||||
static int PhysicsApplyForce(lua_State* L);
|
|
||||||
static int PhysicsApplyImpulse(lua_State* L);
|
|
||||||
static int PhysicsSetLinearVelocity(lua_State* L);
|
|
||||||
static int PhysicsGetLinearVelocity(lua_State* L);
|
|
||||||
static int PhysicsSetGravity(lua_State* L);
|
|
||||||
static int PhysicsStepSimulation(lua_State* L);
|
|
||||||
static int PhysicsGetTransform(lua_State* L);
|
|
||||||
static int PhysicsGetBodyCount(lua_State* L);
|
|
||||||
static int PhysicsClear(lua_State* L);
|
|
||||||
static int AudioPlayBackground(lua_State* L);
|
|
||||||
static int AudioPlaySound(lua_State* L);
|
|
||||||
static int AudioStopBackground(lua_State* L);
|
|
||||||
static int GlmMatrixIdentity(lua_State* L);
|
|
||||||
static int GlmMatrixMultiply(lua_State* L);
|
|
||||||
static int GlmMatrixTranslation(lua_State* L);
|
|
||||||
static int GlmMatrixRotationX(lua_State* L);
|
|
||||||
static int GlmMatrixRotationY(lua_State* L);
|
|
||||||
static int GlmMatrixLookAt(lua_State* L);
|
|
||||||
static int GlmMatrixPerspective(lua_State* L);
|
|
||||||
static int GlmMatrixFromTransform(lua_State* L);
|
|
||||||
static int InputGetMousePosition(lua_State* L);
|
|
||||||
static int InputGetMouseDelta(lua_State* L);
|
|
||||||
static int InputGetMouseWheel(lua_State* L);
|
|
||||||
static int InputIsKeyDown(lua_State* L);
|
|
||||||
static int InputIsActionDown(lua_State* L);
|
|
||||||
static int InputIsMouseDown(lua_State* L);
|
|
||||||
static int InputGetText(lua_State* L);
|
|
||||||
static int ConfigGetJson(lua_State* L);
|
|
||||||
static int ConfigGetTable(lua_State* L);
|
|
||||||
static int MaterialXGetSurfaceParameters(lua_State* L);
|
|
||||||
static int TimeGetSeconds(lua_State* L);
|
|
||||||
static int WindowGetSize(lua_State* L);
|
|
||||||
static int WindowSetTitle(lua_State* L);
|
|
||||||
static int WindowIsMinimized(lua_State* L);
|
|
||||||
static int WindowSetMouseGrabbed(lua_State* L);
|
|
||||||
static int WindowGetMouseGrabbed(lua_State* L);
|
|
||||||
static int WindowSetRelativeMouseMode(lua_State* L);
|
|
||||||
static int WindowGetRelativeMouseMode(lua_State* L);
|
|
||||||
static int WindowSetCursorVisible(lua_State* L);
|
|
||||||
static int WindowIsCursorVisible(lua_State* L);
|
|
||||||
static int LuaPrint(lua_State* L);
|
|
||||||
|
|
||||||
std::shared_ptr<ILogger> logger_;
|
|
||||||
std::shared_ptr<IMeshService> meshService_;
|
|
||||||
std::shared_ptr<IAudioCommandService> audioCommandService_;
|
|
||||||
std::shared_ptr<IPhysicsBridgeService> physicsBridgeService_;
|
|
||||||
std::shared_ptr<IInputService> inputService_;
|
|
||||||
std::shared_ptr<IWindowService> windowService_;
|
|
||||||
std::shared_ptr<IConfigService> configService_;
|
|
||||||
std::filesystem::path scriptPath_;
|
|
||||||
std::filesystem::path scriptDirectory_;
|
|
||||||
bool debugEnabled_ = false;
|
|
||||||
bool initialized_ = false;
|
|
||||||
lua_State* luaState_ = nullptr;
|
|
||||||
std::shared_ptr<LuaBindingContext> bindingContext_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "shader_script_service.hpp"
|
|
||||||
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
ShaderScriptService::ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry,
|
|
||||||
std::shared_ptr<ILogger> logger)
|
|
||||||
: engineService_(std::move(engineService)),
|
|
||||||
shaderSystemRegistry_(std::move(shaderSystemRegistry)),
|
|
||||||
logger_(std::move(logger)) {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("ShaderScriptService", "ShaderScriptService",
|
|
||||||
"engineService=" + std::string(engineService_ ? "set" : "null") +
|
|
||||||
", shaderSystemRegistry=" + std::string(shaderSystemRegistry_ ? "set" : "null"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string, ShaderPaths> ShaderScriptService::LoadShaderPathsMap() {
|
|
||||||
if (!shaderSystemRegistry_) {
|
|
||||||
throw std::runtime_error("Shader script service requires a shader system registry");
|
|
||||||
}
|
|
||||||
return shaderSystemRegistry_->BuildShaderMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_State* ShaderScriptService::GetLuaState() const {
|
|
||||||
if (logger_) {
|
|
||||||
logger_->Trace("ShaderScriptService", "GetLuaState");
|
|
||||||
}
|
|
||||||
if (!engineService_) {
|
|
||||||
throw std::runtime_error("Shader script service is missing script engine service");
|
|
||||||
}
|
|
||||||
lua_State* state = engineService_->GetLuaState();
|
|
||||||
if (!state) {
|
|
||||||
throw std::runtime_error("Lua state is not initialized");
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "services/interfaces/i_shader_script_service.hpp"
|
|
||||||
#include "services/interfaces/i_script_engine_service.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
#include "services/interfaces/i_shader_system_registry.hpp"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services::impl {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing shader service implementation.
|
|
||||||
*/
|
|
||||||
class ShaderScriptService : public IShaderScriptService {
|
|
||||||
public:
|
|
||||||
ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
|
|
||||||
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry,
|
|
||||||
std::shared_ptr<ILogger> logger);
|
|
||||||
|
|
||||||
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
lua_State* GetLuaState() const;
|
|
||||||
ShaderPaths ReadShaderPathsTable(lua_State* L, int index) const;
|
|
||||||
std::string ResolveShaderPath(const std::string& path) const;
|
|
||||||
|
|
||||||
std::shared_ptr<IScriptEngineService> engineService_;
|
|
||||||
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry_;
|
|
||||||
std::shared_ptr<ILogger> logger_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services::impl
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "gui_types.hpp"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing GUI command service interface.
|
|
||||||
*/
|
|
||||||
class IGuiScriptService {
|
|
||||||
public:
|
|
||||||
virtual ~IGuiScriptService() = default;
|
|
||||||
|
|
||||||
virtual std::vector<GuiCommand> LoadGuiCommands() = 0;
|
|
||||||
virtual void UpdateGuiInput(const GuiInputSnapshot& input) = 0;
|
|
||||||
virtual bool HasGuiCommands() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "scene_types.hpp"
|
|
||||||
#include "graphics_types.hpp"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing scene service interface.
|
|
||||||
*/
|
|
||||||
class ISceneScriptService {
|
|
||||||
public:
|
|
||||||
virtual ~ISceneScriptService() = default;
|
|
||||||
|
|
||||||
virtual std::vector<SceneObject> LoadSceneObjects() = 0;
|
|
||||||
virtual std::array<float, 16> ComputeModelMatrix(int functionRef, float time) = 0;
|
|
||||||
virtual ViewState GetViewState(float aspect) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
struct lua_State;
|
|
||||||
|
|
||||||
namespace sdl3cpp::services {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Service for owning and exposing the Lua script engine.
|
|
||||||
*/
|
|
||||||
class IScriptEngineService {
|
|
||||||
public:
|
|
||||||
virtual ~IScriptEngineService() = default;
|
|
||||||
|
|
||||||
virtual lua_State* GetLuaState() const = 0;
|
|
||||||
virtual std::filesystem::path GetScriptDirectory() const = 0;
|
|
||||||
virtual bool IsInitialized() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "graphics_types.hpp"
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace sdl3cpp::services {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Script-facing shader lookup service interface.
|
|
||||||
*/
|
|
||||||
class IShaderScriptService {
|
|
||||||
public:
|
|
||||||
virtual ~IShaderScriptService() = default;
|
|
||||||
|
|
||||||
virtual std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sdl3cpp::services
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "services/impl/script/gui_script_service.hpp"
|
|
||||||
#include "services/interfaces/i_logger.hpp"
|
|
||||||
#include "services/interfaces/i_script_engine_service.hpp"
|
|
||||||
|
|
||||||
#include <lua.hpp>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class CapturingLogger final : public sdl3cpp::services::ILogger {
|
|
||||||
public:
|
|
||||||
void SetLevel(sdl3cpp::services::LogLevel level) override { level_ = level; }
|
|
||||||
sdl3cpp::services::LogLevel GetLevel() const override { return level_; }
|
|
||||||
void SetOutputFile(const std::string&) override {}
|
|
||||||
void SetMaxLinesPerFile(size_t) override {}
|
|
||||||
void EnableConsoleOutput(bool) override {}
|
|
||||||
void Log(sdl3cpp::services::LogLevel, const std::string& message) override {
|
|
||||||
entries_.push_back(message);
|
|
||||||
}
|
|
||||||
void Trace(const std::string& message) override { entries_.push_back(message); }
|
|
||||||
void Trace(const std::string& className,
|
|
||||||
const std::string& methodName,
|
|
||||||
const std::string& args = "",
|
|
||||||
const std::string& message = "") override {
|
|
||||||
std::string formatted = className + "::" + methodName;
|
|
||||||
if (!args.empty()) {
|
|
||||||
formatted += "(" + args + ")";
|
|
||||||
}
|
|
||||||
if (!message.empty()) {
|
|
||||||
formatted += ": " + message;
|
|
||||||
}
|
|
||||||
entries_.push_back(formatted);
|
|
||||||
}
|
|
||||||
void Debug(const std::string& message) override { entries_.push_back(message); }
|
|
||||||
void Info(const std::string& message) override { entries_.push_back(message); }
|
|
||||||
void Warn(const std::string& message) override { entries_.push_back(message); }
|
|
||||||
void Error(const std::string& message) override { entries_.push_back(message); }
|
|
||||||
void TraceFunction(const std::string&) override {}
|
|
||||||
void TraceVariable(const std::string&, const std::string&) override {}
|
|
||||||
void TraceVariable(const std::string&, int) override {}
|
|
||||||
void TraceVariable(const std::string&, size_t) override {}
|
|
||||||
void TraceVariable(const std::string&, bool) override {}
|
|
||||||
void TraceVariable(const std::string&, float) override {}
|
|
||||||
void TraceVariable(const std::string&, double) override {}
|
|
||||||
|
|
||||||
bool HasSubstring(const std::string& fragment) const {
|
|
||||||
for (const auto& entry : entries_) {
|
|
||||||
if (entry.find(fragment) != std::string::npos) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
sdl3cpp::services::LogLevel level_ = sdl3cpp::services::LogLevel::TRACE;
|
|
||||||
std::vector<std::string> entries_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StubScriptEngineService final : public sdl3cpp::services::IScriptEngineService {
|
|
||||||
public:
|
|
||||||
explicit StubScriptEngineService(lua_State* state) : state_(state) {}
|
|
||||||
|
|
||||||
lua_State* GetLuaState() const override { return state_; }
|
|
||||||
std::filesystem::path GetScriptDirectory() const override { return {}; }
|
|
||||||
bool IsInitialized() const override { return state_ != nullptr; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
lua_State* state_;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(GuiScriptServiceMissingFieldsTest, MissingBorderColorDefaultsToTransparent) {
|
|
||||||
std::unique_ptr<lua_State, decltype(&lua_close)> state(luaL_newstate(), &lua_close);
|
|
||||||
ASSERT_NE(state.get(), nullptr);
|
|
||||||
luaL_openlibs(state.get());
|
|
||||||
|
|
||||||
const char* script = R"(
|
|
||||||
function get_gui_commands()
|
|
||||||
return {
|
|
||||||
{
|
|
||||||
type = "rect",
|
|
||||||
x = 10,
|
|
||||||
y = 20,
|
|
||||||
width = 30,
|
|
||||||
height = 40,
|
|
||||||
color = {0.1, 0.2, 0.3, 0.4}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
)";
|
|
||||||
|
|
||||||
ASSERT_EQ(luaL_dostring(state.get(), script), LUA_OK) << lua_tostring(state.get(), -1);
|
|
||||||
|
|
||||||
auto engineService = std::make_shared<StubScriptEngineService>(state.get());
|
|
||||||
auto logger = std::make_shared<CapturingLogger>();
|
|
||||||
sdl3cpp::services::impl::GuiScriptService service(engineService, logger);
|
|
||||||
|
|
||||||
service.Initialize();
|
|
||||||
|
|
||||||
std::vector<sdl3cpp::services::GuiCommand> commands;
|
|
||||||
ASSERT_NO_THROW(commands = service.LoadGuiCommands());
|
|
||||||
ASSERT_EQ(commands.size(), 1u);
|
|
||||||
|
|
||||||
const auto& command = commands.front();
|
|
||||||
EXPECT_EQ(command.type, sdl3cpp::services::GuiCommand::Type::Rect);
|
|
||||||
EXPECT_FLOAT_EQ(command.borderColor.r, 0.0f);
|
|
||||||
EXPECT_FLOAT_EQ(command.borderColor.g, 0.0f);
|
|
||||||
EXPECT_FLOAT_EQ(command.borderColor.b, 0.0f);
|
|
||||||
EXPECT_FLOAT_EQ(command.borderColor.a, 0.0f);
|
|
||||||
EXPECT_TRUE(logger->HasSubstring("Field not found or not table: borderColor"));
|
|
||||||
|
|
||||||
service.Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
@@ -1,875 +0,0 @@
|
|||||||
#include "services/impl/graphics/bgfx_graphics_backend.hpp"
|
|
||||||
#include "services/impl/diagnostics/logger_service.hpp"
|
|
||||||
#include "services/impl/scene/mesh_service.hpp"
|
|
||||||
#include "services/impl/shader/pipeline_compiler_service.hpp"
|
|
||||||
#include "services/impl/scene/physics_bridge_service.hpp"
|
|
||||||
#include "services/impl/platform/platform_service.hpp"
|
|
||||||
#include "services/impl/script/script_engine_service.hpp"
|
|
||||||
#include "services/impl/script/scene_script_service.hpp"
|
|
||||||
#include "services/impl/script/shader_script_service.hpp"
|
|
||||||
#include "services/impl/shader/shader_system_registry.hpp"
|
|
||||||
#include "services/impl/scene/ecs_service.hpp"
|
|
||||||
#include "services/impl/scene/scene_service.hpp"
|
|
||||||
#include "services/impl/platform/sdl_window_service.hpp"
|
|
||||||
#include "services/interfaces/i_audio_command_service.hpp"
|
|
||||||
#include "services/interfaces/i_config_service.hpp"
|
|
||||||
#include "events/event_bus.hpp"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <bgfx/bgfx.h>
|
|
||||||
#include <cmath>
|
|
||||||
#include <limits>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <lua.hpp>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr std::array<float, 16> 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<float, 16>& 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";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetCubeScriptPath() {
|
|
||||||
auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path();
|
|
||||||
return repoRoot / "scripts" / "cube_logic.lua";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path GetSeedConfigPath() {
|
|
||||||
auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path();
|
|
||||||
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) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
std::string contents((std::istreambuf_iterator<char>(input)),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
class StubConfigService final : public sdl3cpp::services::IConfigService {
|
|
||||||
public:
|
|
||||||
StubConfigService() {
|
|
||||||
materialXConfig_.enabled = true;
|
|
||||||
materialXConfig_.useConstantColor = true;
|
|
||||||
materialXConfig_.shaderKey = "test";
|
|
||||||
materialXConfig_.libraryPath = ResolveMaterialXLibraryPath();
|
|
||||||
auto configJson = ReadFileContents(GetSeedConfigPath());
|
|
||||||
if (configJson) {
|
|
||||||
configJson_ = *configJson;
|
|
||||||
} else {
|
|
||||||
configJson_ = "{}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GetWindowWidth() const override { return 1; }
|
|
||||||
uint32_t GetWindowHeight() const override { return 1; }
|
|
||||||
std::filesystem::path GetScriptPath() const override { return {}; }
|
|
||||||
bool IsLuaDebugEnabled() const override { return false; }
|
|
||||||
std::string GetWindowTitle() const override { return ""; }
|
|
||||||
sdl3cpp::services::SceneSource GetSceneSource() const override {
|
|
||||||
return sdl3cpp::services::SceneSource::Lua;
|
|
||||||
}
|
|
||||||
const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; }
|
|
||||||
const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; }
|
|
||||||
const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; }
|
|
||||||
const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; }
|
|
||||||
const std::vector<sdl3cpp::services::MaterialXMaterialConfig>& GetMaterialXMaterialConfigs() const override {
|
|
||||||
return materialXMaterials_;
|
|
||||||
}
|
|
||||||
const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; }
|
|
||||||
const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; }
|
|
||||||
const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; }
|
|
||||||
const sdl3cpp::services::ValidationTourConfig& GetValidationTourConfig() const override {
|
|
||||||
return validationTour_;
|
|
||||||
}
|
|
||||||
const std::string& GetConfigJson() const override { return configJson_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::filesystem::path ResolveMaterialXLibraryPath() {
|
|
||||||
auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path();
|
|
||||||
return repoRoot / "MaterialX" / "libraries";
|
|
||||||
}
|
|
||||||
|
|
||||||
sdl3cpp::services::InputBindings inputBindings_{};
|
|
||||||
sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{};
|
|
||||||
sdl3cpp::services::BgfxConfig bgfxConfig_{};
|
|
||||||
sdl3cpp::services::MaterialXConfig materialXConfig_{};
|
|
||||||
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
|
|
||||||
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
|
|
||||||
sdl3cpp::services::RenderBudgetConfig budgets_{};
|
|
||||||
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
|
|
||||||
sdl3cpp::services::ValidationTourConfig validationTour_{};
|
|
||||||
std::string configJson_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class CubeDemoConfigService final : public sdl3cpp::services::IConfigService {
|
|
||||||
public:
|
|
||||||
CubeDemoConfigService(std::filesystem::path scriptPath, std::string configJson, std::string renderer = "auto")
|
|
||||||
: scriptPath_(std::move(scriptPath)),
|
|
||||||
configJson_(std::move(configJson)) {
|
|
||||||
materialXConfig_.enabled = true;
|
|
||||||
materialXConfig_.useConstantColor = true;
|
|
||||||
materialXConfig_.shaderKey = "test";
|
|
||||||
materialXConfig_.libraryPath = ResolveMaterialXLibraryPath();
|
|
||||||
renderer_ = std::move(renderer);
|
|
||||||
bgfxConfig_.renderer = renderer_;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t GetWindowWidth() const override { return 1; }
|
|
||||||
uint32_t GetWindowHeight() const override { return 1; }
|
|
||||||
std::filesystem::path GetScriptPath() const override { return scriptPath_; }
|
|
||||||
bool IsLuaDebugEnabled() const override { return false; }
|
|
||||||
std::string GetWindowTitle() const override { return ""; }
|
|
||||||
sdl3cpp::services::SceneSource GetSceneSource() const override {
|
|
||||||
return sdl3cpp::services::SceneSource::Lua;
|
|
||||||
}
|
|
||||||
const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; }
|
|
||||||
const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; }
|
|
||||||
const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; }
|
|
||||||
const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; }
|
|
||||||
const std::vector<sdl3cpp::services::MaterialXMaterialConfig>& GetMaterialXMaterialConfigs() const override {
|
|
||||||
return materialXMaterials_;
|
|
||||||
}
|
|
||||||
const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; }
|
|
||||||
const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; }
|
|
||||||
const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; }
|
|
||||||
const sdl3cpp::services::ValidationTourConfig& GetValidationTourConfig() const override {
|
|
||||||
return validationTour_;
|
|
||||||
}
|
|
||||||
const std::string& GetConfigJson() const override { return configJson_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::filesystem::path ResolveMaterialXLibraryPath() {
|
|
||||||
auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path();
|
|
||||||
return repoRoot / "MaterialX" / "libraries";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path scriptPath_;
|
|
||||||
std::string configJson_;
|
|
||||||
sdl3cpp::services::InputBindings inputBindings_{};
|
|
||||||
sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{};
|
|
||||||
sdl3cpp::services::BgfxConfig bgfxConfig_{};
|
|
||||||
sdl3cpp::services::MaterialXConfig materialXConfig_{};
|
|
||||||
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
|
|
||||||
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
|
|
||||||
sdl3cpp::services::RenderBudgetConfig budgets_{};
|
|
||||||
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
|
|
||||||
sdl3cpp::services::ValidationTourConfig validationTour_{};
|
|
||||||
std::string renderer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StubAudioCommandService final : public sdl3cpp::services::IAudioCommandService {
|
|
||||||
public:
|
|
||||||
bool QueueAudioCommand(sdl3cpp::services::AudioCommandType,
|
|
||||||
const std::string&,
|
|
||||||
bool,
|
|
||||||
std::string&) override {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool StopBackground(std::string&) override {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void Assert(bool condition, const std::string& message, int& failures) {
|
|
||||||
if (!condition) {
|
|
||||||
std::cerr << "test failure: " << message << '\n';
|
|
||||||
++failures;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MatrixSummary {
|
|
||||||
std::array<float, 3> translation{};
|
|
||||||
std::array<float, 3> scale{};
|
|
||||||
};
|
|
||||||
|
|
||||||
MatrixSummary ExtractMatrixSummary(const std::array<float, 16>& matrix) {
|
|
||||||
MatrixSummary summary;
|
|
||||||
summary.translation = {matrix[12], matrix[13], matrix[14]};
|
|
||||||
|
|
||||||
// Extract scale as magnitude of basis vectors (correct for rotation+scale matrices)
|
|
||||||
// X-axis: matrix[0,1,2], Y-axis: matrix[4,5,6], Z-axis: matrix[8,9,10]
|
|
||||||
auto length = [](float x, float y, float z) {
|
|
||||||
return std::sqrt(x*x + y*y + z*z);
|
|
||||||
};
|
|
||||||
summary.scale = {
|
|
||||||
length(matrix[0], matrix[1], matrix[2]),
|
|
||||||
length(matrix[4], matrix[5], matrix[6]),
|
|
||||||
length(matrix[8], matrix[9], matrix[10])
|
|
||||||
};
|
|
||||||
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<float, 16> ToArray(const glm::mat4& matrix) {
|
|
||||||
std::array<float, 16> values{};
|
|
||||||
std::memcpy(values.data(), glm::value_ptr(matrix), sizeof(float) * values.size());
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpectMatrixNear(const std::array<float, 16>& actual,
|
|
||||||
const std::array<float, 16>& expected,
|
|
||||||
const std::string& label,
|
|
||||||
int& failures,
|
|
||||||
float eps = 1e-4f) {
|
|
||||||
for (size_t i = 0; i < actual.size(); ++i) {
|
|
||||||
if (!ApproximatelyEqual(actual[i], expected[i], eps)) {
|
|
||||||
std::cerr << label << " differs at index " << i << " (" << actual[i]
|
|
||||||
<< " vs " << expected[i] << ")\n";
|
|
||||||
++failures;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExpectColorNear(const sdl3cpp::core::Vertex& vertex,
|
|
||||||
const std::array<float, 3>& expected,
|
|
||||||
const std::string& label,
|
|
||||||
int& failures,
|
|
||||||
float eps = 1e-4f) {
|
|
||||||
for (size_t i = 0; i < expected.size(); ++i) {
|
|
||||||
if (!ApproximatelyEqual(vertex.color[i], expected[i], eps)) {
|
|
||||||
std::cerr << label << " color differs at index " << i << " (" << vertex.color[i]
|
|
||||||
<< " vs " << expected[i] << ")\n";
|
|
||||||
++failures;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RunGpuRenderTest(int& failures,
|
|
||||||
const std::shared_ptr<sdl3cpp::services::IConfigService>& configService,
|
|
||||||
const std::shared_ptr<sdl3cpp::services::ILogger>& logger) {
|
|
||||||
const char* preferredDrivers[] = {"x11", "wayland", "offscreen", "dummy", nullptr};
|
|
||||||
const char* selectedDriver = nullptr;
|
|
||||||
|
|
||||||
auto setVideoDriver = [](const char* driver) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (driver) {
|
|
||||||
_putenv_s("SDL_VIDEODRIVER", driver);
|
|
||||||
} else {
|
|
||||||
_putenv_s("SDL_VIDEODRIVER", "");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (driver) {
|
|
||||||
setenv("SDL_VIDEODRIVER", driver, 1);
|
|
||||||
} else {
|
|
||||||
unsetenv("SDL_VIDEODRIVER");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (driver) {
|
|
||||||
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, driver);
|
|
||||||
} else {
|
|
||||||
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto platformService = std::make_shared<sdl3cpp::services::impl::PlatformService>(logger);
|
|
||||||
auto eventBus = std::make_shared<sdl3cpp::events::EventBus>();
|
|
||||||
auto windowService = std::make_shared<sdl3cpp::services::impl::SdlWindowService>(logger, platformService, eventBus);
|
|
||||||
|
|
||||||
SDL_Window* window = nullptr;
|
|
||||||
bool windowCreated = false;
|
|
||||||
|
|
||||||
for (const char* driver : preferredDrivers) {
|
|
||||||
setVideoDriver(driver);
|
|
||||||
SDL_ClearError();
|
|
||||||
|
|
||||||
try {
|
|
||||||
windowService->Initialize();
|
|
||||||
sdl3cpp::services::WindowConfig config{};
|
|
||||||
config.width = 256;
|
|
||||||
config.height = 256;
|
|
||||||
config.title = "cube_gpu_test";
|
|
||||||
config.resizable = false;
|
|
||||||
windowService->CreateWindow(config);
|
|
||||||
window = windowService->GetNativeHandle();
|
|
||||||
windowCreated = true;
|
|
||||||
selectedDriver = driver;
|
|
||||||
break;
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
std::cerr << "Window creation failed for driver [" << (driver ? driver : "default")
|
|
||||||
<< "]: " << ex.what() << '\n';
|
|
||||||
windowService->Shutdown();
|
|
||||||
windowService = std::make_shared<sdl3cpp::services::impl::SdlWindowService>(logger, platformService, eventBus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!windowCreated) {
|
|
||||||
std::string driverList;
|
|
||||||
const int driverCount = SDL_GetNumVideoDrivers();
|
|
||||||
for (int i = 0; i < driverCount; ++i) {
|
|
||||||
const char* driver = SDL_GetVideoDriver(i);
|
|
||||||
if (!driver) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!driverList.empty()) {
|
|
||||||
driverList += ", ";
|
|
||||||
}
|
|
||||||
driverList += driver;
|
|
||||||
}
|
|
||||||
if (driverList.empty()) {
|
|
||||||
driverList = "none";
|
|
||||||
}
|
|
||||||
std::cerr << "GPU render test failed: no SDL driver available"
|
|
||||||
<< " (available drivers: " << driverList << ")\n";
|
|
||||||
++failures;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedDriver) {
|
|
||||||
std::cout << "SDL video driver selected for GPU test: " << selectedDriver << '\n';
|
|
||||||
} else {
|
|
||||||
std::cout << "SDL video driver selected for GPU test: default\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pipelineCompiler = std::make_shared<sdl3cpp::services::impl::PipelineCompilerService>(logger);
|
|
||||||
sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger, pipelineCompiler);
|
|
||||||
bool success = true;
|
|
||||||
bool skipGpuRenderTest = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
sdl3cpp::services::GraphicsConfig graphicsConfig{};
|
|
||||||
backend.Initialize(window, graphicsConfig);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
std::cerr << "GPU render test failed: bgfx init threw: " << ex.what() << '\n';
|
|
||||||
++failures;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success && bgfx::getRendererType() == bgfx::RendererType::Noop) {
|
|
||||||
const std::string activeDriver = selectedDriver ? selectedDriver : "";
|
|
||||||
if (activeDriver == "offscreen" || activeDriver == "dummy") {
|
|
||||||
std::cerr << "GPU render test skipped: bgfx selected Noop renderer for headless driver '" <<
|
|
||||||
activeDriver << "'\n";
|
|
||||||
skipGpuRenderTest = true;
|
|
||||||
} else {
|
|
||||||
std::cerr << "GPU render test failed: bgfx selected Noop renderer despite SDL success\n";
|
|
||||||
++failures;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success && !skipGpuRenderTest) {
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.EndFrame(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.DestroyDevice(device);
|
|
||||||
sceneService->Shutdown();
|
|
||||||
engineService->Shutdown();
|
|
||||||
|
|
||||||
std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.Shutdown();
|
|
||||||
windowService->DestroyWindow();
|
|
||||||
windowService->Shutdown();
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunCubeDemoSceneTests(int& failures) {
|
|
||||||
auto scriptPath = GetCubeScriptPath();
|
|
||||||
auto configPath = GetSeedConfigPath();
|
|
||||||
auto configJson = ReadFileContents(configPath);
|
|
||||||
Assert(static_cast<bool>(configJson), "seed runtime config missing", failures);
|
|
||||||
if (!configJson) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto logger = std::make_shared<sdl3cpp::services::impl::LoggerService>();
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (!RunGpuRenderTest(failures, configService, logger)) {
|
|
||||||
std::cerr << "Aborting cube scene checks because GPU render test failed\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
|
|
||||||
scriptPath,
|
|
||||||
logger,
|
|
||||||
meshService,
|
|
||||||
audioService,
|
|
||||||
physicsService,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
configService,
|
|
||||||
false);
|
|
||||||
engineService->Initialize();
|
|
||||||
|
|
||||||
auto sceneScriptService = std::make_shared<sdl3cpp::services::impl::SceneScriptService>(engineService, logger);
|
|
||||||
auto objects = sceneScriptService->LoadSceneObjects();
|
|
||||||
|
|
||||||
Assert(objects.size() == 15, "cube demo should return 15 scene objects", failures);
|
|
||||||
if (objects.empty()) {
|
|
||||||
engineService->Shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& object : objects) {
|
|
||||||
Assert(!object.vertices.empty(), "scene object missing vertices", failures);
|
|
||||||
Assert(!object.indices.empty(), "scene object missing indices", failures);
|
|
||||||
Assert(!object.shaderKeys.empty(), "scene object missing shader key", failures);
|
|
||||||
Assert(object.computeModelMatrixRef >= 0, "scene object must keep a Lua reference", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ecsService = std::make_shared<sdl3cpp::services::impl::EcsService>(logger);
|
|
||||||
auto sceneManager = std::make_shared<sdl3cpp::services::impl::SceneService>(sceneScriptService, ecsService, logger);
|
|
||||||
sceneManager->LoadScene(objects);
|
|
||||||
Assert(sceneManager->GetObjectCount() == objects.size(), "scene service object count mismatch", failures);
|
|
||||||
|
|
||||||
size_t expectedVertexCount = 0;
|
|
||||||
size_t expectedIndexCount = 0;
|
|
||||||
for (const auto& object : objects) {
|
|
||||||
expectedVertexCount += object.vertices.size();
|
|
||||||
expectedIndexCount += object.indices.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& combinedVertices = sceneManager->GetCombinedVertices();
|
|
||||||
const auto& combinedIndices = sceneManager->GetCombinedIndices();
|
|
||||||
Assert(combinedVertices.size() == expectedVertexCount, "combined vertex count mismatch", failures);
|
|
||||||
Assert(combinedIndices.size() == expectedIndexCount, "combined index count mismatch", failures);
|
|
||||||
|
|
||||||
const std::array<float, 3> white = {1.0f, 1.0f, 1.0f};
|
|
||||||
const std::array<float, 3> lanternColor = {1.0f, 0.9f, 0.6f};
|
|
||||||
const std::array<float, 3> cubeColor = {0.92f, 0.34f, 0.28f};
|
|
||||||
|
|
||||||
const float roomHalfSize = 15.0f;
|
|
||||||
const float wallThickness = 0.5f;
|
|
||||||
const float wallHeight = 4.0f;
|
|
||||||
const float floorHalfThickness = 0.3f;
|
|
||||||
const float floorTop = 0.0f;
|
|
||||||
const float floorCenterY = floorTop - floorHalfThickness;
|
|
||||||
const float wallCenterY = floorTop + wallHeight;
|
|
||||||
const float ceilingY = floorTop + wallHeight * 2.0f + floorHalfThickness;
|
|
||||||
const float wallOffset = roomHalfSize + wallThickness;
|
|
||||||
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;
|
|
||||||
|
|
||||||
auto staticCommands = sceneManager->GetRenderCommands(0.0f);
|
|
||||||
auto dynamicCommands = sceneManager->GetRenderCommands(0.1f);
|
|
||||||
Assert(staticCommands.size() == objects.size(), "render command count mismatch", failures);
|
|
||||||
Assert(dynamicCommands.size() == objects.size(), "dynamic render command count mismatch", failures);
|
|
||||||
|
|
||||||
std::vector<size_t> floorIndices;
|
|
||||||
std::vector<size_t> wallIndices;
|
|
||||||
std::vector<size_t> ceilingIndices;
|
|
||||||
std::vector<size_t> solidIndices;
|
|
||||||
std::vector<size_t> otherIndices;
|
|
||||||
std::vector<std::array<float, 3>> wallTranslations;
|
|
||||||
wallTranslations.reserve(4);
|
|
||||||
std::vector<std::array<float, 3>> lanternTranslations;
|
|
||||||
lanternTranslations.reserve(8);
|
|
||||||
|
|
||||||
for (size_t index = 0; index < staticCommands.size(); ++index) {
|
|
||||||
const auto& command = staticCommands[index];
|
|
||||||
const auto& object = objects[index];
|
|
||||||
Assert(!command.shaderKeys.empty(), "scene object missing shader key", failures);
|
|
||||||
Assert(!object.indices.empty(), "scene object should have indices", failures);
|
|
||||||
|
|
||||||
const auto summary = ExtractMatrixSummary(command.modelMatrix);
|
|
||||||
const std::string& objectType = object.objectType;
|
|
||||||
|
|
||||||
if (objectType == "floor") {
|
|
||||||
floorIndices.push_back(index);
|
|
||||||
if (!object.vertices.empty()) {
|
|
||||||
ExpectColorNear(object.vertices.front(), white, "floor vertex color", failures);
|
|
||||||
}
|
|
||||||
} else if (objectType == "wall") {
|
|
||||||
wallIndices.push_back(index);
|
|
||||||
wallTranslations.push_back(summary.translation);
|
|
||||||
Assert(ApproximatelyEqual(summary.scale[1], wallHeight), "wall scale height mismatch", failures);
|
|
||||||
if (!object.vertices.empty()) {
|
|
||||||
ExpectColorNear(object.vertices.front(), white, "wall vertex color", failures);
|
|
||||||
}
|
|
||||||
} else if (objectType == "ceiling") {
|
|
||||||
ceilingIndices.push_back(index);
|
|
||||||
Assert(ApproximatelyEqual(summary.translation[1], ceilingY), "ceiling translation mismatch", failures);
|
|
||||||
// Ceiling now uses tessellated plane with scale 1.0 (geometry is pre-sized)
|
|
||||||
Assert(ApproximatelyEqual(summary.scale[0], 1.0f, 0.1f), "ceiling scale mismatch", failures);
|
|
||||||
if (!object.vertices.empty()) {
|
|
||||||
ExpectColorNear(object.vertices.front(), white, "ceiling vertex color", failures);
|
|
||||||
}
|
|
||||||
} else if (objectType == "lantern") {
|
|
||||||
solidIndices.push_back(index);
|
|
||||||
lanternTranslations.push_back(summary.translation);
|
|
||||||
Assert(ApproximatelyEqual(summary.scale[0], lanternSize), "lantern scale mismatch", failures);
|
|
||||||
if (!object.vertices.empty()) {
|
|
||||||
ExpectColorNear(object.vertices.front(), lanternColor, "lantern vertex color", failures);
|
|
||||||
}
|
|
||||||
} else if (objectType == "physics_cube" || objectType == "spinning_cube") {
|
|
||||||
// Physics cube is tracked separately below
|
|
||||||
} else {
|
|
||||||
otherIndices.push_back(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert(ceilingIndices.size() == 1, "expected 1 ceiling object", failures);
|
|
||||||
Assert(wallIndices.size() == 4, "expected 4 wall objects", failures);
|
|
||||||
Assert(solidIndices.size() == 8, "expected 8 lantern objects", failures);
|
|
||||||
Assert(floorIndices.size() == 1, "expected 1 floor object", failures);
|
|
||||||
Assert(otherIndices.empty(), "unexpected object types in cube demo scene", failures);
|
|
||||||
|
|
||||||
const std::vector<std::array<float, 3>> expectedWallTranslations = {
|
|
||||||
{0.0f, wallCenterY, -wallOffset},
|
|
||||||
{0.0f, wallCenterY, wallOffset},
|
|
||||||
{-wallOffset, wallCenterY, 0.0f},
|
|
||||||
{wallOffset, wallCenterY, 0.0f},
|
|
||||||
};
|
|
||||||
for (const auto& expected : expectedWallTranslations) {
|
|
||||||
bool found = false;
|
|
||||||
for (const auto& actual : wallTranslations) {
|
|
||||||
if (ApproximatelyEqual(actual[0], expected[0])
|
|
||||||
&& ApproximatelyEqual(actual[1], expected[1])
|
|
||||||
&& ApproximatelyEqual(actual[2], expected[2])) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert(found, "missing wall at expected translation", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::array<float, 3>> expectedLanternTranslations = {
|
|
||||||
{lanternOffset, lanternHeight, lanternOffset},
|
|
||||||
{-lanternOffset, lanternHeight, lanternOffset},
|
|
||||||
{lanternOffset, lanternHeight, -lanternOffset},
|
|
||||||
{-lanternOffset, lanternHeight, -lanternOffset},
|
|
||||||
{0.0f, lanternHeight, lanternOffset},
|
|
||||||
{0.0f, lanternHeight, -lanternOffset},
|
|
||||||
{lanternOffset, lanternHeight, 0.0f},
|
|
||||||
{-lanternOffset, lanternHeight, 0.0f},
|
|
||||||
};
|
|
||||||
for (const auto& expected : expectedLanternTranslations) {
|
|
||||||
bool found = false;
|
|
||||||
for (const auto& actual : lanternTranslations) {
|
|
||||||
if (ApproximatelyEqual(actual[0], expected[0])
|
|
||||||
&& ApproximatelyEqual(actual[1], expected[1])
|
|
||||||
&& ApproximatelyEqual(actual[2], expected[2])) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert(found, "missing lantern at expected translation", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find floor and physics cube by object type
|
|
||||||
size_t floorObjectIndex = std::numeric_limits<size_t>::max();
|
|
||||||
size_t cubeObjectIndex = std::numeric_limits<size_t>::max();
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < objects.size(); ++idx) {
|
|
||||||
if (objects[idx].objectType == "floor") {
|
|
||||||
floorObjectIndex = idx;
|
|
||||||
} else if (objects[idx].objectType == "physics_cube" || objects[idx].objectType == "spinning_cube") {
|
|
||||||
cubeObjectIndex = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert(floorObjectIndex != std::numeric_limits<size_t>::max(), "floor object not found", failures);
|
|
||||||
Assert(cubeObjectIndex != std::numeric_limits<size_t>::max(), "dynamic cube object not found", failures);
|
|
||||||
|
|
||||||
if (floorObjectIndex != std::numeric_limits<size_t>::max()) {
|
|
||||||
auto summary = ExtractMatrixSummary(staticCommands[floorObjectIndex].modelMatrix);
|
|
||||||
Assert(ApproximatelyEqual(summary.translation[1], floorCenterY), "floor translation mismatch", failures);
|
|
||||||
// Floor now has scale 1.0 (geometry is pre-sized)
|
|
||||||
Assert(ApproximatelyEqual(summary.scale[0], 1.0f, 0.1f), "floor scale mismatch", failures);
|
|
||||||
if (!objects[floorObjectIndex].vertices.empty()) {
|
|
||||||
ExpectColorNear(objects[floorObjectIndex].vertices.front(), white, "floor vertex color", failures);
|
|
||||||
}
|
|
||||||
Assert(!objects[floorObjectIndex].indices.empty(), "floor indices should not be empty", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cubeObjectIndex != std::numeric_limits<size_t>::max()) {
|
|
||||||
auto summary = ExtractMatrixSummary(dynamicCommands[cubeObjectIndex].modelMatrix);
|
|
||||||
Assert(ApproximatelyEqual(summary.translation[0], 0.0f, 0.05f),
|
|
||||||
"physics cube x translation mismatch", failures);
|
|
||||||
Assert(ApproximatelyEqual(summary.translation[2], 0.0f, 0.05f),
|
|
||||||
"physics cube z translation mismatch", failures);
|
|
||||||
Assert(ApproximatelyEqual(summary.translation[1], cubeSpawnY, 0.25f),
|
|
||||||
"physics cube y translation mismatch", failures);
|
|
||||||
// Physics cube scale is 1.5, now correctly extracted from rotated matrix
|
|
||||||
Assert(ApproximatelyEqual(summary.scale[0], 1.5f, 0.1f), "physics cube scale mismatch", failures);
|
|
||||||
if (!objects[cubeObjectIndex].vertices.empty()) {
|
|
||||||
ExpectColorNear(objects[cubeObjectIndex].vertices.front(), cubeColor, "physics cube vertex color", failures);
|
|
||||||
}
|
|
||||||
Assert(!objects[cubeObjectIndex].indices.empty(), "cube indices should not be empty", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneManager->Shutdown();
|
|
||||||
engineService->Shutdown();
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// When invoking this test locally, prefer `python scripts/dev_commands.py cmake -- --target script_engine_tests`
|
|
||||||
// so the helper picks the right build dir/generator.
|
|
||||||
int main() {
|
|
||||||
int failures = 0;
|
|
||||||
auto scriptPath = GetTestScriptPath();
|
|
||||||
std::cout << "Loading Lua fixture: " << scriptPath << '\n';
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto logger = std::make_shared<sdl3cpp::services::impl::LoggerService>();
|
|
||||||
auto configService = std::make_shared<StubConfigService>();
|
|
||||||
auto engineService = std::make_shared<sdl3cpp::services::impl::ScriptEngineService>(
|
|
||||||
scriptPath,
|
|
||||||
logger,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
configService,
|
|
||||||
false);
|
|
||||||
engineService->Initialize();
|
|
||||||
|
|
||||||
sdl3cpp::services::impl::SceneScriptService sceneService(engineService, logger);
|
|
||||||
auto shaderSystemRegistry = std::make_shared<sdl3cpp::services::impl::ShaderSystemRegistry>(
|
|
||||||
configService,
|
|
||||||
nullptr,
|
|
||||||
engineService,
|
|
||||||
logger);
|
|
||||||
sdl3cpp::services::impl::ShaderScriptService shaderService(
|
|
||||||
engineService,
|
|
||||||
shaderSystemRegistry,
|
|
||||||
logger);
|
|
||||||
|
|
||||||
auto objects = sceneService.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.shaderKeys.size() == 1, "shader keys should contain one entry", failures);
|
|
||||||
if (!object.shaderKeys.empty()) {
|
|
||||||
Assert(object.shaderKeys.front() == "test", "shader key should match fixture", failures);
|
|
||||||
}
|
|
||||||
const std::vector<uint16_t> expectedIndices{0, 1, 2};
|
|
||||||
Assert(object.indices == expectedIndices, "indices should be zero-based", failures);
|
|
||||||
Assert(object.computeModelMatrixRef >= 0,
|
|
||||||
"vertex object must keep a Lua reference", failures);
|
|
||||||
|
|
||||||
auto objectMatrix = sceneService.ComputeModelMatrix(object.computeModelMatrixRef, 0.5f);
|
|
||||||
ExpectIdentity(objectMatrix, "object compute_model_matrix", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto fallbackMatrix = sceneService.ComputeModelMatrix(-1, 1.0f);
|
|
||||||
ExpectIdentity(fallbackMatrix, "global compute_model_matrix", failures);
|
|
||||||
|
|
||||||
auto viewState = sceneService.GetViewState(1.33f);
|
|
||||||
ExpectIdentity(viewState.viewProj, "view_projection matrix", failures);
|
|
||||||
|
|
||||||
auto shaderMap = shaderService.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.vertexSource.empty(), "vertex shader source missing", failures);
|
|
||||||
Assert(!testEntry->second.fragmentSource.empty(), "fragment shader source missing", failures);
|
|
||||||
}
|
|
||||||
|
|
||||||
RunCubeDemoSceneTests(failures);
|
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
std::cerr << "exception during tests: " << ex.what() << '\n';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failures == 0) {
|
|
||||||
std::cout << "script_engine_tests: PASSED\n";
|
|
||||||
} else {
|
|
||||||
std::cerr << "script_engine_tests: FAILED (" << failures << " errors)\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return failures;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user