Files
QuaternionEngine/docs/GameAPI.md
2025-12-10 01:04:24 +09:00

16 KiB
Raw Blame History

GameFacing 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 perframe state.
  • docs/RenderGraph.md rendergraph API for custom passes.

GameAPI::Engine (HighLevel Game Wrapper)

Header: src/core/game_api.h
Implementation: src/core/game_api.cpp

GameAPI::Engine is a thin, gamefriendly 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.
  • Postprocessing (tonemap, bloom, FXAA).
  • Camera control.
  • Picking and rendergraph pass toggles.

Typical creation:

#include "core/engine.h"
#include "core/game_api.h"

VulkanEngine engine;
engine.init();

GameAPI::Engine api(&engine); // nonowning

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 perframe texture budget using VMA heap info and constants in src/core/config.h:

  • kTextureBudgetFraction fraction of total devicelocal 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 perscene tuning (e.g. reducing upload bandwidth on lowend 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).

Highlevel gameside controls:

  • void set_shadows_enabled(bool enabled);
  • void set_shadow_mode(ShadowMode mode);
    • ClipmapOnly cascaded shadow maps only.
    • ClipmapPlusRT cascades + optional rayquery assist.
    • RTOnly raytraced 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 midrange 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, Gbuffers, 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 devicelocal 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 retest.

The following qualityrelated 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 PostProcessing

Gameside 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 quaternionbased Camera::orientation and position in SceneManager. They respect the engines -Z forward convention.

Render resolution scaling:

  • void set_render_scale(float scale); // 0.31.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).
  • IDbuffer based picking (async, 1frame latency, robust for dense scenes).

Rendergraph 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/... (via AssetManager) and registers it as a runtime scene instance.
      • instanceName becomes the logical name used by SceneManager (e.g. for picking and animation control).
  • Picking access

    • struct PickInfo (nested in VulkanEngine)
      • MeshAsset *mesh, LoadedGLTF *scene, Node *node
      • RenderObject::OwnerType ownerType (e.g. StaticGLTF, GLTFInstance, MeshInstance)
      • std::string ownerName
      • glm::vec3 worldPos
      • glm::mat4 worldTransform
      • uint32_t indexCount, firstIndex, surfaceIndex
      • bool valid
    • const PickInfo &get_last_pick() const
      • Returns the last click selection result.
      • Filled by the engine from either CPU ray picking or IDbuffer picking depending on _useIdBufferPicking.
      • Typical usage:
        • On mouseup in your game layer, read engine->get_last_pick() and, if valid, use ownerName/worldPos to drive selection logic.

Note: hover picks and drag selections are also available as internal fields on VulkanEngine (_hoverPick, _dragSelection) and are documented in docs/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 setMeshInstanceTransform every 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)

  • Scenelevel
    • 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);
  • Instancelevel
    • 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 bool indicating 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 same LoadedGLTF.
    • 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.

PerInstance Node / Joint Control (NonSkinned)

For rigid models and simple “joints” (e.g. flaps, doors, turrets), you can apply localspace 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 gamedriven offsets on top for perinstance 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 VulkanEngines frame loop.

Header: src/scene/vk_scene.h
Implementation: src/scene/vk_scene_picking.cpp

SingleObject Ray Picking

  • bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, glm::vec3 &outWorldPos);
    • Input:
      • mousePosPixels window coordinates (SDL style), origin at topleft.
    • Output on success:
      • outObject closest RenderObject hit by the camera ray.
      • outWorldPos precise worldspace hit position (uses mesh BVH when available).
    • Returns:
      • true if any object was hit, otherwise false.

IDBuffer Picking

  • bool resolveObjectID(uint32_t id, RenderObject &outObject) const;
    • Takes an ID read back from the ID buffer and resolves it to the corresponding RenderObject in the latest DrawContext.
    • Used by the engine when _useIdBufferPicking is enabled to implement asynchronous picking.

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 windowspace rectangle (topleft origin).
    • Output:
      • outObjects appended with all RenderObjects whose projected bounds intersect the rectangle.
    • Internals:
      • Uses sceneData.viewproj and an NDCspace bounds test to determine overlap.

Typical Game Usage

  • Hover tooltips:
    • Track mouse position in window coordinates.
    • Use SceneManager::pick directly, or read VulkanEngine::get_last_pick() and/or _hoverPick as documented in Scene.md.
  • Object selection / interaction:
    • On mouse click release, inspect engine->get_last_pick():
      • If valid, dispatch interaction based on ownerType / ownerName (e.g. select unit, open door).
  • Multiselect:
    • When implementing drag selection, call SceneManager::selectRect with the drag rectangle to get all hits, or reuse the engines _dragSelection mechanism.

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 ingame 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(...).
      • Pointlight editor UI built on SceneManager light APIs.
    • Object gizmo (ImGuizmo):
      • Uses last pick / hover pick as the current target and manipulates transforms via setMeshInstanceTransform / setGLTFInstanceTransform.