mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat: Enhance rendering and crash recovery configurations
- Added new rendering budget configurations including VRAM limits and texture dimensions in seed_runtime.json and seed_runtime_opengl.json. - Introduced crash recovery parameters such as heartbeat timeouts and memory limits in the configuration files. - Updated cube logic to utilize new camera and control settings from the configuration. - Modified bgfx graphics backend to respect new rendering budget limits and handle texture loading accordingly. - Implemented crash recovery service enhancements to utilize new configuration parameters for better resource management. - Added unit tests to validate the integration of new rendering budgets and crash recovery configurations.
This commit is contained in:
@@ -73,6 +73,28 @@
|
||||
},
|
||||
"scene": {
|
||||
"rotation_speed": 0.9,
|
||||
"cube_mesh": {
|
||||
"path": "models/cube.stl",
|
||||
"double_sided": true
|
||||
},
|
||||
"camera": {
|
||||
"position": [0.0, 1.6, 10.0],
|
||||
"fov": 0.78,
|
||||
"near": 0.1,
|
||||
"far": 50.0
|
||||
},
|
||||
"controls": {
|
||||
"move_speed": 16.0,
|
||||
"fly_speed": 3.0,
|
||||
"jump_speed": 5.5,
|
||||
"gravity": -12.0,
|
||||
"max_fall_speed": -20.0,
|
||||
"mouse_sensitivity": 0.0025,
|
||||
"gamepad_look_speed": 2.5,
|
||||
"stick_deadzone": 0.2,
|
||||
"max_pitch_degrees": 85.0,
|
||||
"move_forward_uses_pitch": true
|
||||
},
|
||||
"room": {
|
||||
"half_size": 15.0,
|
||||
"wall_thickness": 0.5,
|
||||
@@ -163,6 +185,22 @@
|
||||
"pbr_metallic": 0.08
|
||||
}
|
||||
},
|
||||
"budgets": {
|
||||
"vram_mb": 512,
|
||||
"max_texture_dim": 4096,
|
||||
"gui_text_cache_entries": 256,
|
||||
"gui_svg_cache_entries": 64
|
||||
},
|
||||
"crash_recovery": {
|
||||
"heartbeat_timeout_ms": 5000,
|
||||
"heartbeat_poll_interval_ms": 200,
|
||||
"memory_limit_mb": 1024,
|
||||
"gpu_hang_frame_time_multiplier": 10.0,
|
||||
"max_consecutive_gpu_timeouts": 5,
|
||||
"max_lua_failures": 3,
|
||||
"max_file_format_errors": 2,
|
||||
"max_memory_warnings": 3
|
||||
},
|
||||
"gui": {
|
||||
"font": {
|
||||
"use_freetype": true,
|
||||
|
||||
@@ -70,6 +70,65 @@
|
||||
"gamepad_axis_action_threshold": 0.5
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"rotation_speed": 0.9,
|
||||
"cube_mesh": {
|
||||
"path": "models/cube.stl",
|
||||
"double_sided": true
|
||||
},
|
||||
"camera": {
|
||||
"position": [0.0, 1.6, 10.0],
|
||||
"fov": 0.78,
|
||||
"near": 0.1,
|
||||
"far": 50.0
|
||||
},
|
||||
"controls": {
|
||||
"move_speed": 16.0,
|
||||
"fly_speed": 3.0,
|
||||
"jump_speed": 5.5,
|
||||
"gravity": -12.0,
|
||||
"max_fall_speed": -20.0,
|
||||
"mouse_sensitivity": 0.0025,
|
||||
"gamepad_look_speed": 2.5,
|
||||
"stick_deadzone": 0.2,
|
||||
"max_pitch_degrees": 85.0,
|
||||
"move_forward_uses_pitch": true
|
||||
},
|
||||
"room": {
|
||||
"half_size": 15.0,
|
||||
"wall_thickness": 0.5,
|
||||
"wall_height": 4.0,
|
||||
"floor_half_thickness": 0.3,
|
||||
"floor_top": 0.0,
|
||||
"floor_subdivisions": 20,
|
||||
"floor_color": [1.0, 1.0, 1.0],
|
||||
"wall_color": [1.0, 1.0, 1.0],
|
||||
"ceiling_color": [1.0, 1.0, 1.0]
|
||||
},
|
||||
"physics_cube": {
|
||||
"enabled": true,
|
||||
"half_extents": [1.5, 1.5, 1.5],
|
||||
"mass": 1.0,
|
||||
"color": [0.92, 0.34, 0.28],
|
||||
"kick_strength": 6.0,
|
||||
"gravity": [0.0, -9.8, 0.0],
|
||||
"max_sub_steps": 10
|
||||
},
|
||||
"spinning_cube": {
|
||||
"enabled": true,
|
||||
"scale": 1.5,
|
||||
"height": 5.0,
|
||||
"color": [0.75, 0.45, 0.25]
|
||||
},
|
||||
"lanterns": {
|
||||
"enabled": true,
|
||||
"height": 8.0,
|
||||
"size": 0.2,
|
||||
"color": [1.0, 0.9, 0.6],
|
||||
"corner_offset": 2.0,
|
||||
"wall_offset": 2.0
|
||||
}
|
||||
},
|
||||
"rendering": {
|
||||
"bgfx": {
|
||||
"renderer": "opengl"
|
||||
@@ -125,6 +184,22 @@
|
||||
"pbr_metallic": 0.08
|
||||
}
|
||||
},
|
||||
"budgets": {
|
||||
"vram_mb": 512,
|
||||
"max_texture_dim": 4096,
|
||||
"gui_text_cache_entries": 256,
|
||||
"gui_svg_cache_entries": 64
|
||||
},
|
||||
"crash_recovery": {
|
||||
"heartbeat_timeout_ms": 5000,
|
||||
"heartbeat_poll_interval_ms": 200,
|
||||
"memory_limit_mb": 1024,
|
||||
"gpu_hang_frame_time_multiplier": 10.0,
|
||||
"max_consecutive_gpu_timeouts": 5,
|
||||
"max_lua_failures": 3,
|
||||
"max_file_format_errors": 2,
|
||||
"max_memory_warnings": 3
|
||||
},
|
||||
"gui": {
|
||||
"font": {
|
||||
"use_freetype": true,
|
||||
|
||||
@@ -2,8 +2,19 @@ local scene_framework = require("scene_framework")
|
||||
local math3d = require("math3d")
|
||||
local config_resolver = require("config_resolver")
|
||||
|
||||
local resolve_number = scene_framework.resolve_number
|
||||
local resolve_boolean = scene_framework.resolve_boolean
|
||||
local resolve_string = scene_framework.resolve_string
|
||||
local resolve_table = scene_framework.resolve_table
|
||||
local resolve_vec3 = scene_framework.resolve_vec3
|
||||
|
||||
local scene_config = resolve_table(config_resolver.resolve_scene(config))
|
||||
local cube_mesh_config = resolve_table(scene_config.cube_mesh)
|
||||
local cube_mesh_path = resolve_string(cube_mesh_config.path, "models/cube.stl")
|
||||
local cube_mesh_double_sided = resolve_boolean(cube_mesh_config.double_sided, true)
|
||||
|
||||
local cube_mesh_info = {
|
||||
path = "models/cube.stl",
|
||||
path = cube_mesh_path,
|
||||
loaded = false,
|
||||
vertex_count = 0,
|
||||
index_count = 0,
|
||||
@@ -63,7 +74,11 @@ local function load_cube_mesh()
|
||||
|
||||
cube_vertices = mesh.vertices
|
||||
cube_indices = mesh.indices
|
||||
cube_indices_double_sided = build_double_sided_indices(cube_indices)
|
||||
if cube_mesh_double_sided then
|
||||
cube_indices_double_sided = build_double_sided_indices(cube_indices)
|
||||
else
|
||||
cube_indices_double_sided = {}
|
||||
end
|
||||
cube_mesh_info.loaded = true
|
||||
cube_mesh_info.vertex_count = #mesh.vertices
|
||||
cube_mesh_info.index_count = #mesh.indices
|
||||
@@ -108,11 +123,6 @@ start_music()
|
||||
local Gui = require("gui")
|
||||
local string_format = string.format
|
||||
|
||||
local resolve_number = scene_framework.resolve_number
|
||||
local resolve_boolean = scene_framework.resolve_boolean
|
||||
local resolve_table = scene_framework.resolve_table
|
||||
local resolve_vec3 = scene_framework.resolve_vec3
|
||||
|
||||
local function resolve_positive_int(value, fallback)
|
||||
local candidate = resolve_number(value, fallback)
|
||||
local rounded = math.floor(candidate)
|
||||
@@ -275,26 +285,37 @@ if cube_mesh_info.loaded then
|
||||
end
|
||||
end
|
||||
|
||||
local camera_config = resolve_table(scene_config.camera)
|
||||
local controls_config = resolve_table(scene_config.controls)
|
||||
local max_pitch_degrees = resolve_number(controls_config.max_pitch_degrees, nil)
|
||||
local max_pitch = resolve_number(controls_config.max_pitch, nil)
|
||||
if max_pitch_degrees then
|
||||
max_pitch = math.rad(max_pitch_degrees)
|
||||
end
|
||||
if not max_pitch then
|
||||
max_pitch = math.rad(85.0)
|
||||
end
|
||||
|
||||
local camera = {
|
||||
position = {0.0, 0.0, 5.0},
|
||||
yaw = math.pi, -- Face toward -Z (center of room)
|
||||
pitch = 0.0,
|
||||
fov = 0.78,
|
||||
near = 0.1,
|
||||
far = 50.0,
|
||||
yaw = resolve_number(camera_config.yaw, math.pi),
|
||||
pitch = resolve_number(camera_config.pitch, 0.0),
|
||||
fov = resolve_number(camera_config.fov, 0.78),
|
||||
near = resolve_number(camera_config.near, 0.1),
|
||||
far = resolve_number(camera_config.far, 50.0),
|
||||
}
|
||||
|
||||
local controls = {
|
||||
move_speed = 16.0,
|
||||
fly_speed = 3.0,
|
||||
jump_speed = 5.5,
|
||||
gravity = -12.0,
|
||||
max_fall_speed = -20.0,
|
||||
mouse_sensitivity = 0.0025,
|
||||
gamepad_look_speed = 2.5,
|
||||
stick_deadzone = 0.2,
|
||||
max_pitch = math.rad(85.0),
|
||||
move_forward_uses_pitch = true,
|
||||
move_speed = resolve_number(controls_config.move_speed, 16.0),
|
||||
fly_speed = resolve_number(controls_config.fly_speed, 3.0),
|
||||
jump_speed = resolve_number(controls_config.jump_speed, 5.5),
|
||||
gravity = resolve_number(controls_config.gravity, -12.0),
|
||||
max_fall_speed = resolve_number(controls_config.max_fall_speed, -20.0),
|
||||
mouse_sensitivity = resolve_number(controls_config.mouse_sensitivity, 0.0025),
|
||||
gamepad_look_speed = resolve_number(controls_config.gamepad_look_speed, 2.5),
|
||||
stick_deadzone = resolve_number(controls_config.stick_deadzone, 0.2),
|
||||
max_pitch = max_pitch,
|
||||
move_forward_uses_pitch = resolve_boolean(controls_config.move_forward_uses_pitch, true),
|
||||
}
|
||||
|
||||
local last_frame_time = nil
|
||||
@@ -310,7 +331,6 @@ local function get_time_seconds()
|
||||
end
|
||||
local movement_log_cooldown = 0.0
|
||||
local world_up = {0.0, 1.0, 0.0}
|
||||
local scene_config = resolve_table(config_resolver.resolve_scene(config))
|
||||
local room_config = resolve_table(scene_config.room)
|
||||
local lantern_config = resolve_table(scene_config.lanterns)
|
||||
local physics_config = resolve_table(scene_config.physics_cube)
|
||||
@@ -385,9 +405,11 @@ local physics_state = {
|
||||
gravity = physics_gravity,
|
||||
}
|
||||
|
||||
camera.position[1] = 0.0
|
||||
camera.position[2] = room.floor_top + player_state.eye_height
|
||||
camera.position[3] = 10.0
|
||||
camera.position = resolve_vec3(camera_config.position, {
|
||||
0.0,
|
||||
room.floor_top + player_state.eye_height,
|
||||
10.0,
|
||||
})
|
||||
|
||||
local function clamp(value, minValue, maxValue)
|
||||
if value < minValue then
|
||||
|
||||
@@ -194,7 +194,8 @@ void ServiceBasedApp::RegisterServices() {
|
||||
|
||||
// Crash recovery service (needed early for crash detection)
|
||||
registry_.RegisterService<services::ICrashRecoveryService, services::impl::CrashRecoveryService>(
|
||||
registry_.GetService<services::ILogger>());
|
||||
registry_.GetService<services::ILogger>(),
|
||||
runtimeConfig_.crashRecovery);
|
||||
|
||||
// Lifecycle service
|
||||
registry_.RegisterService<services::ILifecycleService, services::impl::LifecycleService>(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
@@ -284,6 +285,16 @@ BgfxGraphicsBackend::BgfxGraphicsBackend(std::shared_ptr<IConfigService> configS
|
||||
"configService=" + std::string(configService_ ? "set" : "null") +
|
||||
", platformService=" + std::string(platformService_ ? "set" : "null"));
|
||||
}
|
||||
if (configService_) {
|
||||
const auto& budgets = configService_->GetRenderBudgetConfig();
|
||||
textureMemoryTracker_.SetMaxBytes(budgets.vramMB * 1024 * 1024);
|
||||
maxTextureDim_ = budgets.maxTextureDim;
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "BgfxGraphicsBackend",
|
||||
"vramMB=" + std::to_string(budgets.vramMB) +
|
||||
", maxTextureDim=" + std::to_string(maxTextureDim_));
|
||||
}
|
||||
}
|
||||
vertexLayout_.begin()
|
||||
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
|
||||
.add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float)
|
||||
@@ -735,10 +746,14 @@ bgfx::ShaderHandle BgfxGraphicsBackend::CreateShader(const std::string& label,
|
||||
}
|
||||
|
||||
bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string& path,
|
||||
uint64_t samplerFlags) const {
|
||||
uint64_t samplerFlags,
|
||||
size_t* outSizeBytes) const {
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGraphicsBackend", "LoadTextureFromFile", "path=" + path);
|
||||
}
|
||||
if (outSizeBytes) {
|
||||
*outSizeBytes = 0;
|
||||
}
|
||||
if (!HasProcessedFrame()) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::LoadTextureFromFile: Attempted to load texture BEFORE first "
|
||||
@@ -762,6 +777,18 @@ bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string&
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (maxTextureDim_ > 0) {
|
||||
if (width > static_cast<int>(maxTextureDim_) || height > static_cast<int>(maxTextureDim_)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::LoadTextureFromFile: texture " + path +
|
||||
" size (" + std::to_string(width) + "x" + std::to_string(height) +
|
||||
") exceeds config max texture dim (" + std::to_string(maxTextureDim_) + ")");
|
||||
}
|
||||
stbi_image_free(pixels);
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate texture dimensions against GPU capabilities
|
||||
const bgfx::Caps* caps = bgfx::getCaps();
|
||||
if (caps) {
|
||||
@@ -777,7 +804,16 @@ bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string&
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t size = static_cast<uint32_t>(width * height * 4);
|
||||
const size_t size = static_cast<size_t>(width) * static_cast<size_t>(height) * 4u;
|
||||
if (size > std::numeric_limits<uint32_t>::max()) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGraphicsBackend::LoadTextureFromFile: texture " + path +
|
||||
" exceeds supported size for upload (" + std::to_string(size) + " bytes)");
|
||||
}
|
||||
stbi_image_free(pixels);
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
const uint32_t sizeBytes = static_cast<uint32_t>(size);
|
||||
|
||||
// Check memory budget before allocation
|
||||
if (!textureMemoryTracker_.CanAllocate(size)) {
|
||||
@@ -791,7 +827,7 @@ bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string&
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const bgfx::Memory* mem = bgfx::copy(pixels, size);
|
||||
const bgfx::Memory* mem = bgfx::copy(pixels, sizeBytes);
|
||||
stbi_image_free(pixels);
|
||||
|
||||
// Validate bgfx::copy() succeeded
|
||||
@@ -830,6 +866,10 @@ bgfx::TextureHandle BgfxGraphicsBackend::LoadTextureFromFile(const std::string&
|
||||
", memoryMB=" + std::to_string(size / 1024 / 1024));
|
||||
}
|
||||
|
||||
if (outSizeBytes) {
|
||||
*outSizeBytes = size;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -955,23 +995,34 @@ GraphicsPipelineHandle BgfxGraphicsBackend::CreatePipeline(GraphicsDeviceHandle
|
||||
}
|
||||
|
||||
// Try to load texture from file
|
||||
binding.texture = LoadTextureFromFile(binding.sourcePath, samplerFlags);
|
||||
size_t textureSizeBytes = 0;
|
||||
binding.texture = LoadTextureFromFile(binding.sourcePath, samplerFlags, &textureSizeBytes);
|
||||
if (bgfx::isValid(binding.texture)) {
|
||||
// Estimate texture memory size (assume RGBA8 format, no mipmaps for now)
|
||||
// In production, should query actual texture info from bgfx
|
||||
// For now, estimate based on typical 2048x2048 textures
|
||||
binding.memorySizeBytes = 2048 * 2048 * 4; // Conservative estimate
|
||||
textureMemoryTracker_.Allocate(binding.memorySizeBytes);
|
||||
binding.memorySizeBytes = textureSizeBytes;
|
||||
if (binding.memorySizeBytes > 0) {
|
||||
textureMemoryTracker_.Allocate(binding.memorySizeBytes);
|
||||
}
|
||||
} else {
|
||||
if (logger_) {
|
||||
logger_->Warn("BgfxGraphicsBackend::CreatePipeline: texture load failed for " +
|
||||
binding.sourcePath + ", creating fallback texture");
|
||||
}
|
||||
// Use fallback magenta texture (1x1)
|
||||
binding.texture = CreateSolidTexture(0xff00ffff, samplerFlags);
|
||||
if (bgfx::isValid(binding.texture)) {
|
||||
binding.memorySizeBytes = 1 * 1 * 4; // 1x1 RGBA8
|
||||
textureMemoryTracker_.Allocate(binding.memorySizeBytes);
|
||||
binding.memorySizeBytes = 1 * 1 * 4; // 1x1 RGBA8
|
||||
if (!textureMemoryTracker_.CanAllocate(binding.memorySizeBytes)) {
|
||||
if (logger_) {
|
||||
logger_->Warn("BgfxGraphicsBackend::CreatePipeline: budget prevents fallback texture for " +
|
||||
binding.sourcePath);
|
||||
}
|
||||
binding.texture = BGFX_INVALID_HANDLE;
|
||||
binding.memorySizeBytes = 0;
|
||||
} else {
|
||||
binding.texture = CreateSolidTexture(0xff00ffff, samplerFlags);
|
||||
if (bgfx::isValid(binding.texture)) {
|
||||
textureMemoryTracker_.Allocate(binding.memorySizeBytes);
|
||||
} else {
|
||||
binding.memorySizeBytes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ private:
|
||||
TextureMemoryTracker() = default;
|
||||
|
||||
bool CanAllocate(size_t bytes) const {
|
||||
if (maxBytes_ == 0) {
|
||||
return true;
|
||||
}
|
||||
return (totalBytes_ + bytes) <= maxBytes_;
|
||||
}
|
||||
|
||||
@@ -78,7 +81,7 @@ private:
|
||||
|
||||
size_t GetUsedBytes() const { return totalBytes_; }
|
||||
size_t GetMaxBytes() const { return maxBytes_; }
|
||||
size_t GetAvailableBytes() const { return maxBytes_ - totalBytes_; }
|
||||
size_t GetAvailableBytes() const { return maxBytes_ == 0 ? 0 : maxBytes_ - totalBytes_; }
|
||||
|
||||
void SetMaxBytes(size_t max) { maxBytes_ = max; }
|
||||
|
||||
@@ -140,7 +143,9 @@ private:
|
||||
bgfx::ShaderHandle CreateShader(const std::string& label,
|
||||
const std::string& source,
|
||||
bool isVertex) const;
|
||||
bgfx::TextureHandle LoadTextureFromFile(const std::string& path, uint64_t samplerFlags) const;
|
||||
bgfx::TextureHandle LoadTextureFromFile(const std::string& path,
|
||||
uint64_t samplerFlags,
|
||||
size_t* outSizeBytes = nullptr) const;
|
||||
void InitializeUniforms();
|
||||
void DestroyUniforms();
|
||||
void ApplyMaterialXUniforms(const std::array<float, 16>& modelMatrix);
|
||||
@@ -168,6 +173,7 @@ private:
|
||||
bgfx::PlatformData platformData_{};
|
||||
bool loggedInitFailureDiagnostics_ = false;
|
||||
mutable TextureMemoryTracker textureMemoryTracker_{};
|
||||
uint32_t maxTextureDim_ = 0;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -125,6 +125,18 @@ BgfxGuiService::BgfxGuiService(std::shared_ptr<IConfigService> configService,
|
||||
"configService=" + std::string(configService_ ? "set" : "null") +
|
||||
", pipelineCompiler=" + std::string(pipelineCompiler_ ? "set" : "null"));
|
||||
}
|
||||
if (configService_) {
|
||||
const auto& budgets = configService_->GetRenderBudgetConfig();
|
||||
maxTextCacheEntries_ = budgets.guiTextCacheEntries;
|
||||
maxSvgCacheEntries_ = budgets.guiSvgCacheEntries;
|
||||
maxTextureDim_ = budgets.maxTextureDim;
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "BgfxGuiService",
|
||||
"guiTextCacheEntries=" + std::to_string(maxTextCacheEntries_) +
|
||||
", guiSvgCacheEntries=" + std::to_string(maxSvgCacheEntries_) +
|
||||
", maxTextureDim=" + std::to_string(maxTextureDim_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BgfxGuiService::~BgfxGuiService() {
|
||||
@@ -967,6 +979,22 @@ bgfx::TextureHandle BgfxGuiService::CreateTexture(const uint8_t* rgba,
|
||||
if (!rgba || width == 0 || height == 0) {
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
uint32_t maxDim = maxTextureDim_;
|
||||
if (const bgfx::Caps* caps = bgfx::getCaps()) {
|
||||
if (caps->limits.maxTextureSize > 0) {
|
||||
maxDim = (maxDim == 0)
|
||||
? caps->limits.maxTextureSize
|
||||
: std::min<uint32_t>(maxDim, caps->limits.maxTextureSize);
|
||||
}
|
||||
}
|
||||
if (maxDim > 0 && (width > maxDim || height > maxDim)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGuiService::CreateTexture: texture size (" +
|
||||
std::to_string(width) + "x" + std::to_string(height) +
|
||||
") exceeds max texture dim (" + std::to_string(maxDim) + ")");
|
||||
}
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
const uint32_t size = width * height * 4;
|
||||
const bgfx::Memory* mem = bgfx::copy(rgba, size);
|
||||
return bgfx::createTexture2D(static_cast<uint16_t>(width),
|
||||
|
||||
@@ -179,6 +179,7 @@ private:
|
||||
uint64_t frameIndex_ = 0;
|
||||
size_t maxTextCacheEntries_ = 256;
|
||||
size_t maxSvgCacheEntries_ = 64;
|
||||
uint32_t maxTextureDim_ = 0;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
|
||||
@@ -35,8 +35,8 @@ int64_t GetSteadyClockNs() {
|
||||
// Static instance for signal handler
|
||||
CrashRecoveryService* CrashRecoveryService::instance_ = nullptr;
|
||||
|
||||
CrashRecoveryService::CrashRecoveryService(std::shared_ptr<ILogger> logger)
|
||||
: logger_(logger)
|
||||
CrashRecoveryService::CrashRecoveryService(std::shared_ptr<ILogger> logger, CrashRecoveryConfig config)
|
||||
: logger_(std::move(logger))
|
||||
, crashDetected_(false)
|
||||
, lastSignal_(0)
|
||||
, lastHeartbeatNs_(0)
|
||||
@@ -48,10 +48,19 @@ CrashRecoveryService::CrashRecoveryService(std::shared_ptr<ILogger> logger)
|
||||
, fileFormatErrors_(0)
|
||||
, memoryWarnings_(0)
|
||||
, lastHealthCheck_(std::chrono::steady_clock::now())
|
||||
, signalHandlersInstalled_(false) {
|
||||
logger_->Trace("CrashRecoveryService", "CrashRecoveryService",
|
||||
"logger=" + std::string(logger_ ? "set" : "null"),
|
||||
"Created");
|
||||
, signalHandlersInstalled_(false)
|
||||
, config_(std::move(config)) {
|
||||
heartbeatTimeout_ = std::chrono::milliseconds(config_.heartbeatTimeoutMs);
|
||||
heartbeatPollInterval_ = std::chrono::milliseconds(config_.heartbeatPollIntervalMs);
|
||||
memoryLimitBytes_ = config_.memoryLimitMB * 1024 * 1024;
|
||||
|
||||
if (logger_) {
|
||||
logger_->Trace("CrashRecoveryService", "CrashRecoveryService",
|
||||
"logger=" + std::string(logger_ ? "set" : "null") +
|
||||
", heartbeatTimeoutMs=" + std::to_string(config_.heartbeatTimeoutMs) +
|
||||
", heartbeatPollIntervalMs=" + std::to_string(config_.heartbeatPollIntervalMs) +
|
||||
", memoryLimitMB=" + std::to_string(config_.memoryLimitMB));
|
||||
}
|
||||
}
|
||||
|
||||
CrashRecoveryService::~CrashRecoveryService() {
|
||||
@@ -69,8 +78,17 @@ void CrashRecoveryService::Initialize() {
|
||||
heartbeatSeen_ = false;
|
||||
crashReport_.clear();
|
||||
|
||||
if (!heartbeatMonitorRunning_.exchange(true)) {
|
||||
heartbeatMonitorThread_ = std::thread(&CrashRecoveryService::MonitorHeartbeats, this);
|
||||
const bool heartbeatEnabled = config_.heartbeatTimeoutMs > 0 && config_.heartbeatPollIntervalMs > 0;
|
||||
if (heartbeatEnabled) {
|
||||
if (!heartbeatMonitorRunning_.exchange(true)) {
|
||||
heartbeatMonitorThread_ = std::thread(&CrashRecoveryService::MonitorHeartbeats, this);
|
||||
}
|
||||
} else {
|
||||
heartbeatMonitorRunning_ = false;
|
||||
if (logger_) {
|
||||
logger_->Trace("CrashRecoveryService", "Initialize",
|
||||
"heartbeatEnabled=false", "Heartbeat monitor disabled via config");
|
||||
}
|
||||
}
|
||||
|
||||
logger_->Info("CrashRecoveryService::Initialize: Crash recovery service initialized");
|
||||
@@ -370,10 +388,16 @@ bool CrashRecoveryService::CheckGpuHealth(double lastFrameTime, double expectedF
|
||||
", expectedFrameTime=" + std::to_string(expectedFrameTime));
|
||||
UpdateHealthMetrics();
|
||||
|
||||
if (config_.gpuHangFrameTimeMultiplier <= 0.0 || config_.maxConsecutiveGpuTimeouts == 0) {
|
||||
consecutiveFrameTimeouts_ = 0;
|
||||
lastSuccessfulFrameTime_ = lastFrameTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for GPU hangs - if we haven't had a successful frame in too long
|
||||
if (lastFrameTime > expectedFrameTime * 10.0) { // 10x expected frame time
|
||||
if (lastFrameTime > expectedFrameTime * config_.gpuHangFrameTimeMultiplier) {
|
||||
consecutiveFrameTimeouts_++;
|
||||
if (consecutiveFrameTimeouts_ > 5) { // 5 consecutive timeouts
|
||||
if (consecutiveFrameTimeouts_ > config_.maxConsecutiveGpuTimeouts) {
|
||||
std::lock_guard<std::mutex> lock(crashMutex_);
|
||||
crashDetected_ = true;
|
||||
crashReport_ += "\nGPU HANG DETECTED: No successful frames for " +
|
||||
@@ -406,7 +430,7 @@ bool CrashRecoveryService::ValidateLuaExecution(bool scriptResult, const std::st
|
||||
logger_->Error("CrashRecoveryService::ValidateLuaExecution: Lua script '" +
|
||||
scriptName + "' failed to execute");
|
||||
|
||||
if (luaExecutionFailures_ > 3) { // Multiple Lua failures indicate a serious issue
|
||||
if (config_.maxLuaFailures > 0 && luaExecutionFailures_ > config_.maxLuaFailures) {
|
||||
crashDetected_ = true;
|
||||
crashReport_ += "CRITICAL: Multiple Lua execution failures detected\n";
|
||||
}
|
||||
@@ -442,7 +466,7 @@ bool CrashRecoveryService::ValidateFileFormat(const std::string& filePath, const
|
||||
logger_->Warn("CrashRecoveryService::ValidateFileFormat: Invalid format for " +
|
||||
filePath + " - expected " + expectedFormat + ", got " + extension);
|
||||
|
||||
if (fileFormatErrors_ > 2) { // Multiple format errors
|
||||
if (config_.maxFileFormatErrors > 0 && fileFormatErrors_ > config_.maxFileFormatErrors) {
|
||||
crashDetected_ = true;
|
||||
crashReport_ += "CRITICAL: Multiple file format errors detected\n";
|
||||
}
|
||||
@@ -476,13 +500,15 @@ bool CrashRecoveryService::CheckMemoryHealth() {
|
||||
logger_->Trace("CrashRecoveryService", "CheckMemoryHealth");
|
||||
UpdateHealthMetrics();
|
||||
|
||||
if (memoryLimitBytes_ == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t currentMemory = GetCurrentMemoryUsage();
|
||||
|
||||
// Basic memory usage check (this is a simplified version)
|
||||
// In a real implementation, you'd track memory growth over time
|
||||
const size_t MAX_MEMORY_MB = 1024; // 1GB limit for this example
|
||||
|
||||
if (currentMemory > MAX_MEMORY_MB * 1024 * 1024) {
|
||||
if (currentMemory > memoryLimitBytes_) {
|
||||
memoryWarnings_++;
|
||||
std::lock_guard<std::mutex> lock(crashMutex_);
|
||||
crashReport_ += "\nHIGH MEMORY USAGE: " + std::to_string(currentMemory / (1024*1024)) + " MB\n";
|
||||
@@ -490,7 +516,7 @@ bool CrashRecoveryService::CheckMemoryHealth() {
|
||||
logger_->Warn("CrashRecoveryService::CheckMemoryHealth: High memory usage detected: " +
|
||||
std::to_string(currentMemory / (1024*1024)) + " MB");
|
||||
|
||||
if (memoryWarnings_ > 3) {
|
||||
if (config_.maxMemoryWarnings > 0 && memoryWarnings_ > config_.maxMemoryWarnings) {
|
||||
crashDetected_ = true;
|
||||
crashReport_ += "CRITICAL: Persistent high memory usage detected\n";
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../interfaces/config_types.hpp"
|
||||
#include "../interfaces/i_crash_recovery_service.hpp"
|
||||
#include "../interfaces/i_logger.hpp"
|
||||
#include <atomic>
|
||||
@@ -21,7 +22,7 @@ namespace sdl3cpp::services::impl {
|
||||
*/
|
||||
class CrashRecoveryService : public ICrashRecoveryService {
|
||||
public:
|
||||
explicit CrashRecoveryService(std::shared_ptr<ILogger> logger);
|
||||
CrashRecoveryService(std::shared_ptr<ILogger> logger, CrashRecoveryConfig config);
|
||||
~CrashRecoveryService() override;
|
||||
|
||||
// ICrashRecoveryService interface
|
||||
@@ -65,6 +66,8 @@ private:
|
||||
std::chrono::milliseconds heartbeatPollInterval_{200};
|
||||
std::string crashReport_;
|
||||
mutable std::mutex crashMutex_;
|
||||
CrashRecoveryConfig config_{};
|
||||
size_t memoryLimitBytes_ = 0;
|
||||
|
||||
// Health monitoring state
|
||||
std::atomic<double> lastSuccessfulFrameTime_;
|
||||
|
||||
@@ -959,6 +959,79 @@ RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr<ILogger> logger,
|
||||
config.guiOpacity = static_cast<float>(value.GetDouble());
|
||||
}
|
||||
|
||||
auto readSizeT = [](const rapidjson::Value& parent,
|
||||
const char* name,
|
||||
const std::string& path,
|
||||
size_t& target) {
|
||||
if (!parent.HasMember(name)) {
|
||||
return;
|
||||
}
|
||||
const auto& value = parent[name];
|
||||
if (!value.IsNumber()) {
|
||||
throw std::runtime_error("JSON member '" + path + "." + std::string(name) + "' must be a number");
|
||||
}
|
||||
const double rawValue = value.GetDouble();
|
||||
if (rawValue < 0.0) {
|
||||
throw std::runtime_error("JSON member '" + path + "." + std::string(name) + "' must be non-negative");
|
||||
}
|
||||
target = static_cast<size_t>(rawValue);
|
||||
};
|
||||
|
||||
auto readUint32 = [](const rapidjson::Value& parent,
|
||||
const char* name,
|
||||
const std::string& path,
|
||||
uint32_t& target) {
|
||||
if (!parent.HasMember(name)) {
|
||||
return;
|
||||
}
|
||||
const auto& value = parent[name];
|
||||
if (!value.IsNumber()) {
|
||||
throw std::runtime_error("JSON member '" + path + "." + std::string(name) + "' must be a number");
|
||||
}
|
||||
const double rawValue = value.GetDouble();
|
||||
if (rawValue < 0.0) {
|
||||
throw std::runtime_error("JSON member '" + path + "." + std::string(name) + "' must be non-negative");
|
||||
}
|
||||
target = static_cast<uint32_t>(rawValue);
|
||||
};
|
||||
|
||||
const auto* budgetsValue = getObjectMember(document, "budgets", "budgets");
|
||||
if (budgetsValue) {
|
||||
readSizeT(*budgetsValue, "vram_mb", "budgets", config.budgets.vramMB);
|
||||
readUint32(*budgetsValue, "max_texture_dim", "budgets", config.budgets.maxTextureDim);
|
||||
readSizeT(*budgetsValue, "gui_text_cache_entries", "budgets", config.budgets.guiTextCacheEntries);
|
||||
readSizeT(*budgetsValue, "gui_svg_cache_entries", "budgets", config.budgets.guiSvgCacheEntries);
|
||||
}
|
||||
|
||||
const auto* crashRecoveryValue = getObjectMember(document, "crash_recovery", "crash_recovery");
|
||||
if (crashRecoveryValue) {
|
||||
readUint32(*crashRecoveryValue, "heartbeat_timeout_ms",
|
||||
"crash_recovery", config.crashRecovery.heartbeatTimeoutMs);
|
||||
readUint32(*crashRecoveryValue, "heartbeat_poll_interval_ms",
|
||||
"crash_recovery", config.crashRecovery.heartbeatPollIntervalMs);
|
||||
readSizeT(*crashRecoveryValue, "memory_limit_mb",
|
||||
"crash_recovery", config.crashRecovery.memoryLimitMB);
|
||||
readSizeT(*crashRecoveryValue, "max_consecutive_gpu_timeouts",
|
||||
"crash_recovery", config.crashRecovery.maxConsecutiveGpuTimeouts);
|
||||
readSizeT(*crashRecoveryValue, "max_lua_failures",
|
||||
"crash_recovery", config.crashRecovery.maxLuaFailures);
|
||||
readSizeT(*crashRecoveryValue, "max_file_format_errors",
|
||||
"crash_recovery", config.crashRecovery.maxFileFormatErrors);
|
||||
readSizeT(*crashRecoveryValue, "max_memory_warnings",
|
||||
"crash_recovery", config.crashRecovery.maxMemoryWarnings);
|
||||
if (crashRecoveryValue->HasMember("gpu_hang_frame_time_multiplier")) {
|
||||
const auto& value = (*crashRecoveryValue)["gpu_hang_frame_time_multiplier"];
|
||||
if (!value.IsNumber()) {
|
||||
throw std::runtime_error("JSON member 'crash_recovery.gpu_hang_frame_time_multiplier' must be a number");
|
||||
}
|
||||
const double rawValue = value.GetDouble();
|
||||
if (rawValue < 0.0) {
|
||||
throw std::runtime_error("JSON member 'crash_recovery.gpu_hang_frame_time_multiplier' must be non-negative");
|
||||
}
|
||||
config.crashRecovery.gpuHangFrameTimeMultiplier = rawValue;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1091,6 +1164,33 @@ std::string JsonConfigService::BuildConfigJson(const RuntimeConfig& config,
|
||||
renderingObject.AddMember("atmospherics", atmosphericsObject, allocator);
|
||||
document.AddMember("rendering", renderingObject, allocator);
|
||||
|
||||
rapidjson::Value budgetsObject(rapidjson::kObjectType);
|
||||
budgetsObject.AddMember("vram_mb", static_cast<uint64_t>(config.budgets.vramMB), allocator);
|
||||
budgetsObject.AddMember("max_texture_dim", config.budgets.maxTextureDim, allocator);
|
||||
budgetsObject.AddMember("gui_text_cache_entries",
|
||||
static_cast<uint64_t>(config.budgets.guiTextCacheEntries),
|
||||
allocator);
|
||||
budgetsObject.AddMember("gui_svg_cache_entries",
|
||||
static_cast<uint64_t>(config.budgets.guiSvgCacheEntries),
|
||||
allocator);
|
||||
document.AddMember("budgets", budgetsObject, allocator);
|
||||
|
||||
rapidjson::Value crashObject(rapidjson::kObjectType);
|
||||
crashObject.AddMember("heartbeat_timeout_ms", config.crashRecovery.heartbeatTimeoutMs, allocator);
|
||||
crashObject.AddMember("heartbeat_poll_interval_ms", config.crashRecovery.heartbeatPollIntervalMs, allocator);
|
||||
crashObject.AddMember("memory_limit_mb", static_cast<uint64_t>(config.crashRecovery.memoryLimitMB), allocator);
|
||||
crashObject.AddMember("gpu_hang_frame_time_multiplier",
|
||||
config.crashRecovery.gpuHangFrameTimeMultiplier, allocator);
|
||||
crashObject.AddMember("max_consecutive_gpu_timeouts",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxConsecutiveGpuTimeouts), allocator);
|
||||
crashObject.AddMember("max_lua_failures",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxLuaFailures), allocator);
|
||||
crashObject.AddMember("max_file_format_errors",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxFileFormatErrors), allocator);
|
||||
crashObject.AddMember("max_memory_warnings",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxMemoryWarnings), allocator);
|
||||
document.AddMember("crash_recovery", crashObject, allocator);
|
||||
|
||||
rapidjson::Value bindingsObject(rapidjson::kObjectType);
|
||||
auto addBindingMember = [&](const char* name, const std::string& value) {
|
||||
rapidjson::Value nameValue(name, allocator);
|
||||
|
||||
@@ -111,6 +111,18 @@ public:
|
||||
}
|
||||
return config_.guiFont;
|
||||
}
|
||||
const RenderBudgetConfig& GetRenderBudgetConfig() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetRenderBudgetConfig");
|
||||
}
|
||||
return config_.budgets;
|
||||
}
|
||||
const CrashRecoveryConfig& GetCrashRecoveryConfig() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetCrashRecoveryConfig");
|
||||
}
|
||||
return config_.crashRecovery;
|
||||
}
|
||||
const std::string& GetConfigJson() const override {
|
||||
if (logger_) {
|
||||
logger_->Trace("JsonConfigService", "GetConfigJson");
|
||||
|
||||
@@ -225,6 +225,33 @@ void JsonConfigWriterService::WriteConfig(const RuntimeConfig& config, const std
|
||||
renderingObject.AddMember("atmospherics", atmosphericsObject, allocator);
|
||||
document.AddMember("rendering", renderingObject, allocator);
|
||||
|
||||
rapidjson::Value budgetsObject(rapidjson::kObjectType);
|
||||
budgetsObject.AddMember("vram_mb", static_cast<uint64_t>(config.budgets.vramMB), allocator);
|
||||
budgetsObject.AddMember("max_texture_dim", config.budgets.maxTextureDim, allocator);
|
||||
budgetsObject.AddMember("gui_text_cache_entries",
|
||||
static_cast<uint64_t>(config.budgets.guiTextCacheEntries),
|
||||
allocator);
|
||||
budgetsObject.AddMember("gui_svg_cache_entries",
|
||||
static_cast<uint64_t>(config.budgets.guiSvgCacheEntries),
|
||||
allocator);
|
||||
document.AddMember("budgets", budgetsObject, allocator);
|
||||
|
||||
rapidjson::Value crashObject(rapidjson::kObjectType);
|
||||
crashObject.AddMember("heartbeat_timeout_ms", config.crashRecovery.heartbeatTimeoutMs, allocator);
|
||||
crashObject.AddMember("heartbeat_poll_interval_ms", config.crashRecovery.heartbeatPollIntervalMs, allocator);
|
||||
crashObject.AddMember("memory_limit_mb", static_cast<uint64_t>(config.crashRecovery.memoryLimitMB), allocator);
|
||||
crashObject.AddMember("gpu_hang_frame_time_multiplier",
|
||||
config.crashRecovery.gpuHangFrameTimeMultiplier, allocator);
|
||||
crashObject.AddMember("max_consecutive_gpu_timeouts",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxConsecutiveGpuTimeouts), allocator);
|
||||
crashObject.AddMember("max_lua_failures",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxLuaFailures), allocator);
|
||||
crashObject.AddMember("max_file_format_errors",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxFileFormatErrors), allocator);
|
||||
crashObject.AddMember("max_memory_warnings",
|
||||
static_cast<uint64_t>(config.crashRecovery.maxMemoryWarnings), allocator);
|
||||
document.AddMember("crash_recovery", crashObject, allocator);
|
||||
|
||||
rapidjson::Value guiObject(rapidjson::kObjectType);
|
||||
rapidjson::Value fontObject(rapidjson::kObjectType);
|
||||
fontObject.AddMember("use_freetype", config.guiFont.useFreeType, allocator);
|
||||
|
||||
@@ -120,6 +120,30 @@ struct GuiFontConfig {
|
||||
float fontSize = 18.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Resource budget and rendering limits.
|
||||
*/
|
||||
struct RenderBudgetConfig {
|
||||
size_t vramMB = 512;
|
||||
uint32_t maxTextureDim = 0;
|
||||
size_t guiTextCacheEntries = 256;
|
||||
size_t guiSvgCacheEntries = 64;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Crash recovery tuning parameters.
|
||||
*/
|
||||
struct CrashRecoveryConfig {
|
||||
uint32_t heartbeatTimeoutMs = 5000;
|
||||
uint32_t heartbeatPollIntervalMs = 200;
|
||||
size_t memoryLimitMB = 1024;
|
||||
double gpuHangFrameTimeMultiplier = 10.0;
|
||||
size_t maxConsecutiveGpuTimeouts = 5;
|
||||
size_t maxLuaFailures = 3;
|
||||
size_t maxFileFormatErrors = 2;
|
||||
size_t maxMemoryWarnings = 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Runtime configuration values used across services.
|
||||
*/
|
||||
@@ -135,8 +159,10 @@ struct RuntimeConfig {
|
||||
BgfxConfig bgfx{};
|
||||
MaterialXConfig materialX{};
|
||||
std::vector<MaterialXMaterialConfig> materialXMaterials{};
|
||||
RenderBudgetConfig budgets{};
|
||||
GuiFontConfig guiFont{};
|
||||
float guiOpacity = 1.0f;
|
||||
CrashRecoveryConfig crashRecovery{};
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services
|
||||
|
||||
@@ -85,6 +85,18 @@ public:
|
||||
*/
|
||||
virtual const GuiFontConfig& GetGuiFontConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get rendering budgets.
|
||||
* @return Render budget configuration
|
||||
*/
|
||||
virtual const RenderBudgetConfig& GetRenderBudgetConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get crash recovery configuration.
|
||||
* @return Crash recovery configuration
|
||||
*/
|
||||
virtual const CrashRecoveryConfig& GetCrashRecoveryConfig() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the full JSON configuration as a string.
|
||||
*
|
||||
|
||||
@@ -148,6 +148,8 @@ public:
|
||||
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 std::string& GetConfigJson() const override { return configJson_; }
|
||||
|
||||
void DisableFreeType() {
|
||||
@@ -173,6 +175,8 @@ private:
|
||||
sdl3cpp::services::MaterialXConfig materialXConfig_{};
|
||||
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
|
||||
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
|
||||
sdl3cpp::services::RenderBudgetConfig budgets_{};
|
||||
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
|
||||
std::string configJson_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ public:
|
||||
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 std::string& GetConfigJson() const override { return configJson_; }
|
||||
|
||||
private:
|
||||
@@ -125,6 +127,8 @@ private:
|
||||
sdl3cpp::services::MaterialXConfig materialXConfig_{};
|
||||
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
|
||||
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
|
||||
sdl3cpp::services::RenderBudgetConfig budgets_{};
|
||||
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
|
||||
std::string configJson_{};
|
||||
};
|
||||
|
||||
@@ -154,6 +158,8 @@ public:
|
||||
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 std::string& GetConfigJson() const override { return configJson_; }
|
||||
|
||||
private:
|
||||
@@ -170,6 +176,8 @@ private:
|
||||
sdl3cpp::services::MaterialXConfig materialXConfig_{};
|
||||
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
|
||||
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
|
||||
sdl3cpp::services::RenderBudgetConfig budgets_{};
|
||||
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
|
||||
std::string renderer_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user