From 0172996e124e0646e03028720ca852f281e6e3f3 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Thu, 25 Dec 2025 20:08:25 +0900 Subject: [PATCH] ADD: Texture cache system improvement 2 --- docs/GameAPI.md | 874 +++++++++++++++++++++++++++++++++++++++--- src/core/game_api.cpp | 48 ++- src/core/game_api.h | 9 + 3 files changed, 884 insertions(+), 47 deletions(-) diff --git a/docs/GameAPI.md b/docs/GameAPI.md index 235f2cd..5170808 100644 --- a/docs/GameAPI.md +++ b/docs/GameAPI.md @@ -26,8 +26,12 @@ Implementation: `src/core/game_api.cpp` - Instances and animation. - Post‑processing (tonemap, bloom, FXAA). - Camera control. +- Lighting (directional, point, spot). +- Volumetrics (clouds, smoke, flame). +- Particle systems. +- Debug drawing. - Picking and render‑graph pass toggles. -- Input handling (keyboard, mouse, cursor modes). +- Time and statistics. Typical creation: @@ -49,10 +53,15 @@ Relevant methods: - `size_t get_texture_budget() const;` - `void set_texture_loads_per_frame(int count);` +- `int get_texture_loads_per_frame() const;` - `void set_texture_upload_budget(size_t bytes);` +- `size_t get_texture_upload_budget() const;` - `void set_cpu_source_budget(size_t bytes);` +- `size_t get_cpu_source_budget() const;` - `void set_max_upload_dimension(uint32_t dim);` +- `uint32_t get_max_upload_dimension() const;` - `void set_keep_source_bytes(bool keep);` +- `bool get_keep_source_bytes() const;` - `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`: @@ -63,6 +72,65 @@ At a lower level, `VulkanEngine::query_texture_budget_bytes()` computes a conser 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). +#### Texture Loading + +```cpp +// Load from file (relative to assets/textures/ or absolute path) +TextureHandle load_texture(const std::string& path, const TextureLoadParams& params = {}); + +// Load from memory (compressed image data: PNG, JPG, KTX2, etc.) +TextureHandle load_texture_from_memory(const std::vector& data, const TextureLoadParams& params = {}); + +// Check if texture is loaded and resident in VRAM +bool is_texture_loaded(TextureHandle handle) const; + +// Get internal Vulkan image view (VkImageView) for advanced use +void* get_texture_image_view(TextureHandle handle) const; + +// Pin texture to prevent automatic eviction (for UI, critical assets) +void pin_texture(TextureHandle handle); +void unpin_texture(TextureHandle handle); +bool is_texture_pinned(TextureHandle handle) const; + +// Unload texture and free VRAM (optional - cache auto-manages) +void unload_texture(TextureHandle handle); + +// Create ImGui descriptor set for use with ImGui::Image() +void* create_imgui_texture(TextureHandle handle, void* sampler = nullptr); +void free_imgui_texture(void* imgui_texture_id); +``` + +**TextureLoadParams:** +```cpp +struct TextureLoadParams +{ + bool srgb{false}; // Use sRGB color space (true for albedo/emissive) + bool mipmapped{true}; // Generate mipmap chain + TextureChannels channels{Auto}; // Channel hint (Auto, R, RG, RGBA) + uint32_t mipLevels{0}; // 0 = full chain, otherwise limit to N levels +}; +``` + +**Usage Example:** +```cpp +GameAPI::Engine api(&engine); + +// Load UI texture and pin it to prevent eviction +GameAPI::TextureLoadParams params; +params.srgb = true; +params.mipmapped = false; // UI textures don't need mipmaps +TextureHandle uiTex = api.load_texture("ui/button.png", params); +api.pin_texture(uiTex); + +// Create ImGui descriptor for rendering +void* imguiId = api.create_imgui_texture(uiTex); +ImGui::Image(imguiId, ImVec2(128, 64)); + +// Later: cleanup +api.free_imgui_texture(imguiId); +api.unpin_texture(uiTex); +``` + ### Shadows: Resolution, Quality, and RT Modes Shadows are controlled by a combination of: @@ -121,12 +189,101 @@ The following quality‑related shadow constants also live in `config.h`: 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. +### IBL (Image-Based Lighting) + +**API:** +```cpp +// Load global IBL asynchronously +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/remove local IBL volumes +size_t add_ibl_volume(const IBLVolume& volume); +size_t add_ibl_volume(const IBLVolumeD& volume); // double-precision +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); +bool get_ibl_volume(size_t index, IBLVolumeD& out) const; // double-precision +bool set_ibl_volume(size_t index, const IBLVolumeD& volume); + +// Query active volume +int get_active_ibl_volume() const; // -1 = global +size_t get_ibl_volume_count() const; +void clear_ibl_volumes(); +``` + +**Structures:** +```cpp +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) +}; + +struct IBLVolume +{ + glm::vec3 center{0.0f}; + glm::vec3 halfExtents{10.0f}; + IBLPaths paths; + bool enabled{true}; +}; + +struct IBLVolumeD // double-precision variant +{ + glm::dvec3 center{0.0}; + glm::vec3 halfExtents{10.0f}; + IBLPaths paths; + bool enabled{true}; +}; +``` + +**Usage Example:** +```cpp +GameAPI::Engine api(&engine); + +// Load global IBL (outdoor environment) +GameAPI::IBLPaths globalIBL; +globalIBL.specularCube = "ibl/outdoor_spec.ktx2"; +globalIBL.diffuseCube = "ibl/outdoor_diff.ktx2"; +globalIBL.brdfLut = "ibl/brdf_lut.ktx2"; +api.load_global_ibl(globalIBL); + +// Create local IBL volume for interior (overrides global when camera inside) +GameAPI::IBLVolume interior; +interior.center = glm::vec3(10.0f, 2.0f, -5.0f); +interior.halfExtents = glm::vec3(5.0f, 3.0f, 5.0f); +interior.paths.specularCube = "ibl/indoor_spec.ktx2"; +interior.paths.diffuseCube = "ibl/indoor_diff.ktx2"; +interior.paths.brdfLut = "ibl/brdf_lut.ktx2"; +interior.enabled = true; + +size_t idx = api.add_ibl_volume(interior); + +// Query which IBL is active +int activeVol = api.get_active_ibl_volume(); +if (activeVol == -1) +{ + // Using global IBL +} +else +{ + // Using local volume at index activeVol +} +``` + ### Reflections and Post‑Processing Game‑side reflection controls: - `void set_ssr_enabled(bool enabled);` -- `void set_reflection_mode(ReflectionMode mode);` +- `void set_reflection_mode(ReflectionMode mode);` (`SSROnly`, `SSRPlusRT`, `RTOnly`) Tone mapping and bloom: @@ -150,14 +307,17 @@ FXAA: Camera: - `void set_camera_position(const glm::vec3 &position);` +- `void set_camera_position(const glm::dvec3 &position);` // double-precision - `glm::vec3 get_camera_position() const;` +- `glm::dvec3 get_camera_position_d() const;` // double-precision - `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);` +- `void camera_look_at(const glm::dvec3 &target);` // double-precision -These functions internally manipulate the quaternion‑based `Camera::orientation` and `position` in `SceneManager`. They respect the engine’s `-Z` forward convention. +These functions internally manipulate the quaternion‑based `Camera::orientation` and `position` in `SceneManager`. They respect the engine's `-Z` forward convention. Double-precision variants allow precise camera positioning in large worlds. Render resolution scaling: @@ -171,9 +331,27 @@ This scales the internal draw extent relative to the swapchain and main HDR imag Picking: - `Engine::PickResult get_last_pick() const;` +- `Engine::PickResultD get_last_pick_d() const;` // double-precision - `void set_use_id_buffer_picking(bool use);` - `bool get_use_id_buffer_picking() const;` +**PickResult structure:** +```cpp +struct PickResult +{ + bool valid{false}; + std::string ownerName; + glm::vec3 worldPosition{0.0f}; +}; + +struct PickResultD // double-precision variant +{ + bool valid{false}; + std::string ownerName; + glm::dvec3 worldPosition{0.0}; +}; +``` + These mirror `VulkanEngine::get_last_pick()` and `_useIdBufferPicking`, letting you choose between: - CPU raycast picking (immediate, cheaper VRAM). @@ -369,20 +547,102 @@ Header: `src/core/engine.h` ## Scene & Instances (Actors, Lights, Animations) -Header: `src/scene/vk_scene.h` +Header: `src/scene/vk_scene.h` Docs: `docs/Scene.md` -### Dynamic Mesh/GLTF Instances +### Transform Structures -- Mesh instances - - `void addMeshInstance(const std::string &name, std::shared_ptr mesh, const glm::mat4 &transform = glm::mat4(1.f), std::optional 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. +The GameAPI provides both single and double-precision transform representations: + +```cpp +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); +}; + +struct TransformD // double-precision variant for large worlds +{ + glm::dvec3 position{0.0}; + glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec3 scale{1.0f}; + + glm::mat4 to_matrix() const; + static TransformD from_matrix(const glm::mat4& m); +}; +``` + +Use `TransformD` for positioning objects in large worlds (e.g., space games, flight sims) where single-precision floating point loses sub-meter precision at large coordinates. + +### GLTF Instances + +**API:** +```cpp +// 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); +bool add_gltf_instance(const std::string& name, + const std::string& modelPath, + const TransformD& 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); +uint32_t add_gltf_instance_async(const std::string& name, + const std::string& modelPath, + const TransformD& 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); +bool get_gltf_instance_transform(const std::string& name, TransformD& out) const; +bool set_gltf_instance_transform(const std::string& name, const TransformD& transform); + +// Preload textures for an instance +void preload_instance_textures(const std::string& name); + +// Clear all dynamic instances +void clear_all_instances(); +``` + +### Primitive Mesh Instances + +**API:** +```cpp +// Add primitive mesh instance +bool add_primitive_instance(const std::string& name, + PrimitiveType type, + const Transform& transform = {}); +bool add_primitive_instance(const std::string& name, + PrimitiveType type, + const TransformD& transform); + +// Remove mesh instance +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); +bool get_mesh_instance_transform(const std::string& name, TransformD& out) const; +bool set_mesh_instance_transform(const std::string& name, const TransformD& transform); +``` + +**Typical usage:** +- Spawn primitives or dynamic meshes at runtime (e.g. projectiles, props). +- Use `set_mesh_instance_transform` every frame to move them based on game logic. ### Textured Primitives @@ -488,29 +748,50 @@ api.add_textured_primitive("chrome_sphere", GameAPI::PrimitiveType::Sphere, meta ### 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 `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. +**API:** +```cpp +// 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); +``` + +**Notes:** +- All functions return `bool` indicating whether the named instance exists. +- Animation state is **independent per instance**: + - Each glTF instance has its own `AnimationState`, even when sharing the same `LoadedGLTF`. +- An index `< 0` (e.g. `-1`) disables animation for that instance (pose is frozen at the last evaluated state). +- `SceneManager::update_scene()` advances each active animation state every frame using engine delta time. + +**Usage Example:** +```cpp +GameAPI::Engine api(&engine); + +// Play walk animation by index +api.set_instance_animation("player", 0, true); // Reset to start +api.set_instance_animation_loop("player", true); + +// Switch to run animation by name +api.set_instance_animation("player", "run", true); + +// Stop animation (freeze pose) +api.set_instance_animation("player", -1); +``` ### 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: +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);` +**API:** +```cpp +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); +``` Typical usage: @@ -526,20 +807,108 @@ Typical usage: sceneMgr->setGLTFInstanceNodeOffset("plane01", "LeftAileron", offset); ``` -### Point Lights +### Lighting - Directional (Sunlight) -- 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). +- `void set_sunlight_direction(const glm::vec3& dir);` +- `glm::vec3 get_sunlight_direction() const;` +- `void set_sunlight_color(const glm::vec3& color, float intensity);` +- `glm::vec3 get_sunlight_color() const;` +- `float get_sunlight_intensity() const;` + +### Lighting - Point Lights + +**Structs:** +```cpp +struct PointLight +{ + glm::vec3 position{0.0f}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; +}; + +struct PointLightD // double-precision variant +{ + glm::dvec3 position{0.0}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; +}; +``` + +**API:** +- `size_t add_point_light(const PointLight &light);` +- `size_t add_point_light(const PointLightD &light);` +- `bool remove_point_light(size_t index);` +- `bool get_point_light(size_t index, PointLight &out) const;` +- `bool get_point_light(size_t index, PointLightD &out) const;` +- `bool set_point_light(size_t index, const PointLight &light);` +- `bool set_point_light(size_t index, const PointLightD &light);` +- `size_t get_point_light_count() const;` +- `void clear_point_lights();` + +**Typical usage:** +- On level load, add all static lights. +- At runtime, animate or toggle lights based on gameplay events (e.g. explosions, flickering lamps). + +### Lighting - Spot Lights + +**Structs:** +```cpp +struct SpotLight +{ + glm::vec3 position{0.0f}; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + float inner_angle_deg{15.0f}; + float outer_angle_deg{25.0f}; +}; + +struct SpotLightD // double-precision variant +{ + glm::dvec3 position{0.0}; + glm::vec3 direction{0.0f, -1.0f, 0.0f}; + float radius{10.0f}; + glm::vec3 color{1.0f}; + float intensity{1.0f}; + float inner_angle_deg{15.0f}; + float outer_angle_deg{25.0f}; +}; +``` + +**API:** +- `size_t add_spot_light(const SpotLight &light);` +- `size_t add_spot_light(const SpotLightD &light);` +- `bool remove_spot_light(size_t index);` +- `bool get_spot_light(size_t index, SpotLight &out) const;` +- `bool get_spot_light(size_t index, SpotLightD &out) const;` +- `bool set_spot_light(size_t index, const SpotLight &light);` +- `bool set_spot_light(size_t index, const SpotLightD &light);` +- `size_t get_spot_light_count() const;` +- `void clear_spot_lights();` + +**Usage Example:** +```cpp +GameAPI::Engine api(&engine); + +// Create a flashlight +GameAPI::SpotLight flashlight; +flashlight.position = glm::vec3(0.0f, 1.5f, 0.0f); +flashlight.direction = glm::vec3(0.0f, 0.0f, -1.0f); +flashlight.radius = 20.0f; +flashlight.color = glm::vec3(1.0f, 0.95f, 0.8f); // Warm white +flashlight.intensity = 50.0f; +flashlight.inner_angle_deg = 10.0f; +flashlight.outer_angle_deg = 25.0f; + +size_t idx = api.add_spot_light(flashlight); + +// Later: update flashlight direction to follow camera +flashlight.direction = camera_forward; +api.set_spot_light(idx, flashlight); +``` --- @@ -723,3 +1092,416 @@ void draw_gizmo(const PickingSystem::PickInfo& pick, } } ``` + +--- + +## Time and Statistics + +Header: `src/core/game_api.h` + +### Delta Time + +```cpp +// Get delta time in seconds for the current frame (clamped to 0.0-0.1) +float get_delta_time() const; +``` + +Use this for frame-rate independent movement and animation. + +### Engine Statistics + +```cpp +struct Stats +{ + float frametime{0.0f}; // ms + float drawTime{0.0f}; // ms + float sceneUpdateTime{0.0f}; // ms + int triangleCount{0}; + int drawCallCount{0}; +}; + +Stats get_stats() const; +``` + +**Usage Example:** +```cpp +GameAPI::Engine api(&engine); + +// Frame-rate independent movement +float dt = api.get_delta_time(); +player_position += velocity * dt; + +// Display performance stats +GameAPI::Stats stats = api.get_stats(); +fmt::println("FPS: {:.1f} | Tris: {} | Draws: {}", + 1000.0f / stats.frametime, + stats.triangleCount, + stats.drawCallCount); +``` + +--- + +## Volumetrics (Clouds, Smoke, Flame) + +Header: `src/core/game_api.h` + +The engine supports GPU-based voxel volumetric rendering for clouds, smoke, and flame effects. Up to 4 independent volumes can be active simultaneously. + +### API + +```cpp +// Enable/disable volumetrics system +void set_volumetrics_enabled(bool enabled); +bool get_volumetrics_enabled() const; + +// Get/set voxel volume settings by index (0-3) +bool get_voxel_volume(size_t index, VoxelVolumeSettings& out) const; +bool set_voxel_volume(size_t index, const VoxelVolumeSettings& settings); + +// Get maximum number of voxel volumes +size_t get_max_voxel_volumes() const; // Returns 4 +``` + +### VoxelVolumeSettings Structure + +```cpp +struct VoxelVolumeSettings +{ + bool enabled{false}; + VoxelVolumeType type{VoxelVolumeType::Clouds}; // Clouds, Smoke, Flame + + // Volume positioning + bool followCameraXZ{false}; // Follow camera in XZ, offset in Y + bool animateVoxels{true}; // Run voxel advection/update compute + glm::vec3 volumeCenterLocal{0.0f, 2.0f, 0.0f}; + glm::vec3 volumeHalfExtents{8.0f, 8.0f, 8.0f}; + glm::vec3 volumeVelocityLocal{0.0f, 0.0f, 0.0f}; // Drift when not following camera + + // Raymarch/composite controls + float densityScale{1.0f}; + float coverage{0.0f}; // 0..1 threshold (higher = emptier) + float extinction{1.0f}; // Absorption/extinction scale + int stepCount{48}; // Raymarch steps + + // Voxel grid resolution (cubic) + uint32_t gridResolution{48}; + + // Voxel animation (advection + injection) parameters + glm::vec3 windVelocityLocal{0.0f, 2.0f, 0.0f}; // Local units/sec (buoyancy) + float dissipation{1.25f}; // Density decay rate (1/sec) + float noiseStrength{1.0f}; // Injection rate + float noiseScale{8.0f}; // Noise frequency in UVW space + float noiseSpeed{1.0f}; // Time scale for injection noise + + // Smoke/flame source in normalized volume UVW space + glm::vec3 emitterUVW{0.5f, 0.05f, 0.5f}; + float emitterRadius{0.18f}; // Normalized (0..1) + + // Shading + glm::vec3 albedo{1.0f, 1.0f, 1.0f}; // Scattering tint (cloud/smoke) + float scatterStrength{1.0f}; + glm::vec3 emissionColor{1.0f, 0.6f, 0.25f}; // Flame emissive tint + float emissionStrength{0.0f}; +}; +``` + +### Usage Example + +```cpp +GameAPI::Engine api(&engine); + +// Enable volumetrics +api.set_volumetrics_enabled(true); + +// Create a flame effect +GameAPI::VoxelVolumeSettings flame; +flame.enabled = true; +flame.type = GameAPI::VoxelVolumeType::Flame; +flame.volumeCenterLocal = glm::vec3(0.0f, 1.0f, -5.0f); +flame.volumeHalfExtents = glm::vec3(2.0f, 3.0f, 2.0f); +flame.gridResolution = 64; +flame.densityScale = 1.5f; +flame.coverage = 0.3f; +flame.windVelocityLocal = glm::vec3(0.0f, 5.0f, 0.0f); // Upward +flame.emitterUVW = glm::vec3(0.5f, 0.1f, 0.5f); +flame.emitterRadius = 0.2f; +flame.emissionStrength = 2.0f; +flame.emissionColor = glm::vec3(1.0f, 0.5f, 0.1f); + +api.set_voxel_volume(0, flame); + +// Create cloud layer that follows camera +GameAPI::VoxelVolumeSettings clouds; +clouds.enabled = true; +clouds.type = GameAPI::VoxelVolumeType::Clouds; +clouds.followCameraXZ = true; +clouds.volumeCenterLocal = glm::vec3(0.0f, 50.0f, 0.0f); // Offset in Y +clouds.volumeHalfExtents = glm::vec3(100.0f, 20.0f, 100.0f); +clouds.gridResolution = 128; +clouds.densityScale = 0.8f; +clouds.coverage = 0.5f; +clouds.albedo = glm::vec3(0.9f, 0.95f, 1.0f); // Bluish tint + +api.set_voxel_volume(1, clouds); +``` + +--- + +## Particle Systems + +Header: `src/core/game_api.h` + +GPU-accelerated particle systems with flipbook animation, soft particles, and flexible spawning. + +### API + +```cpp +// Create/destroy particle systems +uint32_t create_particle_system(uint32_t particle_count); +bool destroy_particle_system(uint32_t id); +bool resize_particle_system(uint32_t id, uint32_t new_count); + +// Get/set particle system settings +bool get_particle_system(uint32_t id, ParticleSystem& out) const; +bool set_particle_system(uint32_t id, const ParticleSystem& system); + +// Query particle systems +std::vector get_particle_system_ids() const; +uint32_t get_allocated_particles() const; +uint32_t get_free_particles() const; +uint32_t get_max_particles() const; + +// Preload VFX textures (e.g., "vfx/flame.ktx2") +void preload_particle_texture(const std::string& assetPath); +``` + +### ParticleSystem Structure + +```cpp +struct ParticleSystem +{ + uint32_t id{0}; + uint32_t particleCount{0}; + bool enabled{true}; + bool reset{true}; + ParticleBlendMode blendMode{ParticleBlendMode::Additive}; // Additive or Alpha + ParticleParams params{}; + + // Asset-relative texture paths (e.g., "vfx/flame.ktx2") + std::string flipbookTexture{"vfx/flame.ktx2"}; + std::string noiseTexture{"vfx/simplex.ktx2"}; +}; + +struct ParticleParams +{ + glm::vec3 emitterPosLocal{0.0f, 0.0f, 0.0f}; + float spawnRadius{0.1f}; + glm::vec3 emitterDirLocal{0.0f, 1.0f, 0.0f}; + float coneAngleDegrees{20.0f}; + + float minSpeed{2.0f}; + float maxSpeed{8.0f}; + float minLife{0.5f}; + float maxLife{1.5f}; + float minSize{0.05f}; + float maxSize{0.15f}; + + float drag{1.0f}; + float gravity{0.0f}; // Positive pulls down -Y in local space + + glm::vec4 color{1.0f, 0.5f, 0.1f, 1.0f}; + + // Soft particles (fade near opaque geometry) + float softDepthDistance{0.15f}; + + // Flipbook animation (atlas layout) + uint32_t flipbookCols{16}; + uint32_t flipbookRows{4}; + float flipbookFps{30.0f}; + float flipbookIntensity{1.0f}; + + // Noise UV distortion + float noiseScale{6.0f}; + float noiseStrength{0.05f}; + glm::vec2 noiseScroll{0.0f, 0.0f}; +}; +``` + +### Usage Example + +```cpp +GameAPI::Engine api(&engine); + +// Create fire particle system +uint32_t fireId = api.create_particle_system(4096); + +GameAPI::ParticleSystem fire; +fire.id = fireId; +fire.particleCount = 4096; +fire.enabled = true; +fire.blendMode = GameAPI::ParticleBlendMode::Additive; +fire.flipbookTexture = "vfx/flame.ktx2"; +fire.noiseTexture = "vfx/simplex.ktx2"; + +fire.params.emitterPosLocal = glm::vec3(0.0f, 0.0f, -5.0f); +fire.params.spawnRadius = 0.5f; +fire.params.emitterDirLocal = glm::vec3(0.0f, 1.0f, 0.0f); +fire.params.coneAngleDegrees = 15.0f; +fire.params.minSpeed = 2.0f; +fire.params.maxSpeed = 4.0f; +fire.params.minLife = 0.8f; +fire.params.maxLife = 1.5f; +fire.params.minSize = 0.3f; +fire.params.maxSize = 0.6f; +fire.params.gravity = -2.0f; // Upward buoyancy +fire.params.color = glm::vec4(1.0f, 0.7f, 0.3f, 1.0f); +fire.params.flipbookCols = 16; +fire.params.flipbookRows = 4; +fire.params.flipbookFps = 24.0f; + +api.set_particle_system(fireId, fire); + +// Later: move emitter to follow player +fire.params.emitterPosLocal = player_position; +api.set_particle_system(fireId, fire); + +// Reset particles (trigger burst) +fire.reset = true; +api.set_particle_system(fireId, fire); +``` + +--- + +## Debug Drawing + +Header: `src/core/game_api.h` + +Runtime debug visualization for primitives (lines, spheres, boxes, etc.) with optional depth testing and duration. + +### Settings API + +```cpp +// Enable/disable debug drawing system +void set_debug_draw_enabled(bool enabled); +bool get_debug_draw_enabled() const; + +// Control which debug layers are visible (bitmask) +void set_debug_layer_mask(uint32_t mask); +uint32_t get_debug_layer_mask() const; + +// Show/hide depth-tested primitives +void set_debug_show_depth_tested(bool show); +bool get_debug_show_depth_tested() const; + +// Show/hide overlay (always-on-top) primitives +void set_debug_show_overlay(bool show); +bool get_debug_show_overlay() const; + +// Set tessellation quality (segments for circles/spheres) +void set_debug_segments(int segments); +int get_debug_segments() const; + +// Clear all debug draw commands +void debug_draw_clear(); +``` + +### Drawing API + +All drawing functions support both single and double-precision variants: + +```cpp +// Line +void debug_draw_line(const glm::vec3& a, const glm::vec3& b, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_line(const glm::dvec3& a, const glm::dvec3& b, ...); + +// Ray (origin + direction + length) +void debug_draw_ray(const glm::vec3& origin, const glm::vec3& direction, float length, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_ray(const glm::dvec3& origin, const glm::dvec3& direction, double length, ...); + +// AABB (axis-aligned bounding box) +void debug_draw_aabb(const glm::vec3& center, const glm::vec3& half_extents, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_aabb(const glm::dvec3& center, const glm::vec3& half_extents, ...); + +// Sphere +void debug_draw_sphere(const glm::vec3& center, float radius, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_sphere(const glm::dvec3& center, float radius, ...); + +// Capsule (line segment + radius) +void debug_draw_capsule(const glm::vec3& p0, const glm::vec3& p1, float radius, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_capsule(const glm::dvec3& p0, const glm::dvec3& p1, float radius, ...); + +// Circle (center + normal + radius) +void debug_draw_circle(const glm::vec3& center, const glm::vec3& normal, float radius, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_circle(const glm::dvec3& center, const glm::dvec3& normal, float radius, ...); + +// Cone (apex + direction + length + angle) +void debug_draw_cone(const glm::vec3& apex, const glm::vec3& direction, + float length, float angle_degrees, + const glm::vec4& color = glm::vec4(1.0f), + float duration_seconds = 0.0f, + bool depth_tested = true); +void debug_draw_cone(const glm::dvec3& apex, const glm::dvec3& direction, + float length, float angle_degrees, ...); +``` + +### Usage Example + +```cpp +GameAPI::Engine api(&engine); + +// Enable debug drawing +api.set_debug_draw_enabled(true); +api.set_debug_segments(32); // Smooth circles/spheres + +// Visualize player bounds (persistent, depth-tested) +api.debug_draw_aabb(player_pos, glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), + 0.0f, // Duration 0 = single frame + true); // Depth tested + +// Visualize raycast (red ray, always on top, 2 seconds) +api.debug_draw_ray(ray_origin, ray_dir, 100.0f, + glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), + 2.0f, // Show for 2 seconds + false); // Always on top + +// Visualize trigger volume (transparent sphere) +api.debug_draw_sphere(trigger_pos, trigger_radius, + glm::vec4(1.0f, 1.0f, 0.0f, 0.3f), // Yellow, 30% alpha + 0.0f, + true); + +// Visualize spot light cone +api.debug_draw_cone(light_pos, light_dir, light_radius, light_angle_deg, + glm::vec4(1.0f, 0.9f, 0.7f, 0.5f), + 0.0f, + true); + +// One-shot clear (useful for clearing persistent debug viz) +api.debug_draw_clear(); +``` + +**Notes:** +- `duration_seconds = 0.0f`: Draw for a single frame (re-submit each frame for persistent viz). +- `duration_seconds > 0.0f`: Draw for N seconds, then automatically expire. +- `depth_tested = true`: Primitive is occluded by scene geometry. +- `depth_tested = false`: Always on top (overlay mode). +- All primitives support alpha blending via the color's alpha channel. diff --git a/src/core/game_api.cpp b/src/core/game_api.cpp index 9080722..446c51e 100644 --- a/src/core/game_api.cpp +++ b/src/core/game_api.cpp @@ -16,9 +16,12 @@ #include #include -#include #include +// ImGui integration for texture display +#include "imgui.h" +#include "imgui_impl_vulkan.h" + namespace GameAPI { @@ -332,6 +335,49 @@ void Engine::unload_texture(TextureHandle handle) _engine->_textureCache->unload(cacheHandle); } +void* Engine::create_imgui_texture(TextureHandle handle, void* sampler) +{ + if (!_engine || !_engine->_textureCache) + { + return nullptr; + } + + auto cacheHandle = static_cast(handle); + VkImageView imageView = _engine->_textureCache->image_view(cacheHandle); + + if (imageView == VK_NULL_HANDLE) + { + return nullptr; + } + + // Use provided sampler or default linear sampler + VkSampler vkSampler = reinterpret_cast(sampler); + if (vkSampler == VK_NULL_HANDLE && _engine->_context && _engine->_context->samplers) + { + vkSampler = _engine->_context->samplers->defaultLinear(); + } + + // Create ImGui descriptor set using ImGui_ImplVulkan + VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture( + vkSampler, + imageView, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + ); + + return reinterpret_cast(descriptorSet); +} + +void Engine::free_imgui_texture(void* imgui_texture_id) +{ + if (imgui_texture_id == nullptr) + { + return; + } + + VkDescriptorSet descriptorSet = reinterpret_cast(imgui_texture_id); + ImGui_ImplVulkan_RemoveTexture(descriptorSet); +} + // ---------------------------------------------------------------------------- // Shadows // ---------------------------------------------------------------------------- diff --git a/src/core/game_api.h b/src/core/game_api.h index f215984..02e5c4c 100644 --- a/src/core/game_api.h +++ b/src/core/game_api.h @@ -383,6 +383,15 @@ public: // This is optional - the cache manages memory automatically void unload_texture(TextureHandle handle); + // Create an ImGui descriptor set for a texture (for use with ImGui::Image()) + // Returns ImTextureID (actually VkDescriptorSet) that can be used in ImGui + // The returned descriptor set is managed by ImGui and valid until cleanup + // sampler: VK_NULL_HANDLE uses default linear sampler + void* create_imgui_texture(TextureHandle handle, void* sampler = nullptr); + + // Free an ImGui descriptor set created by create_imgui_texture() + void free_imgui_texture(void* imgui_texture_id); + // ------------------------------------------------------------------------ // Shadows // ------------------------------------------------------------------------