ADD: engine wrapper
This commit is contained in:
174
docs/GameAPI.md
174
docs/GameAPI.md
@@ -10,6 +10,180 @@ For details on the underlying systems, see also:
|
||||
|
||||
---
|
||||
|
||||
## `GameAPI::Engine` (High‑Level Game Wrapper)
|
||||
|
||||
Header: `src/core/game_api.h`
|
||||
Implementation: `src/core/game_api.cpp`
|
||||
|
||||
`GameAPI::Engine` is a thin, game‑friendly wrapper around `VulkanEngine`. It exposes stable, snake_case methods grouped by responsibility:
|
||||
|
||||
- Texture streaming and VRAM budget.
|
||||
- Shadows and reflections.
|
||||
- IBL volumes.
|
||||
- Instances and animation.
|
||||
- Post‑processing (tonemap, bloom, FXAA).
|
||||
- Camera control.
|
||||
- Picking and render‑graph pass toggles.
|
||||
|
||||
Typical creation:
|
||||
|
||||
```cpp
|
||||
#include "core/engine.h"
|
||||
#include "core/game_api.h"
|
||||
|
||||
VulkanEngine engine;
|
||||
engine.init();
|
||||
|
||||
GameAPI::Engine api(&engine); // non‑owning
|
||||
```
|
||||
|
||||
You then call `api.*` from your game loop to spawn content and tweak settings.
|
||||
|
||||
### Texture Streaming & VRAM Budget
|
||||
|
||||
Relevant methods:
|
||||
|
||||
- `size_t get_texture_budget() const;`
|
||||
- `void set_texture_loads_per_frame(int count);`
|
||||
- `void set_texture_upload_budget(size_t bytes);`
|
||||
- `void set_cpu_source_budget(size_t bytes);`
|
||||
- `void set_max_upload_dimension(uint32_t dim);`
|
||||
- `void set_keep_source_bytes(bool keep);`
|
||||
- `void evict_textures_to_budget();`
|
||||
|
||||
At a lower level, `VulkanEngine::query_texture_budget_bytes()` computes a conservative per‑frame texture budget using VMA heap info and constants in `src/core/config.h`:
|
||||
|
||||
- `kTextureBudgetFraction` – fraction of total device‑local VRAM reserved for streamed textures (default `0.35`).
|
||||
- `kTextureBudgetFallbackBytes` – fallback budget when memory properties are unavailable (default `512 MiB`).
|
||||
- `kTextureBudgetMinBytes` – minimum budget clamp (default `128 MiB`).
|
||||
|
||||
To globally change how aggressive streaming can be, edit these constants in `config.h` and rebuild. Use the `GameAPI::Engine` setters for per‑scene tuning (e.g. reducing upload bandwidth on low‑end machines).
|
||||
|
||||
### Shadows: Resolution, Quality, and RT Modes
|
||||
|
||||
Shadows are controlled by a combination of:
|
||||
|
||||
- Global settings in `EngineContext::shadowSettings`.
|
||||
- Config constants in `src/core/config.h`.
|
||||
- The `ShadowPass` render pass and lighting shader (`shadow.vert`, `deferred_lighting.frag`).
|
||||
|
||||
High‑level game‑side controls:
|
||||
|
||||
- `void set_shadows_enabled(bool enabled);`
|
||||
- `void set_shadow_mode(ShadowMode mode);`
|
||||
- `ClipmapOnly` – cascaded shadow maps only.
|
||||
- `ClipmapPlusRT` – cascades + optional ray‑query assist.
|
||||
- `RTOnly` – ray‑traced shadows only (no raster maps).
|
||||
- `void set_hybrid_ray_cascade_mask(uint32_t mask);`
|
||||
- `void set_hybrid_ray_threshold(float threshold);`
|
||||
|
||||
These map directly onto `EngineContext::shadowSettings` and are also visualized in the ImGui “Shadows / Ray Query” tab.
|
||||
|
||||
#### Shadow Map Resolution (`kShadowMapResolution`)
|
||||
|
||||
The shadow map resolution is driven by `kShadowMapResolution` in `src/core/config.h`:
|
||||
|
||||
- Used for:
|
||||
- The actual depth image size created for each cascaded shadow map in `VulkanEngine::draw()` (`shadowExtent`).
|
||||
- Texel snapping for cascade stabilization in `SceneManager::update_scene()`:
|
||||
- `texel = (2.0f * cover) / float(kShadowMapResolution);`
|
||||
- Default: `2048.0f`, which gives a good compromise between quality and VRAM usage on mid‑range GPUs.
|
||||
|
||||
Increasing `kShadowMapResolution` has two important effects:
|
||||
|
||||
- **VRAM cost grows quadratically.**
|
||||
- Depth D32F, per cascade:
|
||||
- 2048 → ~16 MB.
|
||||
- 4096 → ~64 MB.
|
||||
- With 4 cascades, 4096×4096 can consume ~256 MB just for shadow depth, on top of swapchain, HDR, G‑buffers, IBL, and other images.
|
||||
- **Allocation failures can effectively “kill” shadows.**
|
||||
- All shadow maps are created as transient RenderGraph images each frame run.
|
||||
- If VMA runs out of suitable device‑local memory, `vmaCreateImage` will fail, and the engine will assert via `VK_CHECK`. In practice (especially in a release build), this often manifests as:
|
||||
- No shadow rendering, or
|
||||
- The app aborting when the first frame tries to allocate these images.
|
||||
|
||||
Practical guidance:
|
||||
|
||||
- Prefer 2048 or 3072 on consumer hardware unless you have headroom and have profiled memory.
|
||||
- If you push to 4096 and shadows “disappear”, suspect VRAM pressure:
|
||||
- Try reducing `kTextureBudgetFraction` so textures use less VRAM.
|
||||
- Or bring `kShadowMapResolution` back down and re‑test.
|
||||
|
||||
The following quality‑related shadow constants also live in `config.h`:
|
||||
|
||||
- `kShadowCascadeCount`, `kShadowCSMFar`, `kShadowCascadeRadiusScale`, `kShadowCascadeRadiusMargin`.
|
||||
- `kShadowBorderSmoothNDC`, `kShadowPCFBaseRadius`, `kShadowPCFCascadeGain`.
|
||||
- `kShadowDepthBiasConstant`, `kShadowDepthBiasSlope`.
|
||||
|
||||
These affect how cascades are distributed and how soft/filtered the resulting shadows are. Changing them is safe but should be tested against your content and FOV ranges.
|
||||
|
||||
### Reflections and Post‑Processing
|
||||
|
||||
Game‑side reflection controls:
|
||||
|
||||
- `void set_ssr_enabled(bool enabled);`
|
||||
- `void set_reflection_mode(ReflectionMode mode);`
|
||||
(`SSROnly`, `SSRPlusRT`, `RTOnly`)
|
||||
|
||||
Tone mapping and bloom:
|
||||
|
||||
- `void set_exposure(float exposure);`
|
||||
- `void set_tonemap_operator(TonemapOperator op);` (`Reinhard`, `ACES`)
|
||||
- `void set_bloom_enabled(bool enabled);`
|
||||
- `void set_bloom_threshold(float threshold);`
|
||||
- `void set_bloom_intensity(float intensity);`
|
||||
|
||||
These wrap `TonemapPass` parameters and are equivalent to flipping the corresponding ImGui controls at runtime.
|
||||
|
||||
FXAA:
|
||||
|
||||
- `void set_fxaa_enabled(bool enabled);`
|
||||
- `void set_fxaa_edge_threshold(float threshold);`
|
||||
- `void set_fxaa_edge_threshold_min(float threshold);`
|
||||
|
||||
### Camera and Render Scale
|
||||
|
||||
Camera:
|
||||
|
||||
- `void set_camera_position(const glm::vec3 &position);`
|
||||
- `glm::vec3 get_camera_position() const;`
|
||||
- `void set_camera_rotation(float pitchDeg, float yawDeg);`
|
||||
- `void get_camera_rotation(float &pitchDeg, float &yawDeg) const;`
|
||||
- `void set_camera_fov(float fovDegrees);`
|
||||
- `float get_camera_fov() const;`
|
||||
- `void camera_look_at(const glm::vec3 &target);`
|
||||
|
||||
These functions internally manipulate the quaternion‑based `Camera::orientation` and `position` in `SceneManager`. They respect the engine’s `-Z` forward convention.
|
||||
|
||||
Render resolution scaling:
|
||||
|
||||
- `void set_render_scale(float scale); // 0.3–1.0`
|
||||
- `float get_render_scale() const;`
|
||||
|
||||
This scales the internal draw extent relative to the swapchain and main HDR image sizes, trading resolution for performance.
|
||||
|
||||
### Picking & Pass Toggles
|
||||
|
||||
Picking:
|
||||
|
||||
- `Engine::PickResult get_last_pick() const;`
|
||||
- `void set_use_id_buffer_picking(bool use);`
|
||||
- `bool get_use_id_buffer_picking() const;`
|
||||
|
||||
These mirror `VulkanEngine::get_last_pick()` and `_useIdBufferPicking`, letting you choose between:
|
||||
|
||||
- CPU raycast picking (immediate, cheaper VRAM).
|
||||
- ID‑buffer based picking (async, 1‑frame latency, robust for dense scenes).
|
||||
|
||||
Render‑graph pass toggles:
|
||||
|
||||
- `void set_pass_enabled(const std::string &passName, bool enabled);`
|
||||
- `bool get_pass_enabled(const std::string &passName) const;`
|
||||
|
||||
This writes into `VulkanEngine::_rgPassToggles` and is applied during RenderGraph compilation. It allows you to permanently disable or enable named passes (e.g. `"ShadowMap[0]"`, `"FXAA"`, `"SSR"`) from game code, not just via the debug UI.
|
||||
|
||||
---
|
||||
|
||||
## VulkanEngine Helpers
|
||||
|
||||
Header: `src/core/engine.h`
|
||||
|
||||
@@ -10,6 +10,8 @@ add_executable (vulkan_engine
|
||||
core/engine.h
|
||||
core/engine.cpp
|
||||
core/engine_ui.cpp
|
||||
core/game_api.h
|
||||
core/game_api.cpp
|
||||
# core/device
|
||||
core/device/device.h
|
||||
core/device/device.cpp
|
||||
|
||||
@@ -38,11 +38,11 @@ inline constexpr float kShadowCascadeRadiusMargin = 10.0f;
|
||||
inline constexpr float kShadowClipBaseRadius = 20.0f;
|
||||
// When using dynamic pullback, compute it from the covered XY range of each level.
|
||||
// pullback = max(kShadowClipPullbackMin, cover * kShadowClipPullbackFactor)
|
||||
inline constexpr float kShadowClipPullbackFactor = 1.5f; // fraction of XY half-size behind center
|
||||
inline constexpr float kShadowClipForwardFactor = 1.5f; // fraction of XY half-size in front of center for zFar
|
||||
inline constexpr float kShadowClipPullbackMin = 40.0f; // lower bound on pullback so near levels don’t collapse
|
||||
inline constexpr float kShadowClipPullbackFactor = 1.2f; // fraction of XY half-size behind center
|
||||
inline constexpr float kShadowClipForwardFactor = 1.2f; // fraction of XY half-size in front of center for zFar
|
||||
inline constexpr float kShadowClipPullbackMin = 20.0f; // lower bound on pullback so near levels don’t collapse
|
||||
// Additional Z padding for the orthographic frustum along light direction
|
||||
inline constexpr float kShadowClipZPadding = 40.0f;
|
||||
inline constexpr float kShadowClipZPadding = 20.0f;
|
||||
|
||||
// Shadow quality & filtering
|
||||
// Soft cross-fade band between cascades in light-space NDC (0..1)
|
||||
@@ -55,3 +55,12 @@ inline constexpr float kShadowPCFCascadeGain = 2.0f;
|
||||
// Raster depth-bias parameters for shadow map rendering (tuned conservatively)
|
||||
inline constexpr float kShadowDepthBiasConstant = 1.25f;
|
||||
inline constexpr float kShadowDepthBiasSlope = 1.5f;
|
||||
|
||||
// Texture streaming / VRAM budget configuration
|
||||
// Fraction of total device-local VRAM reserved for streamed textures.
|
||||
// The remaining budget is left for attachments, swapchain images, meshes, AS, etc.
|
||||
inline constexpr double kTextureBudgetFraction = 0.35;
|
||||
// Fallback texture budget in bytes when Vulkan memory properties are unavailable.
|
||||
inline constexpr size_t kTextureBudgetFallbackBytes = 512ull * 1024ull * 1024ull;
|
||||
// Minimum texture budget clamp in bytes.
|
||||
inline constexpr size_t kTextureBudgetMinBytes = 128ull * 1024ull * 1024ull;
|
||||
|
||||
@@ -107,13 +107,13 @@ static void dump_vma_json(DeviceManager* dev, const char* tag)
|
||||
size_t VulkanEngine::query_texture_budget_bytes() const
|
||||
{
|
||||
DeviceManager *dev = _deviceManager.get();
|
||||
if (!dev) return 512ull * 1024ull * 1024ull; // fallback
|
||||
if (!dev) return kTextureBudgetFallbackBytes; // fallback
|
||||
VmaAllocator alloc = dev->allocator();
|
||||
if (!alloc) return 512ull * 1024ull * 1024ull;
|
||||
if (!alloc) return kTextureBudgetFallbackBytes;
|
||||
|
||||
const VkPhysicalDeviceMemoryProperties *memProps = nullptr;
|
||||
vmaGetMemoryProperties(alloc, &memProps);
|
||||
if (!memProps) return 512ull * 1024ull * 1024ull;
|
||||
if (!memProps) return kTextureBudgetFallbackBytes;
|
||||
|
||||
VmaBudget budgets[VK_MAX_MEMORY_HEAPS] = {};
|
||||
vmaGetHeapBudgets(alloc, budgets);
|
||||
@@ -128,14 +128,13 @@ size_t VulkanEngine::query_texture_budget_bytes() const
|
||||
totalUsage += budgets[i].usage;
|
||||
}
|
||||
}
|
||||
if (totalBudget == 0) return 512ull * 1024ull * 1024ull;
|
||||
if (totalBudget == 0) return kTextureBudgetFallbackBytes;
|
||||
|
||||
// Reserve ~65% of VRAM for attachments, swapchain, meshes, AS, etc.
|
||||
unsigned long long cap = static_cast<unsigned long long>(double(totalBudget) * 0.35);
|
||||
unsigned long long cap = static_cast<unsigned long long>(double(totalBudget) * kTextureBudgetFraction);
|
||||
|
||||
// If usage is already near the cap, still allow current textures to live; eviction will trim.
|
||||
// Clamp to at least 128 MB, at most totalBudget.
|
||||
unsigned long long minCap = 128ull * 1024ull * 1024ull;
|
||||
// Clamp to at least a minimum budget, at most totalBudget.
|
||||
unsigned long long minCap = static_cast<unsigned long long>(kTextureBudgetMinBytes);
|
||||
if (cap < minCap) cap = minCap;
|
||||
if (cap > totalBudget) cap = totalBudget;
|
||||
return static_cast<size_t>(cap);
|
||||
@@ -827,7 +826,9 @@ void VulkanEngine::draw()
|
||||
RGImageHandle hDebugColor = hDraw;
|
||||
|
||||
// Create transient depth targets for cascaded shadow maps (even if RT-only / disabled, to keep descriptors stable)
|
||||
const VkExtent2D shadowExtent{2048, 2048};
|
||||
const VkExtent2D shadowExtent{
|
||||
static_cast<uint32_t>(kShadowMapResolution),
|
||||
static_cast<uint32_t>(kShadowMapResolution)};
|
||||
std::array<RGImageHandle, kShadowCascadeCount> hShadowCascades{};
|
||||
for (int i = 0; i < kShadowCascadeCount; ++i)
|
||||
{
|
||||
|
||||
921
src/core/game_api.cpp
Normal file
921
src/core/game_api.cpp
Normal file
@@ -0,0 +1,921 @@
|
||||
#include "game_api.h"
|
||||
#include "engine.h"
|
||||
#include "context.h"
|
||||
#include "core/assets/texture_cache.h"
|
||||
#include "core/assets/ibl_manager.h"
|
||||
#include "core/pipeline/manager.h"
|
||||
#include "render/passes/tonemap.h"
|
||||
#include "render/passes/fxaa.h"
|
||||
#include "render/renderpass.h"
|
||||
#include "scene/vk_scene.h"
|
||||
#include "scene/camera.h"
|
||||
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
namespace GameAPI
|
||||
{
|
||||
|
||||
// ============================================================================
|
||||
// Transform helpers
|
||||
// ============================================================================
|
||||
|
||||
glm::mat4 Transform::to_matrix() const
|
||||
{
|
||||
glm::mat4 T = glm::translate(glm::mat4(1.0f), position);
|
||||
glm::mat4 R = glm::mat4_cast(rotation);
|
||||
glm::mat4 S = glm::scale(glm::mat4(1.0f), scale);
|
||||
return T * R * S;
|
||||
}
|
||||
|
||||
Transform Transform::from_matrix(const glm::mat4& m)
|
||||
{
|
||||
Transform t;
|
||||
glm::vec3 skew;
|
||||
glm::vec4 perspective;
|
||||
glm::decompose(m, t.scale, t.rotation, t.position, skew, perspective);
|
||||
return t;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Engine Implementation
|
||||
// ============================================================================
|
||||
|
||||
Engine::Engine(VulkanEngine* engine)
|
||||
: _engine(engine)
|
||||
{
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Memory / Texture Streaming
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
size_t Engine::get_texture_budget() const
|
||||
{
|
||||
return _engine->query_texture_budget_bytes();
|
||||
}
|
||||
|
||||
void Engine::set_texture_loads_per_frame(int count)
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
_engine->_textureCache->set_max_loads_per_pump(count);
|
||||
}
|
||||
}
|
||||
|
||||
int Engine::get_texture_loads_per_frame() const
|
||||
{
|
||||
return _engine->_textureCache ? _engine->_textureCache->max_loads_per_pump() : 0;
|
||||
}
|
||||
|
||||
void Engine::set_texture_upload_budget(size_t bytes)
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
_engine->_textureCache->set_max_bytes_per_pump(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Engine::get_texture_upload_budget() const
|
||||
{
|
||||
return _engine->_textureCache ? _engine->_textureCache->max_bytes_per_pump() : 0;
|
||||
}
|
||||
|
||||
void Engine::set_cpu_source_budget(size_t bytes)
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
_engine->_textureCache->set_cpu_source_budget(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Engine::get_cpu_source_budget() const
|
||||
{
|
||||
return _engine->_textureCache ? _engine->_textureCache->cpu_source_budget() : 0;
|
||||
}
|
||||
|
||||
void Engine::set_max_upload_dimension(uint32_t dim)
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
_engine->_textureCache->set_max_upload_dimension(dim);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Engine::get_max_upload_dimension() const
|
||||
{
|
||||
return _engine->_textureCache ? _engine->_textureCache->max_upload_dimension() : 0;
|
||||
}
|
||||
|
||||
void Engine::set_keep_source_bytes(bool keep)
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
_engine->_textureCache->set_keep_source_bytes(keep);
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::get_keep_source_bytes() const
|
||||
{
|
||||
return _engine->_textureCache ? _engine->_textureCache->keep_source_bytes() : false;
|
||||
}
|
||||
|
||||
void Engine::evict_textures_to_budget()
|
||||
{
|
||||
if (_engine->_textureCache)
|
||||
{
|
||||
size_t budget = _engine->query_texture_budget_bytes();
|
||||
_engine->_textureCache->evictToBudget(budget);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Shadows
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_shadows_enabled(bool enabled)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
_engine->_context->shadowSettings.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::get_shadows_enabled() const
|
||||
{
|
||||
return _engine->_context ? _engine->_context->shadowSettings.enabled : false;
|
||||
}
|
||||
|
||||
void Engine::set_shadow_mode(ShadowMode mode)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
// Guard against requesting RT modes on unsupported hardware.
|
||||
if (mode != ShadowMode::ClipmapOnly)
|
||||
{
|
||||
if (!_engine->_deviceManager
|
||||
|| !_engine->_deviceManager->supportsRayQuery()
|
||||
|| !_engine->_deviceManager->supportsAccelerationStructure())
|
||||
{
|
||||
mode = ShadowMode::ClipmapOnly;
|
||||
}
|
||||
}
|
||||
|
||||
_engine->_context->shadowSettings.mode = static_cast<uint32_t>(mode);
|
||||
_engine->_context->shadowSettings.hybridRayQueryEnabled =
|
||||
_engine->_context->shadowSettings.enabled && (mode != ShadowMode::ClipmapOnly);
|
||||
}
|
||||
}
|
||||
|
||||
ShadowMode Engine::get_shadow_mode() const
|
||||
{
|
||||
if (!_engine->_context) return ShadowMode::ClipmapOnly;
|
||||
return static_cast<ShadowMode>(_engine->_context->shadowSettings.mode);
|
||||
}
|
||||
|
||||
void Engine::set_hybrid_ray_cascade_mask(uint32_t mask)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
_engine->_context->shadowSettings.hybridRayCascadesMask = mask & 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Engine::get_hybrid_ray_cascade_mask() const
|
||||
{
|
||||
return _engine->_context ? _engine->_context->shadowSettings.hybridRayCascadesMask : 0;
|
||||
}
|
||||
|
||||
void Engine::set_hybrid_ray_threshold(float threshold)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
_engine->_context->shadowSettings.hybridRayNoLThreshold = glm::clamp(threshold, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_hybrid_ray_threshold() const
|
||||
{
|
||||
return _engine->_context ? _engine->_context->shadowSettings.hybridRayNoLThreshold : 0.25f;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// IBL (Image-Based Lighting)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static ::IBLPaths to_internal_ibl_paths(const IBLPaths& p)
|
||||
{
|
||||
::IBLPaths out;
|
||||
out.specularCube = p.specularCube;
|
||||
out.diffuseCube = p.diffuseCube;
|
||||
out.brdfLut2D = p.brdfLut;
|
||||
out.background2D = p.background;
|
||||
return out;
|
||||
}
|
||||
|
||||
static IBLPaths from_internal_ibl_paths(const ::IBLPaths& p)
|
||||
{
|
||||
IBLPaths out;
|
||||
out.specularCube = p.specularCube;
|
||||
out.diffuseCube = p.diffuseCube;
|
||||
out.brdfLut = p.brdfLut2D;
|
||||
out.background = p.background2D;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool Engine::load_global_ibl(const IBLPaths& paths)
|
||||
{
|
||||
if (!_engine->_iblManager) return false;
|
||||
|
||||
::IBLPaths internal = to_internal_ibl_paths(paths);
|
||||
_engine->_globalIBLPaths = internal;
|
||||
|
||||
if (_engine->_iblManager->load_async(internal))
|
||||
{
|
||||
_engine->_pendingIBLRequest.active = true;
|
||||
_engine->_pendingIBLRequest.targetVolume = -1;
|
||||
_engine->_pendingIBLRequest.paths = internal;
|
||||
_engine->_hasGlobalIBL = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IBLPaths Engine::get_global_ibl_paths() const
|
||||
{
|
||||
return from_internal_ibl_paths(_engine->_globalIBLPaths);
|
||||
}
|
||||
|
||||
void Engine::set_global_ibl_paths(const IBLPaths& paths)
|
||||
{
|
||||
_engine->_globalIBLPaths = to_internal_ibl_paths(paths);
|
||||
}
|
||||
|
||||
size_t Engine::add_ibl_volume(const IBLVolume& volume)
|
||||
{
|
||||
VulkanEngine::IBLVolume v;
|
||||
v.center = volume.center;
|
||||
v.halfExtents = volume.halfExtents;
|
||||
v.paths = to_internal_ibl_paths(volume.paths);
|
||||
v.enabled = volume.enabled;
|
||||
|
||||
_engine->_iblVolumes.push_back(v);
|
||||
return _engine->_iblVolumes.size() - 1;
|
||||
}
|
||||
|
||||
bool Engine::remove_ibl_volume(size_t index)
|
||||
{
|
||||
if (index >= _engine->_iblVolumes.size()) return false;
|
||||
|
||||
if (_engine->_activeIBLVolume == static_cast<int>(index))
|
||||
{
|
||||
_engine->_activeIBLVolume = -1;
|
||||
}
|
||||
else if (_engine->_activeIBLVolume > static_cast<int>(index))
|
||||
{
|
||||
_engine->_activeIBLVolume -= 1;
|
||||
}
|
||||
|
||||
_engine->_iblVolumes.erase(_engine->_iblVolumes.begin() + index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::get_ibl_volume(size_t index, IBLVolume& out) const
|
||||
{
|
||||
if (index >= _engine->_iblVolumes.size()) return false;
|
||||
|
||||
const auto& v = _engine->_iblVolumes[index];
|
||||
out.center = v.center;
|
||||
out.halfExtents = v.halfExtents;
|
||||
out.paths = from_internal_ibl_paths(v.paths);
|
||||
out.enabled = v.enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::set_ibl_volume(size_t index, const IBLVolume& volume)
|
||||
{
|
||||
if (index >= _engine->_iblVolumes.size()) return false;
|
||||
|
||||
auto& v = _engine->_iblVolumes[index];
|
||||
v.center = volume.center;
|
||||
v.halfExtents = volume.halfExtents;
|
||||
v.paths = to_internal_ibl_paths(volume.paths);
|
||||
v.enabled = volume.enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Engine::get_active_ibl_volume() const
|
||||
{
|
||||
return _engine->_activeIBLVolume;
|
||||
}
|
||||
|
||||
size_t Engine::get_ibl_volume_count() const
|
||||
{
|
||||
return _engine->_iblVolumes.size();
|
||||
}
|
||||
|
||||
void Engine::clear_ibl_volumes()
|
||||
{
|
||||
_engine->_iblVolumes.clear();
|
||||
_engine->_activeIBLVolume = -1;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Objects / Instances
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool Engine::add_gltf_instance(const std::string& name,
|
||||
const std::string& modelPath,
|
||||
const Transform& transform,
|
||||
bool preloadTextures)
|
||||
{
|
||||
return _engine->addGLTFInstance(name, modelPath, transform.to_matrix(), preloadTextures);
|
||||
}
|
||||
|
||||
uint32_t Engine::add_gltf_instance_async(const std::string& name,
|
||||
const std::string& modelPath,
|
||||
const Transform& transform,
|
||||
bool preloadTextures)
|
||||
{
|
||||
return _engine->loadGLTFAsync(name, modelPath, transform.to_matrix(), preloadTextures);
|
||||
}
|
||||
|
||||
bool Engine::remove_gltf_instance(const std::string& name)
|
||||
{
|
||||
return _engine->_sceneManager ? _engine->_sceneManager->removeGLTFInstance(name) : false;
|
||||
}
|
||||
|
||||
bool Engine::get_gltf_instance_transform(const std::string& name, Transform& out) const
|
||||
{
|
||||
if (!_engine->_sceneManager) return false;
|
||||
|
||||
glm::mat4 m;
|
||||
if (_engine->_sceneManager->getGLTFInstanceTransform(name, m))
|
||||
{
|
||||
out = Transform::from_matrix(m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Engine::set_gltf_instance_transform(const std::string& name, const Transform& transform)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setGLTFInstanceTransform(name, transform.to_matrix())
|
||||
: false;
|
||||
}
|
||||
|
||||
bool Engine::add_primitive_instance(const std::string& name,
|
||||
PrimitiveType type,
|
||||
const Transform& transform)
|
||||
{
|
||||
AssetManager::MeshGeometryDesc::Type geomType;
|
||||
switch (type)
|
||||
{
|
||||
case PrimitiveType::Cube: geomType = AssetManager::MeshGeometryDesc::Type::Cube; break;
|
||||
case PrimitiveType::Sphere: geomType = AssetManager::MeshGeometryDesc::Type::Sphere; break;
|
||||
case PrimitiveType::Plane: geomType = AssetManager::MeshGeometryDesc::Type::Plane; break;
|
||||
case PrimitiveType::Capsule: geomType = AssetManager::MeshGeometryDesc::Type::Capsule; break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
return _engine->addPrimitiveInstance(name, geomType, transform.to_matrix());
|
||||
}
|
||||
|
||||
bool Engine::remove_mesh_instance(const std::string& name)
|
||||
{
|
||||
return _engine->_sceneManager ? _engine->_sceneManager->removeMeshInstance(name) : false;
|
||||
}
|
||||
|
||||
bool Engine::get_mesh_instance_transform(const std::string& name, Transform& out) const
|
||||
{
|
||||
if (!_engine->_sceneManager) return false;
|
||||
|
||||
glm::mat4 m;
|
||||
if (_engine->_sceneManager->getMeshInstanceTransform(name, m))
|
||||
{
|
||||
out = Transform::from_matrix(m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Engine::set_mesh_instance_transform(const std::string& name, const Transform& transform)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setMeshInstanceTransform(name, transform.to_matrix())
|
||||
: false;
|
||||
}
|
||||
|
||||
void Engine::preload_instance_textures(const std::string& name)
|
||||
{
|
||||
_engine->preloadInstanceTextures(name);
|
||||
}
|
||||
|
||||
void Engine::clear_all_instances()
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
_engine->_sceneManager->clearGLTFInstances();
|
||||
_engine->_sceneManager->clearMeshInstances();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Animation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool Engine::set_instance_animation(const std::string& instanceName, int animationIndex, bool resetTime)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setGLTFInstanceAnimation(instanceName, animationIndex, resetTime)
|
||||
: false;
|
||||
}
|
||||
|
||||
bool Engine::set_instance_animation(const std::string& instanceName, const std::string& animationName, bool resetTime)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setGLTFInstanceAnimation(instanceName, animationName, resetTime)
|
||||
: false;
|
||||
}
|
||||
|
||||
bool Engine::set_instance_animation_loop(const std::string& instanceName, bool loop)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setGLTFInstanceAnimationLoop(instanceName, loop)
|
||||
: false;
|
||||
}
|
||||
|
||||
bool Engine::set_instance_node_offset(const std::string& instanceName, const std::string& nodeName, const glm::mat4& offset)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->setGLTFInstanceNodeOffset(instanceName, nodeName, offset)
|
||||
: false;
|
||||
}
|
||||
|
||||
bool Engine::clear_instance_node_offset(const std::string& instanceName, const std::string& nodeName)
|
||||
{
|
||||
return _engine->_sceneManager
|
||||
? _engine->_sceneManager->clearGLTFInstanceNodeOffset(instanceName, nodeName)
|
||||
: false;
|
||||
}
|
||||
|
||||
void Engine::clear_all_instance_node_offsets(const std::string& instanceName)
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
_engine->_sceneManager->clearGLTFInstanceNodeOffsets(instanceName);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Lighting
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
size_t Engine::add_point_light(const PointLight& light)
|
||||
{
|
||||
if (!_engine->_sceneManager) return 0;
|
||||
|
||||
SceneManager::PointLight pl;
|
||||
pl.position = light.position;
|
||||
pl.radius = light.radius;
|
||||
pl.color = light.color;
|
||||
pl.intensity = light.intensity;
|
||||
|
||||
size_t idx = _engine->_sceneManager->getPointLightCount();
|
||||
_engine->_sceneManager->addPointLight(pl);
|
||||
return idx;
|
||||
}
|
||||
|
||||
bool Engine::remove_point_light(size_t index)
|
||||
{
|
||||
return _engine->_sceneManager ? _engine->_sceneManager->removePointLight(index) : false;
|
||||
}
|
||||
|
||||
bool Engine::get_point_light(size_t index, PointLight& out) const
|
||||
{
|
||||
if (!_engine->_sceneManager) return false;
|
||||
|
||||
SceneManager::PointLight pl;
|
||||
if (_engine->_sceneManager->getPointLight(index, pl))
|
||||
{
|
||||
out.position = pl.position;
|
||||
out.radius = pl.radius;
|
||||
out.color = pl.color;
|
||||
out.intensity = pl.intensity;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Engine::set_point_light(size_t index, const PointLight& light)
|
||||
{
|
||||
if (!_engine->_sceneManager) return false;
|
||||
|
||||
SceneManager::PointLight pl;
|
||||
pl.position = light.position;
|
||||
pl.radius = light.radius;
|
||||
pl.color = light.color;
|
||||
pl.intensity = light.intensity;
|
||||
|
||||
return _engine->_sceneManager->setPointLight(index, pl);
|
||||
}
|
||||
|
||||
size_t Engine::get_point_light_count() const
|
||||
{
|
||||
return _engine->_sceneManager ? _engine->_sceneManager->getPointLightCount() : 0;
|
||||
}
|
||||
|
||||
void Engine::clear_point_lights()
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
_engine->_sceneManager->clearPointLights();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Post Processing - FXAA
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_fxaa_enabled(bool enabled)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
fxaa->set_enabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::get_fxaa_enabled() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return false;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
return fxaa->enabled();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Engine::set_fxaa_edge_threshold(float threshold)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
fxaa->set_edge_threshold(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_fxaa_edge_threshold() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return 0.125f;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
return fxaa->edge_threshold();
|
||||
}
|
||||
return 0.125f;
|
||||
}
|
||||
|
||||
void Engine::set_fxaa_edge_threshold_min(float threshold)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
fxaa->set_edge_threshold_min(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_fxaa_edge_threshold_min() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return 0.0312f;
|
||||
if (auto* fxaa = _engine->_renderPassManager->getPass<FxaaPass>())
|
||||
{
|
||||
return fxaa->edge_threshold_min();
|
||||
}
|
||||
return 0.0312f;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Post Processing - SSR
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_ssr_enabled(bool enabled)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
_engine->_context->enableSSR = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::get_ssr_enabled() const
|
||||
{
|
||||
return _engine->_context ? _engine->_context->enableSSR : false;
|
||||
}
|
||||
|
||||
void Engine::set_reflection_mode(ReflectionMode mode)
|
||||
{
|
||||
if (_engine->_context)
|
||||
{
|
||||
// Guard against requesting RT reflection modes on unsupported hardware.
|
||||
if (mode != ReflectionMode::SSROnly)
|
||||
{
|
||||
if (!_engine->_deviceManager
|
||||
|| !_engine->_deviceManager->supportsRayQuery()
|
||||
|| !_engine->_deviceManager->supportsAccelerationStructure())
|
||||
{
|
||||
mode = ReflectionMode::SSROnly;
|
||||
}
|
||||
}
|
||||
|
||||
_engine->_context->reflectionMode = static_cast<uint32_t>(mode);
|
||||
}
|
||||
}
|
||||
|
||||
ReflectionMode Engine::get_reflection_mode() const
|
||||
{
|
||||
if (!_engine->_context) return ReflectionMode::SSROnly;
|
||||
return static_cast<ReflectionMode>(_engine->_context->reflectionMode);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Post Processing - Tonemapping
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_exposure(float exposure)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
tonemap->setExposure(exposure);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_exposure() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return 1.0f;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
return tonemap->exposure();
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void Engine::set_tonemap_operator(TonemapOperator op)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
tonemap->setMode(static_cast<int>(op));
|
||||
}
|
||||
}
|
||||
|
||||
TonemapOperator Engine::get_tonemap_operator() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return TonemapOperator::ACES;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
return static_cast<TonemapOperator>(tonemap->mode());
|
||||
}
|
||||
return TonemapOperator::ACES;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Post Processing - Bloom
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_bloom_enabled(bool enabled)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
tonemap->setBloomEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::get_bloom_enabled() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return false;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
return tonemap->bloomEnabled();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Engine::set_bloom_threshold(float threshold)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
tonemap->setBloomThreshold(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_bloom_threshold() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return 1.0f;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
return tonemap->bloomThreshold();
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void Engine::set_bloom_intensity(float intensity)
|
||||
{
|
||||
if (!_engine->_renderPassManager) return;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
tonemap->setBloomIntensity(intensity);
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_bloom_intensity() const
|
||||
{
|
||||
if (!_engine->_renderPassManager) return 0.7f;
|
||||
if (auto* tonemap = _engine->_renderPassManager->getPass<TonemapPass>())
|
||||
{
|
||||
return tonemap->bloomIntensity();
|
||||
}
|
||||
return 0.7f;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Camera
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_camera_position(const glm::vec3& position)
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
_engine->_sceneManager->getMainCamera().position = position;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 Engine::get_camera_position() const
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
return _engine->_sceneManager->getMainCamera().position;
|
||||
}
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void Engine::set_camera_rotation(float pitch, float yaw)
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
Camera& cam = _engine->_sceneManager->getMainCamera();
|
||||
|
||||
// Convert degrees to radians.
|
||||
float pitchRad = glm::radians(pitch);
|
||||
float yawRad = glm::radians(yaw);
|
||||
|
||||
// -Z forward convention: yaw around +Y, then pitch around local +X.
|
||||
glm::quat yawQ = glm::angleAxis(yawRad, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::quat pitchQ = glm::angleAxis(pitchRad, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
cam.orientation = glm::normalize(yawQ * pitchQ);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::get_camera_rotation(float& pitch, float& yaw) const
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
const Camera& cam = _engine->_sceneManager->getMainCamera();
|
||||
|
||||
// Derive forward from orientation and convert to pitch/yaw (degrees).
|
||||
glm::vec3 forward = glm::rotate(cam.orientation, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
forward = glm::normalize(forward);
|
||||
|
||||
pitch = glm::degrees(asinf(-forward.y));
|
||||
yaw = glm::degrees(atan2f(forward.x, forward.z));
|
||||
}
|
||||
else
|
||||
{
|
||||
pitch = 0.0f;
|
||||
yaw = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::set_camera_fov(float fovDegrees)
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
_engine->_sceneManager->getMainCamera().fovDegrees = fovDegrees;
|
||||
}
|
||||
}
|
||||
|
||||
float Engine::get_camera_fov() const
|
||||
{
|
||||
if (_engine->_sceneManager)
|
||||
{
|
||||
return _engine->_sceneManager->getMainCamera().fovDegrees;
|
||||
}
|
||||
return 70.0f;
|
||||
}
|
||||
|
||||
void Engine::camera_look_at(const glm::vec3& target)
|
||||
{
|
||||
if (!_engine->_sceneManager) return;
|
||||
|
||||
Camera& cam = _engine->_sceneManager->getMainCamera();
|
||||
glm::vec3 dir = glm::normalize(target - cam.position);
|
||||
|
||||
// For a -Z forward convention, build a quaternion that rotates -Z into dir.
|
||||
// Use glm's lookAt-style helper via matrices, then convert to a quaternion.
|
||||
glm::vec3 up(0.0f, 1.0f, 0.0f);
|
||||
if (glm::length2(glm::cross(dir, up)) < 1e-6f)
|
||||
{
|
||||
up = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
glm::vec3 f = dir;
|
||||
glm::vec3 r = glm::normalize(glm::cross(up, f));
|
||||
glm::vec3 u = glm::cross(f, r);
|
||||
|
||||
glm::mat3 rot;
|
||||
rot[0] = r;
|
||||
rot[1] = u;
|
||||
rot[2] = -f; // -Z forward
|
||||
|
||||
cam.orientation = glm::quat_cast(rot);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void Engine::set_render_scale(float scale)
|
||||
{
|
||||
_engine->renderScale = glm::clamp(scale, 0.3f, 1.0f);
|
||||
}
|
||||
|
||||
float Engine::get_render_scale() const
|
||||
{
|
||||
return _engine->renderScale;
|
||||
}
|
||||
|
||||
void Engine::set_pass_enabled(const std::string& passName, bool enabled)
|
||||
{
|
||||
_engine->_rgPassToggles[passName] = enabled;
|
||||
}
|
||||
|
||||
bool Engine::get_pass_enabled(const std::string& passName) const
|
||||
{
|
||||
auto it = _engine->_rgPassToggles.find(passName);
|
||||
if (it != _engine->_rgPassToggles.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return true; // Default to enabled if not in map
|
||||
}
|
||||
|
||||
void Engine::hot_reload_shaders()
|
||||
{
|
||||
if (_engine->_pipelineManager)
|
||||
{
|
||||
_engine->_pipelineManager->hotReloadChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Statistics
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Stats Engine::get_stats() const
|
||||
{
|
||||
Stats s;
|
||||
s.frametime = _engine->stats.frametime;
|
||||
s.drawTime = _engine->stats.mesh_draw_time;
|
||||
s.sceneUpdateTime = _engine->stats.scene_update_time;
|
||||
s.triangleCount = _engine->stats.triangle_count;
|
||||
s.drawCallCount = _engine->stats.drawcall_count;
|
||||
return s;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Picking / Selection
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Engine::PickResult Engine::get_last_pick() const
|
||||
{
|
||||
PickResult r;
|
||||
r.valid = _engine->_lastPick.valid;
|
||||
r.ownerName = _engine->_lastPick.ownerName;
|
||||
r.worldPosition = _engine->_lastPick.worldPos;
|
||||
return r;
|
||||
}
|
||||
|
||||
void Engine::set_use_id_buffer_picking(bool use)
|
||||
{
|
||||
_engine->_useIdBufferPicking = use;
|
||||
}
|
||||
|
||||
bool Engine::get_use_id_buffer_picking() const
|
||||
{
|
||||
return _engine->_useIdBufferPicking;
|
||||
}
|
||||
|
||||
} // namespace GameAPI
|
||||
377
src/core/game_api.h
Normal file
377
src/core/game_api.h
Normal file
@@ -0,0 +1,377 @@
|
||||
#pragma once
|
||||
|
||||
// GameAPI: High-level interface for game development
|
||||
// Wraps VulkanEngine internals and exposes clean, game-friendly functions.
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
class VulkanEngine;
|
||||
|
||||
namespace GameAPI
|
||||
{
|
||||
|
||||
// ============================================================================
|
||||
// Forward declarations and simple POD types
|
||||
// ============================================================================
|
||||
|
||||
// Shadow rendering mode
|
||||
enum class ShadowMode : uint32_t
|
||||
{
|
||||
ClipmapOnly = 0, // Raster shadow maps with PCF
|
||||
ClipmapPlusRT = 1, // Shadow maps + ray-traced assist at low N.L angles
|
||||
RTOnly = 2 // Pure ray-traced shadows (no shadow maps)
|
||||
};
|
||||
|
||||
// Reflection rendering mode
|
||||
enum class ReflectionMode : uint32_t
|
||||
{
|
||||
SSROnly = 0, // Screen-space reflections only
|
||||
SSRPlusRT = 1, // SSR with ray-traced fallback
|
||||
RTOnly = 2 // Pure ray-traced reflections
|
||||
};
|
||||
|
||||
// Tone mapping operator
|
||||
enum class TonemapOperator : int
|
||||
{
|
||||
Reinhard = 0,
|
||||
ACES = 1
|
||||
};
|
||||
|
||||
// Primitive geometry types
|
||||
enum class PrimitiveType
|
||||
{
|
||||
Cube,
|
||||
Sphere,
|
||||
Plane,
|
||||
Capsule
|
||||
};
|
||||
|
||||
// Point light data
|
||||
struct PointLight
|
||||
{
|
||||
glm::vec3 position{0.0f};
|
||||
float radius{10.0f};
|
||||
glm::vec3 color{1.0f};
|
||||
float intensity{1.0f};
|
||||
};
|
||||
|
||||
// IBL (Image-Based Lighting) paths
|
||||
struct IBLPaths
|
||||
{
|
||||
std::string specularCube; // .ktx2 specular cubemap
|
||||
std::string diffuseCube; // .ktx2 diffuse cubemap
|
||||
std::string brdfLut; // .ktx2 BRDF lookup table
|
||||
std::string background; // .ktx2 background (optional, falls back to specular)
|
||||
};
|
||||
|
||||
// IBL Volume (local reflection probe)
|
||||
struct IBLVolume
|
||||
{
|
||||
glm::vec3 center{0.0f};
|
||||
glm::vec3 halfExtents{10.0f};
|
||||
IBLPaths paths;
|
||||
bool enabled{true};
|
||||
};
|
||||
|
||||
// Transform decomposition
|
||||
struct Transform
|
||||
{
|
||||
glm::vec3 position{0.0f};
|
||||
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 scale{1.0f};
|
||||
|
||||
glm::mat4 to_matrix() const;
|
||||
static Transform from_matrix(const glm::mat4& m);
|
||||
};
|
||||
|
||||
// Engine statistics (read-only)
|
||||
struct Stats
|
||||
{
|
||||
float frametime{0.0f}; // ms
|
||||
float drawTime{0.0f}; // ms
|
||||
float sceneUpdateTime{0.0f}; // ms
|
||||
int triangleCount{0};
|
||||
int drawCallCount{0};
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Main API Class
|
||||
// ============================================================================
|
||||
|
||||
class Engine
|
||||
{
|
||||
public:
|
||||
explicit Engine(VulkanEngine* engine);
|
||||
~Engine() = default;
|
||||
|
||||
// Non-copyable
|
||||
Engine(const Engine&) = delete;
|
||||
Engine& operator=(const Engine&) = delete;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Memory / Texture Streaming
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Query current VRAM texture budget (bytes)
|
||||
size_t get_texture_budget() const;
|
||||
|
||||
// Set maximum textures loaded per frame (1-16)
|
||||
void set_texture_loads_per_frame(int count);
|
||||
int get_texture_loads_per_frame() const;
|
||||
|
||||
// Set upload budget per frame (bytes, e.g., 128*1024*1024 = 128 MiB)
|
||||
void set_texture_upload_budget(size_t bytes);
|
||||
size_t get_texture_upload_budget() const;
|
||||
|
||||
// Set CPU source data budget (bytes)
|
||||
void set_cpu_source_budget(size_t bytes);
|
||||
size_t get_cpu_source_budget() const;
|
||||
|
||||
// Set maximum upload dimension (clamps large textures)
|
||||
void set_max_upload_dimension(uint32_t dim);
|
||||
uint32_t get_max_upload_dimension() const;
|
||||
|
||||
// Keep CPU source data after GPU upload (useful for streaming)
|
||||
void set_keep_source_bytes(bool keep);
|
||||
bool get_keep_source_bytes() const;
|
||||
|
||||
// Force eviction to budget (call after loading large assets)
|
||||
void evict_textures_to_budget();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Shadows
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_shadows_enabled(bool enabled);
|
||||
bool get_shadows_enabled() const;
|
||||
|
||||
void set_shadow_mode(ShadowMode mode);
|
||||
ShadowMode get_shadow_mode() const;
|
||||
|
||||
// For hybrid mode: which cascades use ray assist (bitmask, bits 0-3)
|
||||
void set_hybrid_ray_cascade_mask(uint32_t mask);
|
||||
uint32_t get_hybrid_ray_cascade_mask() const;
|
||||
|
||||
// N.L threshold for hybrid ray shadows (0.0 - 1.0)
|
||||
void set_hybrid_ray_threshold(float threshold);
|
||||
float get_hybrid_ray_threshold() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// IBL (Image-Based Lighting)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Load global IBL asynchronously (returns false if failed to queue)
|
||||
bool load_global_ibl(const IBLPaths& paths);
|
||||
|
||||
// Get/set global IBL paths (does not trigger reload)
|
||||
IBLPaths get_global_ibl_paths() const;
|
||||
void set_global_ibl_paths(const IBLPaths& paths);
|
||||
|
||||
// Add a local IBL volume (returns volume index)
|
||||
size_t add_ibl_volume(const IBLVolume& volume);
|
||||
|
||||
// Remove IBL volume by index
|
||||
bool remove_ibl_volume(size_t index);
|
||||
|
||||
// Get/set IBL volume properties
|
||||
bool get_ibl_volume(size_t index, IBLVolume& out) const;
|
||||
bool set_ibl_volume(size_t index, const IBLVolume& volume);
|
||||
|
||||
// Get current active IBL volume index (-1 = global)
|
||||
int get_active_ibl_volume() const;
|
||||
|
||||
// Get IBL volume count
|
||||
size_t get_ibl_volume_count() const;
|
||||
|
||||
// Clear all IBL volumes
|
||||
void clear_ibl_volumes();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Objects / Instances
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Add glTF model instance (path relative to assets/models/)
|
||||
bool add_gltf_instance(const std::string& name,
|
||||
const std::string& modelPath,
|
||||
const Transform& transform = {},
|
||||
bool preloadTextures = true);
|
||||
|
||||
// Add glTF model asynchronously (returns job ID, 0 on failure)
|
||||
uint32_t add_gltf_instance_async(const std::string& name,
|
||||
const std::string& modelPath,
|
||||
const Transform& transform = {},
|
||||
bool preloadTextures = true);
|
||||
|
||||
// Remove glTF instance
|
||||
bool remove_gltf_instance(const std::string& name);
|
||||
|
||||
// Get/set glTF instance transform
|
||||
bool get_gltf_instance_transform(const std::string& name, Transform& out) const;
|
||||
bool set_gltf_instance_transform(const std::string& name, const Transform& transform);
|
||||
|
||||
// Add primitive mesh instance
|
||||
bool add_primitive_instance(const std::string& name,
|
||||
PrimitiveType type,
|
||||
const Transform& transform = {});
|
||||
|
||||
// Remove mesh instance (primitives or custom meshes)
|
||||
bool remove_mesh_instance(const std::string& name);
|
||||
|
||||
// Get/set mesh instance transform
|
||||
bool get_mesh_instance_transform(const std::string& name, Transform& out) const;
|
||||
bool set_mesh_instance_transform(const std::string& name, const Transform& transform);
|
||||
|
||||
// Preload textures for an instance (useful before it becomes visible)
|
||||
void preload_instance_textures(const std::string& name);
|
||||
|
||||
// Clear all dynamic instances
|
||||
void clear_all_instances();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Animation
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Set animation by index for a glTF instance (-1 to disable)
|
||||
bool set_instance_animation(const std::string& instanceName, int animationIndex, bool resetTime = true);
|
||||
|
||||
// Set animation by name for a glTF instance
|
||||
bool set_instance_animation(const std::string& instanceName, const std::string& animationName, bool resetTime = true);
|
||||
|
||||
// Set animation looping for a glTF instance
|
||||
bool set_instance_animation_loop(const std::string& instanceName, bool loop);
|
||||
|
||||
// Per-node transform offset (local space, layered on animation)
|
||||
bool set_instance_node_offset(const std::string& instanceName, const std::string& nodeName, const glm::mat4& offset);
|
||||
bool clear_instance_node_offset(const std::string& instanceName, const std::string& nodeName);
|
||||
void clear_all_instance_node_offsets(const std::string& instanceName);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Lighting
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Add point light (returns index)
|
||||
size_t add_point_light(const PointLight& light);
|
||||
|
||||
// Remove point light by index
|
||||
bool remove_point_light(size_t index);
|
||||
|
||||
// Get/set point light properties
|
||||
bool get_point_light(size_t index, PointLight& out) const;
|
||||
bool set_point_light(size_t index, const PointLight& light);
|
||||
|
||||
// Get point light count
|
||||
size_t get_point_light_count() const;
|
||||
|
||||
// Clear all point lights
|
||||
void clear_point_lights();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Post Processing - FXAA
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_fxaa_enabled(bool enabled);
|
||||
bool get_fxaa_enabled() const;
|
||||
|
||||
void set_fxaa_edge_threshold(float threshold);
|
||||
float get_fxaa_edge_threshold() const;
|
||||
|
||||
void set_fxaa_edge_threshold_min(float threshold);
|
||||
float get_fxaa_edge_threshold_min() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Post Processing - SSR (Screen Space Reflections)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_ssr_enabled(bool enabled);
|
||||
bool get_ssr_enabled() const;
|
||||
|
||||
void set_reflection_mode(ReflectionMode mode);
|
||||
ReflectionMode get_reflection_mode() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Post Processing - Tonemapping
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_exposure(float exposure);
|
||||
float get_exposure() const;
|
||||
|
||||
void set_tonemap_operator(TonemapOperator op);
|
||||
TonemapOperator get_tonemap_operator() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Post Processing - Bloom
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_bloom_enabled(bool enabled);
|
||||
bool get_bloom_enabled() const;
|
||||
|
||||
void set_bloom_threshold(float threshold);
|
||||
float get_bloom_threshold() const;
|
||||
|
||||
void set_bloom_intensity(float intensity);
|
||||
float get_bloom_intensity() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Camera
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_camera_position(const glm::vec3& position);
|
||||
glm::vec3 get_camera_position() const;
|
||||
|
||||
void set_camera_rotation(float pitch, float yaw);
|
||||
void get_camera_rotation(float& pitch, float& yaw) const;
|
||||
|
||||
void set_camera_fov(float fovDegrees);
|
||||
float get_camera_fov() const;
|
||||
|
||||
// Look at a target position
|
||||
void camera_look_at(const glm::vec3& target);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void set_render_scale(float scale); // 0.3 - 1.0
|
||||
float get_render_scale() const;
|
||||
|
||||
// Enable/disable specific render passes by name
|
||||
void set_pass_enabled(const std::string& passName, bool enabled);
|
||||
bool get_pass_enabled(const std::string& passName) const;
|
||||
|
||||
// Hot reload all changed shaders
|
||||
void hot_reload_shaders();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Statistics (read-only)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
Stats get_stats() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Picking / Selection
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
struct PickResult
|
||||
{
|
||||
bool valid{false};
|
||||
std::string ownerName;
|
||||
glm::vec3 worldPosition{0.0f};
|
||||
};
|
||||
|
||||
// Get last click selection result
|
||||
PickResult get_last_pick() const;
|
||||
|
||||
// Enable/disable ID buffer picking (vs CPU raycast)
|
||||
void set_use_id_buffer_picking(bool use);
|
||||
bool get_use_id_buffer_picking() const;
|
||||
|
||||
private:
|
||||
VulkanEngine* _engine;
|
||||
};
|
||||
|
||||
} // namespace GameAPI
|
||||
Reference in New Issue
Block a user