ADD: engine wrapper

This commit is contained in:
2025-12-10 01:04:24 +09:00
parent 4880e1a992
commit c61c42f359
6 changed files with 1497 additions and 13 deletions

View File

@@ -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

View File

@@ -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 dont 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 dont 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;

View File

@@ -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
View 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
View 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