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

366 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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:
```cpp
#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:
```cpp
// 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`
### 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 `RenderObject`s 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`.