16 KiB
Game‑Facing API Overview
This document summarizes the main engine APIs that are directly useful when building a game (spawn actors, control lights/animations, and interact via picking).
For details on the underlying systems, see also:
docs/Scene.md– cameras, draw context, instances, picking.docs/EngineContext.md– access to managers and per‑frame state.docs/RenderGraph.md– render‑graph API for custom passes.
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:
#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 (default0.35).kTextureBudgetFallbackBytes– fallback budget when memory properties are unavailable (default512 MiB).kTextureBudgetMinBytes– minimum budget clamp (default128 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
ShadowPassrender 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);
- The actual depth image size created for each cascaded shadow map in
- 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.
- Depth D32F, per cascade:
- 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,
vmaCreateImagewill fail, and the engine will assert viaVK_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
kTextureBudgetFractionso textures use less VRAM. - Or bring
kShadowMapResolutionback down and re‑test.
- Try reducing
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.0float 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
-
Lifecycle
void init()– initialize SDL, device, managers, scene, render graph.void run()– main loop: handles events, updates scene, builds Render Graph, submits frames.void cleanup()– destroy managers and GPU resources.
-
GLTF spawn helper
bool addGLTFInstance(const std::string &instanceName, const std::string &modelRelativePath, const glm::mat4 &transform = glm::mat4(1.f));- Loads a glTF from
assets/models/...(viaAssetManager) and registers it as a runtime scene instance. instanceNamebecomes the logical name used bySceneManager(e.g. for picking and animation control).
- Loads a glTF from
-
Picking access
struct PickInfo(nested inVulkanEngine)MeshAsset *mesh,LoadedGLTF *scene,Node *nodeRenderObject::OwnerType ownerType(e.g.StaticGLTF,GLTFInstance,MeshInstance)std::string ownerNameglm::vec3 worldPosglm::mat4 worldTransformuint32_t indexCount,firstIndex,surfaceIndexbool valid
const PickInfo &get_last_pick() const- Returns the last click selection result.
- Filled by the engine from either CPU ray picking or ID‑buffer picking depending on
_useIdBufferPicking. - Typical usage:
- On mouse‑up in your game layer, read
engine->get_last_pick()and, ifvalid, useownerName/worldPosto drive selection logic.
- On mouse‑up in your game layer, read
Note: hover picks and drag selections are also available as internal fields on
VulkanEngine(_hoverPick,_dragSelection) and are documented indocs/Scene.md. You can expose additional getters if you want to rely on them from game code.
Scene & Instances (Actors, Lights, Animations)
Header: src/scene/vk_scene.h
Docs: docs/Scene.md
Dynamic Mesh/GLTF Instances
-
Mesh instances
void addMeshInstance(const std::string &name, std::shared_ptr<MeshAsset> mesh, const glm::mat4 &transform = glm::mat4(1.f), std::optional<BoundsType> boundsType = {});bool getMeshInstanceTransform(const std::string &name, glm::mat4 &outTransform);bool setMeshInstanceTransform(const std::string &name, const glm::mat4 &transform);bool removeMeshInstance(const std::string &name);void clearMeshInstances();- Typical usage:
- Spawn primitives or dynamic meshes at runtime (e.g. projectiles, props).
- Use
setMeshInstanceTransformevery frame to move them based on game logic.
-
GLTF instances (actors)
void addGLTFInstance(const std::string &name, std::shared_ptr<LoadedGLTF> scene, const glm::mat4 &transform = glm::mat4(1.f));bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform);bool setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform);bool removeGLTFInstance(const std::string &name);void clearGLTFInstances();- Usage pattern:
- Treat each GLTF instance as an “actor” with a name; use transforms to place characters, doors, props, etc.
Animations (GLTF)
- Scene‑level
bool setSceneAnimation(const std::string &sceneName, int animationIndex, bool resetTime = true);bool setSceneAnimation(const std::string &sceneName, const std::string &animationName, bool resetTime = true);bool setSceneAnimationLoop(const std::string &sceneName, bool loop);
- Instance‑level
bool setGLTFInstanceAnimation(const std::string &instanceName, int animationIndex, bool resetTime = true);bool setGLTFInstanceAnimation(const std::string &instanceName, const std::string &animationName, bool resetTime = true);bool setGLTFInstanceAnimationLoop(const std::string &instanceName, bool loop);
- Notes:
- All functions return
boolindicating whether the named scene/instance exists. - Animation state is independent per scene and per instance:
- Each named scene has its own
AnimationState. - Each glTF instance has its own
AnimationState, even when sharing the sameLoadedGLTF.
- Each named scene has its own
- An index
< 0(e.g.-1) disables animation for that scene/instance (pose is frozen at the last evaluated state). SceneManager::update_scene()advances each active animation state every frame using engine delta time.
- All functions return
Per‑Instance Node / Joint Control (Non‑Skinned)
For rigid models and simple “joints” (e.g. flaps, doors, turrets), you can apply local‑space pose offsets to individual glTF nodes per instance:
bool setGLTFInstanceNodeOffset(const std::string &instanceName, const std::string &nodeName, const glm::mat4 &offset);bool clearGLTFInstanceNodeOffset(const std::string &instanceName, const std::string &nodeName);void clearGLTFInstanceNodeOffsets(const std::string &instanceName);
Typical usage:
-
Use glTF animation for the base motion (e.g. gear deployment).
-
Layer game‑driven offsets on top for per‑instance control:
// Rotate a control surface on one aircraft instance glm::mat4 offset = glm::rotate(glm::mat4(1.f), glm::radians(aileronDegrees), glm::vec3(1.f, 0.f, 0.f)); sceneMgr->setGLTFInstanceNodeOffset("plane01", "LeftAileron", offset);
Point Lights
- Struct:
struct PointLight { glm::vec3 position; float radius; glm::vec3 color; float intensity; };
- API:
void addPointLight(const PointLight &light);void clearPointLights();size_t getPointLightCount() const;bool getPointLight(size_t index, PointLight &outLight) const;bool setPointLight(size_t index, const PointLight &light);bool removePointLight(size_t index);
- Typical usage:
- On level load, add all static lights.
- At runtime, animate or toggle lights based on gameplay events (e.g. explosions, flickering lamps).
Picking & Selection (Interaction)
Picking lives in SceneManager and is wired into VulkanEngine’s frame loop.
Header: src/scene/vk_scene.h
Implementation: src/scene/vk_scene_picking.cpp
Single‑Object Ray Picking
bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos);- Input:
mousePosPixels– window coordinates (SDL style), origin at top‑left.
- Output on success:
outObject– closestRenderObjecthit by the camera ray.outWorldPos– precise world‑space hit position (uses mesh BVH when available).
- Returns:
trueif any object was hit, otherwisefalse.
- Input:
ID‑Buffer Picking
bool resolveObjectID(uint32_t id, RenderObject &outObject) const;- Takes an ID read back from the ID buffer and resolves it to the corresponding
RenderObjectin the latestDrawContext. - Used by the engine when
_useIdBufferPickingis enabled to implement asynchronous picking.
- Takes an ID read back from the ID buffer and resolves it to the corresponding
Rectangle Selection (Drag Box)
void selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vector<RenderObject> &outObjects) const;- Inputs:
p0,p1– opposite corners of a window‑space rectangle (top‑left origin).
- Output:
outObjects– appended with allRenderObjects whose projected bounds intersect the rectangle.
- Internals:
- Uses
sceneData.viewprojand an NDC‑space bounds test to determine overlap.
- Uses
- Inputs:
Typical Game Usage
- Hover tooltips:
- Track mouse position in window coordinates.
- Use
SceneManager::pickdirectly, or readVulkanEngine::get_last_pick()and/or_hoverPickas documented inScene.md.
- Object selection / interaction:
- On mouse click release, inspect
engine->get_last_pick():- If
valid, dispatch interaction based onownerType/ownerName(e.g. select unit, open door).
- If
- On mouse click release, inspect
- Multi‑select:
- When implementing drag selection, call
SceneManager::selectRectwith the drag rectangle to get all hits, or reuse the engine’s_dragSelectionmechanism.
- When implementing drag selection, call
ImGui / ImGuizmo Editor Utilities
File: src/core/engine_ui.cpp
These are primarily debug/editor features but can be kept in a game build to provide in‑game tools.
- Main entry:
void vk_engine_draw_debug_ui(VulkanEngine *eng);- Called once per frame by the engine to build the “Debug” window tabs.
- Useful tools for games:
- Scene tab:
- Spawn glTF instances at runtime using
VulkanEngine::addGLTFInstance(...). - Spawn primitive mesh instances (cube/sphere) using
SceneManager::addMeshInstance(...). - Point‑light editor UI built on
SceneManagerlight APIs.
- Spawn glTF instances at runtime using
- Object gizmo (ImGuizmo):
- Uses last pick / hover pick as the current target and manipulates transforms via
setMeshInstanceTransform/setGLTFInstanceTransform.
- Uses last pick / hover pick as the current target and manipulates transforms via
- Scene tab: