2026-02-02 12:24:50 -08:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <cstdint>
|
2026-02-10 19:53:23 -08:00
|
|
|
#include <vector>
|
2026-03-07 22:03:28 -08:00
|
|
|
#include <future>
|
2026-03-08 23:35:39 -07:00
|
|
|
#include <cstddef>
|
2026-03-17 18:23:05 -07:00
|
|
|
#include <unordered_map>
|
2026-03-20 05:56:33 -07:00
|
|
|
#include <unordered_set>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <glm/glm.hpp>
|
2026-02-21 19:41:21 -08:00
|
|
|
#include <vulkan/vulkan.h>
|
|
|
|
|
#include <vk_mem_alloc.h>
|
|
|
|
|
#include "rendering/vk_frame_data.hpp"
|
2026-03-07 22:03:28 -08:00
|
|
|
#include "rendering/vk_utils.hpp"
|
2026-02-21 19:41:21 -08:00
|
|
|
#include "rendering/sky_system.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace core { class Window; }
|
2026-02-21 19:41:21 -08:00
|
|
|
namespace rendering { class VkContext; }
|
2026-02-10 19:30:45 -08:00
|
|
|
namespace game { class World; class ZoneManager; class GameHandler; }
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
namespace audio { class AudioCoordinator; }
|
2026-02-02 12:24:50 -08:00
|
|
|
namespace pipeline { class AssetManager; }
|
|
|
|
|
|
|
|
|
|
namespace rendering {
|
|
|
|
|
|
|
|
|
|
class Camera;
|
|
|
|
|
class CameraController;
|
|
|
|
|
class TerrainRenderer;
|
|
|
|
|
class TerrainManager;
|
|
|
|
|
class PerformanceHUD;
|
|
|
|
|
class WaterRenderer;
|
|
|
|
|
class Skybox;
|
|
|
|
|
class Celestial;
|
|
|
|
|
class StarField;
|
|
|
|
|
class Clouds;
|
|
|
|
|
class LensFlare;
|
|
|
|
|
class Weather;
|
2026-03-13 09:52:23 -07:00
|
|
|
class Lightning;
|
2026-02-10 13:48:50 -08:00
|
|
|
class LightingManager;
|
2026-02-02 12:24:50 -08:00
|
|
|
class SwimEffects;
|
2026-02-09 01:24:17 -08:00
|
|
|
class MountDust;
|
2026-02-19 20:36:25 -08:00
|
|
|
class LevelUpEffect;
|
2026-02-19 21:13:13 -08:00
|
|
|
class ChargeEffect;
|
2026-02-02 12:24:50 -08:00
|
|
|
class CharacterRenderer;
|
|
|
|
|
class WMORenderer;
|
|
|
|
|
class M2Renderer;
|
|
|
|
|
class Minimap;
|
2026-02-21 19:41:21 -08:00
|
|
|
class WorldMap;
|
2026-02-09 23:41:38 -08:00
|
|
|
class QuestMarkerRenderer;
|
2026-02-22 05:58:45 -08:00
|
|
|
class CharacterPreview;
|
2026-03-08 23:13:08 -07:00
|
|
|
class AmdFsr3Runtime;
|
2026-04-02 00:21:21 +03:00
|
|
|
class SpellVisualSystem;
|
|
|
|
|
class PostProcessPipeline;
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
class AnimationController;
|
|
|
|
|
class LevelUpEffect;
|
|
|
|
|
class ChargeEffect;
|
|
|
|
|
class SwimEffects;
|
feat(rendering): GPU architecture + visual quality fixes
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex
Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
host-mapped buffer race condition that caused terrain flickering
GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node
Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot
Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)
Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers
Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
M2 render distance (2800u) and eliminate pop-in when camera turns;
unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
early pop of grass and debris
2026-04-04 13:43:16 +03:00
|
|
|
class RenderGraph;
|
2026-04-05 19:30:44 +03:00
|
|
|
class OverlaySystem;
|
feat(rendering): add HiZ occlusion culling & fix WMO interior shadows
Implement GPU-driven Hierarchical-Z occlusion culling for M2 doodads
using a depth pyramid built from the previous frame's depth buffer.
The cull shader projects bounding spheres via prevViewProj (temporal
reprojection) and samples the HiZ pyramid to reject hidden objects
before the main render pass.
Key implementation details:
- Separate early compute submission (beginSingleTimeCommands + fence
wait) eliminates 2-frame visibility staleness
- Conservative safeguards prevent false culls: screen-edge guard,
full VP row-vector AABB projection (Cauchy-Schwarz), 50% sphere
inflation, depth bias, mip+1, min screen size threshold, camera
motion dampening (auto-disable on fast rotations), and per-instance
previouslyVisible flag tracking
- Graceful fallback to frustum-only culling if HiZ init fails
Fix dark WMO interiors by gating shadow map sampling on isInterior==0
in the WMO fragment shader. Interior groups (flag 0x2000) now rely
solely on pre-baked MOCV vertex-color lighting + MOHD ambient color.
Disable interiorDarken globally (was incorrectly darkening outdoor M2s
when camera was inside a WMO). Use isInsideInteriorWMO() instead of
isInsideWMO() for correct indoor detection.
New files:
- hiz_system.hpp/cpp: pyramid image management, compute pipeline,
descriptors, mip-chain build dispatch, resize handling
- hiz_build.comp.glsl: MAX-depth 2x2 reduction compute shader
- m2_cull_hiz.comp.glsl: frustum + HiZ occlusion cull compute shader
- test_indoor_shadows.cpp: 14 unit tests for shadow/interior contracts
Modified:
- CullUniformsGPU expanded 128->272 bytes (HiZ params, viewProj,
prevViewProj)
- Depth buffer images gain VK_IMAGE_USAGE_SAMPLED_BIT for HiZ reads
- wmo.frag.glsl: interior branch before unlit, shadow skip for 0x2000
- Render graph: hiz_build + compute_cull disabled (run in early compute)
- .gitignore: ignore compiled .spv binaries
- MEGA_BONE_MAX_INSTANCES: 2048 -> 4096
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 16:40:59 +03:00
|
|
|
class HiZSystem;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
class Renderer {
|
|
|
|
|
public:
|
|
|
|
|
Renderer();
|
|
|
|
|
~Renderer();
|
|
|
|
|
|
|
|
|
|
bool initialize(core::Window* window);
|
|
|
|
|
void shutdown();
|
|
|
|
|
|
|
|
|
|
void beginFrame();
|
|
|
|
|
void endFrame();
|
|
|
|
|
|
2026-02-10 19:30:45 -08:00
|
|
|
void renderWorld(game::World* world, game::GameHandler* gameHandler = nullptr);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update renderer (camera, etc.)
|
|
|
|
|
*/
|
|
|
|
|
void update(float deltaTime);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load test terrain for debugging
|
|
|
|
|
* @param assetManager Asset manager to load terrain data
|
|
|
|
|
* @param adtPath Path to ADT file (e.g., "World\\Maps\\Azeroth\\Azeroth_32_49.adt")
|
|
|
|
|
*/
|
|
|
|
|
bool loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath);
|
|
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
/**
|
|
|
|
|
* Initialize all sub-renderers (WMO, M2, Character, terrain, water, minimap, etc.)
|
|
|
|
|
* without loading any ADT tile. Used by WMO-only maps (dungeons/raids/BGs).
|
|
|
|
|
*/
|
|
|
|
|
bool initializeRenderers(pipeline::AssetManager* assetManager, const std::string& mapName);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
/**
|
|
|
|
|
* Enable/disable terrain rendering
|
|
|
|
|
*/
|
|
|
|
|
void setTerrainEnabled(bool enabled) { terrainEnabled = enabled; }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enable/disable wireframe mode
|
|
|
|
|
*/
|
|
|
|
|
void setWireframeMode(bool enabled);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load terrain tiles around position
|
|
|
|
|
* @param mapName Map name (e.g., "Azeroth", "Kalimdor")
|
|
|
|
|
* @param centerX Center tile X coordinate
|
|
|
|
|
* @param centerY Center tile Y coordinate
|
|
|
|
|
* @param radius Load radius in tiles
|
|
|
|
|
*/
|
|
|
|
|
bool loadTerrainArea(const std::string& mapName, int centerX, int centerY, int radius = 1);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enable/disable terrain streaming
|
|
|
|
|
*/
|
|
|
|
|
void setTerrainStreaming(bool enabled);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render performance HUD
|
|
|
|
|
*/
|
|
|
|
|
void renderHUD();
|
|
|
|
|
|
|
|
|
|
Camera* getCamera() { return camera.get(); }
|
|
|
|
|
CameraController* getCameraController() { return cameraController.get(); }
|
|
|
|
|
TerrainRenderer* getTerrainRenderer() const { return terrainRenderer.get(); }
|
|
|
|
|
TerrainManager* getTerrainManager() const { return terrainManager.get(); }
|
|
|
|
|
PerformanceHUD* getPerformanceHUD() { return performanceHUD.get(); }
|
|
|
|
|
WaterRenderer* getWaterRenderer() const { return waterRenderer.get(); }
|
2026-02-21 19:41:21 -08:00
|
|
|
Skybox* getSkybox() const { return skySystem ? skySystem->getSkybox() : nullptr; }
|
|
|
|
|
Celestial* getCelestial() const { return skySystem ? skySystem->getCelestial() : nullptr; }
|
|
|
|
|
StarField* getStarField() const { return skySystem ? skySystem->getStarField() : nullptr; }
|
|
|
|
|
Clouds* getClouds() const { return skySystem ? skySystem->getClouds() : nullptr; }
|
|
|
|
|
LensFlare* getLensFlare() const { return skySystem ? skySystem->getLensFlare() : nullptr; }
|
2026-02-02 12:24:50 -08:00
|
|
|
Weather* getWeather() const { return weather.get(); }
|
2026-03-13 09:52:23 -07:00
|
|
|
Lightning* getLightning() const { return lightning.get(); }
|
2026-02-02 12:24:50 -08:00
|
|
|
CharacterRenderer* getCharacterRenderer() const { return characterRenderer.get(); }
|
|
|
|
|
WMORenderer* getWMORenderer() const { return wmoRenderer.get(); }
|
|
|
|
|
M2Renderer* getM2Renderer() const { return m2Renderer.get(); }
|
|
|
|
|
Minimap* getMinimap() const { return minimap.get(); }
|
2026-02-21 19:41:21 -08:00
|
|
|
WorldMap* getWorldMap() const { return worldMap.get(); }
|
2026-02-09 23:41:38 -08:00
|
|
|
QuestMarkerRenderer* getQuestMarkerRenderer() const { return questMarkerRenderer.get(); }
|
2026-02-10 19:30:45 -08:00
|
|
|
SkySystem* getSkySystem() const { return skySystem.get(); }
|
2026-04-05 19:30:44 +03:00
|
|
|
const std::string& getCurrentZoneName() const;
|
|
|
|
|
uint32_t getCurrentZoneId() const;
|
2026-03-20 06:29:33 -07:00
|
|
|
bool isPlayerIndoors() const { return playerIndoors_; }
|
2026-02-21 19:41:21 -08:00
|
|
|
VkContext* getVkContext() const { return vkCtx; }
|
|
|
|
|
VkDescriptorSetLayout getPerFrameSetLayout() const { return perFrameSetLayout; }
|
|
|
|
|
VkRenderPass getShadowRenderPass() const { return shadowRenderPass; }
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Third-person character follow
|
|
|
|
|
void setCharacterFollow(uint32_t instanceId);
|
|
|
|
|
glm::vec3& getCharacterPosition() { return characterPosition; }
|
|
|
|
|
uint32_t getCharacterInstanceId() const { return characterInstanceId; }
|
|
|
|
|
float getCharacterYaw() const { return characterYaw; }
|
2026-02-08 03:05:38 -08:00
|
|
|
void setCharacterYaw(float yawDeg) { characterYaw = yawDeg; }
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-18 10:47:34 -07:00
|
|
|
// Screenshot capture — copies swapchain image to PNG file
|
|
|
|
|
bool captureScreenshot(const std::string& outputPath);
|
|
|
|
|
|
2026-03-17 18:30:11 -07:00
|
|
|
// Spell visual effects (SMSG_PLAY_SPELL_VISUAL / SMSG_PLAY_SPELL_IMPACT)
|
2026-04-02 00:21:21 +03:00
|
|
|
// Delegates to SpellVisualSystem (owned by Renderer)
|
|
|
|
|
SpellVisualSystem* getSpellVisualSystem() const { return spellVisualSystem_.get(); }
|
2026-04-05 19:30:44 +03:00
|
|
|
|
|
|
|
|
// Combat visual state (compound: resets AnimationController + SpellVisualSystem)
|
2026-03-14 09:19:16 -07:00
|
|
|
void resetCombatVisualState();
|
2026-04-05 19:30:44 +03:00
|
|
|
|
|
|
|
|
// Sub-system accessors (§4.2)
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
AnimationController* getAnimationController() const { return animationController_.get(); }
|
|
|
|
|
LevelUpEffect* getLevelUpEffect() const { return levelUpEffect.get(); }
|
|
|
|
|
ChargeEffect* getChargeEffect() const { return chargeEffect.get(); }
|
|
|
|
|
SwimEffects* getSwimEffects() const { return swimEffects.get(); }
|
2026-02-07 17:59:40 -08:00
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
// Selection circle for targeted entity
|
|
|
|
|
void setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color);
|
|
|
|
|
void clearSelectionCircle();
|
|
|
|
|
|
2026-02-03 16:21:48 -08:00
|
|
|
// CPU timing stats (milliseconds, last frame).
|
|
|
|
|
double getLastUpdateMs() const { return lastUpdateMs; }
|
|
|
|
|
double getLastRenderMs() const { return lastRenderMs; }
|
|
|
|
|
double getLastCameraUpdateMs() const { return lastCameraUpdateMs; }
|
|
|
|
|
double getLastTerrainRenderMs() const { return lastTerrainRenderMs; }
|
|
|
|
|
double getLastWMORenderMs() const { return lastWMORenderMs; }
|
|
|
|
|
double getLastM2RenderMs() const { return lastM2RenderMs; }
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Audio coordinator — owned by Application, set via setAudioCoordinator().
|
2026-04-02 00:21:21 +03:00
|
|
|
void setAudioCoordinator(audio::AudioCoordinator* ac) { audioCoordinator_ = ac; }
|
|
|
|
|
audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_; }
|
2026-02-17 16:26:49 -08:00
|
|
|
game::ZoneManager* getZoneManager() { return zoneManager.get(); }
|
2026-02-10 13:48:50 -08:00
|
|
|
LightingManager* getLightingManager() { return lightingManager.get(); }
|
2026-02-03 16:21:48 -08:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
private:
|
2026-02-20 20:31:04 -08:00
|
|
|
void runDeferredWorldInitStep(float deltaTime);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
core::Window* window = nullptr;
|
|
|
|
|
std::unique_ptr<Camera> camera;
|
|
|
|
|
std::unique_ptr<CameraController> cameraController;
|
|
|
|
|
std::unique_ptr<TerrainRenderer> terrainRenderer;
|
|
|
|
|
std::unique_ptr<TerrainManager> terrainManager;
|
|
|
|
|
std::unique_ptr<PerformanceHUD> performanceHUD;
|
|
|
|
|
std::unique_ptr<WaterRenderer> waterRenderer;
|
|
|
|
|
std::unique_ptr<Skybox> skybox;
|
|
|
|
|
std::unique_ptr<Celestial> celestial;
|
|
|
|
|
std::unique_ptr<StarField> starField;
|
|
|
|
|
std::unique_ptr<Clouds> clouds;
|
|
|
|
|
std::unique_ptr<LensFlare> lensFlare;
|
|
|
|
|
std::unique_ptr<Weather> weather;
|
2026-03-13 09:52:23 -07:00
|
|
|
std::unique_ptr<Lightning> lightning;
|
2026-02-10 13:48:50 -08:00
|
|
|
std::unique_ptr<LightingManager> lightingManager;
|
2026-02-10 19:30:45 -08:00
|
|
|
std::unique_ptr<SkySystem> skySystem; // Coordinator for sky rendering
|
2026-02-02 12:24:50 -08:00
|
|
|
std::unique_ptr<SwimEffects> swimEffects;
|
2026-02-09 01:24:17 -08:00
|
|
|
std::unique_ptr<MountDust> mountDust;
|
2026-02-19 20:36:25 -08:00
|
|
|
std::unique_ptr<LevelUpEffect> levelUpEffect;
|
2026-02-19 21:13:13 -08:00
|
|
|
std::unique_ptr<ChargeEffect> chargeEffect;
|
2026-02-02 12:24:50 -08:00
|
|
|
std::unique_ptr<CharacterRenderer> characterRenderer;
|
|
|
|
|
std::unique_ptr<WMORenderer> wmoRenderer;
|
|
|
|
|
std::unique_ptr<M2Renderer> m2Renderer;
|
|
|
|
|
std::unique_ptr<Minimap> minimap;
|
2026-02-21 19:41:21 -08:00
|
|
|
std::unique_ptr<WorldMap> worldMap;
|
2026-02-09 23:41:38 -08:00
|
|
|
std::unique_ptr<QuestMarkerRenderer> questMarkerRenderer;
|
2026-04-02 00:21:21 +03:00
|
|
|
audio::AudioCoordinator* audioCoordinator_ = nullptr; // Owned by Application
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
std::unique_ptr<AnimationController> animationController_; // §4.2
|
2026-02-02 12:24:50 -08:00
|
|
|
std::unique_ptr<game::ZoneManager> zoneManager;
|
2026-02-21 19:41:21 -08:00
|
|
|
// Shadow mapping (Vulkan)
|
2026-02-23 04:59:39 -08:00
|
|
|
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
2026-03-09 22:14:32 -07:00
|
|
|
// Per-frame shadow resources: each in-flight frame has its own depth image and
|
|
|
|
|
// framebuffer so that frame N's shadow read and frame N+1's shadow write don't
|
|
|
|
|
// race on the same image across concurrent GPU submissions.
|
|
|
|
|
// Array size must match MAX_FRAMES (= 2, defined in the private section below).
|
|
|
|
|
VkImage shadowDepthImage[2] = {};
|
|
|
|
|
VmaAllocation shadowDepthAlloc[2] = {};
|
|
|
|
|
VkImageView shadowDepthView[2] = {};
|
2026-02-21 19:41:21 -08:00
|
|
|
VkSampler shadowSampler = VK_NULL_HANDLE;
|
|
|
|
|
VkRenderPass shadowRenderPass = VK_NULL_HANDLE;
|
2026-03-09 22:14:32 -07:00
|
|
|
VkFramebuffer shadowFramebuffer[2] = {};
|
|
|
|
|
VkImageLayout shadowDepthLayout_[2] = {};
|
2026-02-04 16:08:35 -08:00
|
|
|
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
|
2026-02-04 16:22:18 -08:00
|
|
|
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
|
|
|
|
bool shadowCenterInitialized = false;
|
2026-02-21 02:23:08 -08:00
|
|
|
bool shadowsEnabled = true;
|
2026-03-07 22:29:06 -08:00
|
|
|
float shadowDistance_ = 300.0f; // Shadow frustum half-extent (default: 300 units)
|
2026-02-25 10:22:05 -08:00
|
|
|
uint32_t shadowFrameCounter_ = 0;
|
2026-02-23 08:47:38 -08:00
|
|
|
|
2026-02-04 16:08:35 -08:00
|
|
|
|
2026-02-05 16:11:24 -08:00
|
|
|
public:
|
2026-02-22 05:58:45 -08:00
|
|
|
// Character preview registration (for off-screen composite pass)
|
|
|
|
|
void registerPreview(CharacterPreview* preview);
|
|
|
|
|
void unregisterPreview(CharacterPreview* preview);
|
|
|
|
|
|
2026-02-05 16:11:24 -08:00
|
|
|
void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; }
|
|
|
|
|
bool areShadowsEnabled() const { return shadowsEnabled; }
|
2026-03-07 22:29:06 -08:00
|
|
|
void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 500.0f); }
|
2026-03-06 20:38:58 -08:00
|
|
|
float getShadowDistance() const { return shadowDistance_; }
|
2026-02-22 02:59:24 -08:00
|
|
|
void setMsaaSamples(VkSampleCountFlagBits samples);
|
2026-02-05 16:11:24 -08:00
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Post-process pipeline API — delegates to PostProcessPipeline (§4.3)
|
|
|
|
|
PostProcessPipeline* getPostProcessPipeline() const;
|
2026-03-07 22:03:28 -08:00
|
|
|
void setFSREnabled(bool enabled);
|
2026-03-07 23:13:01 -08:00
|
|
|
void setFSR2Enabled(bool enabled);
|
2026-03-07 22:03:28 -08:00
|
|
|
|
2026-03-06 19:15:34 -08:00
|
|
|
void setWaterRefractionEnabled(bool enabled);
|
|
|
|
|
bool isWaterRefractionEnabled() const;
|
|
|
|
|
|
2026-02-05 16:11:24 -08:00
|
|
|
private:
|
2026-02-22 03:37:47 -08:00
|
|
|
void applyMsaaChange();
|
|
|
|
|
VkSampleCountFlagBits pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
|
bool msaaChangePending_ = false;
|
2026-02-04 16:08:35 -08:00
|
|
|
void renderShadowPass();
|
|
|
|
|
glm::mat4 computeLightSpaceMatrix();
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
pipeline::AssetManager* cachedAssetManager = nullptr;
|
2026-03-17 18:23:05 -07:00
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Spell visual effects — owned SpellVisualSystem (extracted from Renderer §4.4)
|
|
|
|
|
std::unique_ptr<SpellVisualSystem> spellVisualSystem_;
|
|
|
|
|
|
|
|
|
|
// Post-process pipeline — owns all FSR/FXAA/FSR2 state (extracted §4.3)
|
|
|
|
|
std::unique_ptr<PostProcessPipeline> postProcessPipeline_;
|
2026-03-17 18:23:05 -07:00
|
|
|
|
2026-03-20 06:29:33 -07:00
|
|
|
bool playerIndoors_ = false; // Cached WMO inside state for macro conditionals
|
2026-02-20 20:31:04 -08:00
|
|
|
bool deferredWorldInitEnabled_ = true;
|
|
|
|
|
bool deferredWorldInitPending_ = false;
|
|
|
|
|
uint8_t deferredWorldInitStage_ = 0;
|
|
|
|
|
float deferredWorldInitCooldown_ = 0.0f;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Third-person character state
|
|
|
|
|
glm::vec3 characterPosition = glm::vec3(0.0f);
|
|
|
|
|
uint32_t characterInstanceId = 0;
|
|
|
|
|
float characterYaw = 0.0f;
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-04-05 19:30:44 +03:00
|
|
|
// Selection circle + overlay rendering (owned by OverlaySystem)
|
|
|
|
|
std::unique_ptr<OverlaySystem> overlaySystem_;
|
2026-03-07 22:03:28 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
|
2026-02-07 17:59:40 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Vulkan frame state
|
|
|
|
|
VkContext* vkCtx = nullptr;
|
|
|
|
|
VkCommandBuffer currentCmd = VK_NULL_HANDLE;
|
|
|
|
|
uint32_t currentImageIndex = 0;
|
|
|
|
|
|
|
|
|
|
// Per-frame UBO + descriptors (set 0)
|
|
|
|
|
static constexpr uint32_t MAX_FRAMES = 2;
|
|
|
|
|
VkDescriptorSetLayout perFrameSetLayout = VK_NULL_HANDLE;
|
|
|
|
|
VkDescriptorPool sceneDescriptorPool = VK_NULL_HANDLE;
|
|
|
|
|
VkDescriptorSet perFrameDescSets[MAX_FRAMES] = {};
|
|
|
|
|
VkBuffer perFrameUBOs[MAX_FRAMES] = {};
|
|
|
|
|
VmaAllocation perFrameUBOAllocs[MAX_FRAMES] = {};
|
|
|
|
|
void* perFrameUBOMapped[MAX_FRAMES] = {};
|
|
|
|
|
GPUPerFrameData currentFrameData{};
|
|
|
|
|
float globalTime = 0.0f;
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
// Per-frame reflection UBO (mirrors camera for planar reflections)
|
|
|
|
|
VkBuffer reflPerFrameUBO = VK_NULL_HANDLE;
|
|
|
|
|
VmaAllocation reflPerFrameUBOAlloc = VK_NULL_HANDLE;
|
|
|
|
|
void* reflPerFrameUBOMapped = nullptr;
|
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
|
|
|
VkDescriptorSet reflPerFrameDescSet[MAX_FRAMES] = {};
|
2026-02-22 22:34:48 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
bool createPerFrameResources();
|
|
|
|
|
void destroyPerFrameResources();
|
|
|
|
|
void updatePerFrameUBO();
|
2026-02-22 22:34:48 -08:00
|
|
|
void setupWater1xPass();
|
|
|
|
|
void renderReflectionPass();
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// ── Multithreaded secondary command buffer recording ──
|
|
|
|
|
// Indices into secondaryCmds_ arrays
|
|
|
|
|
static constexpr uint32_t SEC_SKY = 0; // sky (main thread)
|
|
|
|
|
static constexpr uint32_t SEC_TERRAIN = 1; // terrain (worker 0)
|
|
|
|
|
static constexpr uint32_t SEC_WMO = 2; // WMO (worker 1)
|
|
|
|
|
static constexpr uint32_t SEC_CHARS = 3; // selection circle + characters (main thread)
|
|
|
|
|
static constexpr uint32_t SEC_M2 = 4; // M2 + particles + glow (worker 2)
|
|
|
|
|
static constexpr uint32_t SEC_POST = 5; // water + weather + effects (main thread)
|
|
|
|
|
static constexpr uint32_t SEC_IMGUI = 6; // ImGui (main thread, non-FSR only)
|
|
|
|
|
static constexpr uint32_t NUM_SECONDARIES = 7;
|
|
|
|
|
static constexpr uint32_t NUM_WORKERS = 3; // terrain, WMO, M2
|
|
|
|
|
|
|
|
|
|
// Per-worker command pools (thread-safe: one pool per thread)
|
|
|
|
|
VkCommandPool workerCmdPools_[NUM_WORKERS] = {};
|
|
|
|
|
// Main-thread command pool for its secondary buffers
|
|
|
|
|
VkCommandPool mainSecondaryCmdPool_ = VK_NULL_HANDLE;
|
|
|
|
|
// Pre-allocated secondary command buffers [secondaryIndex][frameInFlight]
|
|
|
|
|
VkCommandBuffer secondaryCmds_[NUM_SECONDARIES][MAX_FRAMES] = {};
|
|
|
|
|
|
|
|
|
|
bool parallelRecordingEnabled_ = false; // set true after pools/buffers created
|
2026-03-24 13:05:27 -07:00
|
|
|
bool endFrameInlineMode_ = false; // true when endFrame switched to INLINE render pass
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
float lastDeltaTime_ = 0.0f; // cached for post-process pipeline
|
2026-03-07 22:03:28 -08:00
|
|
|
bool createSecondaryCommandResources();
|
|
|
|
|
void destroySecondaryCommandResources();
|
|
|
|
|
VkCommandBuffer beginSecondary(uint32_t secondaryIndex);
|
|
|
|
|
void setSecondaryViewportScissor(VkCommandBuffer cmd);
|
|
|
|
|
|
|
|
|
|
// Cached render pass state for secondary buffer inheritance
|
|
|
|
|
VkRenderPass activeRenderPass_ = VK_NULL_HANDLE;
|
|
|
|
|
VkFramebuffer activeFramebuffer_ = VK_NULL_HANDLE;
|
|
|
|
|
VkExtent2D activeRenderExtent_ = {0, 0};
|
|
|
|
|
|
2026-02-22 05:58:45 -08:00
|
|
|
// Active character previews for off-screen rendering
|
|
|
|
|
std::vector<CharacterPreview*> activePreviews_;
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
bool terrainEnabled = true;
|
|
|
|
|
bool terrainLoaded = false;
|
2026-02-03 16:21:48 -08:00
|
|
|
|
2026-03-13 00:59:36 -07:00
|
|
|
bool ghostMode_ = false; // set each frame from gameHandler->isPlayerGhost()
|
|
|
|
|
|
feat(animation): 452 named constants, 30-phase character animation state machine
Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND,
anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1)
lookup, and flyVariant() compact 218-element ground→FLY_* resolver.
Expand AnimationController into a full state machine with 20+ named states:
spell cast (directed→omni→cast fallback chain, instant one-shot release),
hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle,
stealth animation substitution, loot, fishing channel, sit/sleep/kneel
down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons
(BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY,
vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS),
emote state variants, off-hand/pierce/dual-wield alternation, NPC
birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell.
Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo
(12 constants) namespaces; replace all magic numbers in spell_handler.cpp.
Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire
GameObjectStateCallback. Add DBC cross-validation on world entry.
Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and
asset_pipeline_gui.py. Add tests/test_animation_ids.cpp.
Bug fixes included:
- Stand state 1 was animating READY_2H(27) — fixed to SITTING(97)
- Spell casts ended freeze-frame — add one-shot release animation
- NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff)
- Chair sits (states 2/4/5/6) incorrectly played floor-sit transition
- STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
|
|
|
// Render Graph — declarative pass ordering with automatic barriers
|
feat(rendering): GPU architecture + visual quality fixes
M2 GPU instancing
- M2InstanceGPU SSBO (96 B/entry, double-buffered, 16384 max)
- Group opaque instances by (modelId, LOD); single vkCmdDrawIndexed per group
- boneBase field indexes into mega bone SSBO via gl_InstanceIndex
Indirect terrain drawing
- 24 MB mega index buffer (6M uint32) + 64 MB mega vertex buffer
- CPU builds VkDrawIndexedIndirectCommand per visible chunk
- Single VB/IB bind per frame; shadow pass reuses mega buffers
- Replaced vkCmdDrawIndexedIndirect with direct vkCmdDrawIndexed to fix
host-mapped buffer race condition that caused terrain flickering
GPU frustum culling (compute shader)
- m2_cull.comp.glsl: 64-thread workgroups, sphere-vs-6-planes + distance cull
- CullInstanceGPU SSBO input, uint visibility[] output, double-buffered
- dispatchCullCompute() runs before main pass via render graph node
Consolidated bone matrix SSBOs
- 16 MB double-buffered mega bone SSBO (2048 instances × 128 bones)
- Eliminated per-instance descriptor sets; one megaBoneSet_ per frame
- prepareRender() packs bone matrices consecutively into current frame slot
Render graph / frame graph
- RenderGraph: RGResource handles, RGPass nodes, Kahn topological sort
- Automatic VkImageMemoryBarrier/VkBufferMemoryBarrier between passes
- Passes: minimap_composite, worldmap_composite, preview_composite,
shadow_pass, reflection_pass, compute_cull
- beginFrame() uses buildFrameGraph() + renderGraph_->execute(cmd)
Pipeline derivatives
- PipelineBuilder::setFlags/setBasePipeline for VK_PIPELINE_CREATE_DERIVATIVE_BIT
- M2 opaque = base; alphaTest/alpha/additive are derivatives
- Applied to terrain (wireframe) and WMO (alpha-test) renderers
Rendering bug fixes:
- fix(shadow): compute lightSpaceMatrix before updatePerFrameUBO to eliminate
one-frame lag that caused shadow trails and flicker on moving objects
- fix(shadow): scale depth bias with shadowDistance_ instead of hardcoded 0.8f
to prevent acne at close range and gaps at far range
- fix(visibility): WMO group distance threshold 500u → 1200u to match terrain
view distance; buildings were disappearing on the horizon
- fix(precision): camera near plane 0.05 → 0.5 (ratio 600K:1 → 60K:1),
eliminating Z-fighting and improving frustum plane extraction stability
- fix(streaming): terrain load radius 4 → 6 tiles (~2133u → ~3200u) to exceed
M2 render distance (2800u) and eliminate pop-in when camera turns;
unload radius 7 → 9; spawn radius 3 → 4
- fix(visibility): ground-detail M2 distance multiplier 0.75 → 0.9 to reduce
early pop of grass and debris
2026-04-04 13:43:16 +03:00
|
|
|
std::unique_ptr<RenderGraph> renderGraph_;
|
|
|
|
|
void buildFrameGraph(game::GameHandler* gameHandler);
|
|
|
|
|
|
feat(rendering): add HiZ occlusion culling & fix WMO interior shadows
Implement GPU-driven Hierarchical-Z occlusion culling for M2 doodads
using a depth pyramid built from the previous frame's depth buffer.
The cull shader projects bounding spheres via prevViewProj (temporal
reprojection) and samples the HiZ pyramid to reject hidden objects
before the main render pass.
Key implementation details:
- Separate early compute submission (beginSingleTimeCommands + fence
wait) eliminates 2-frame visibility staleness
- Conservative safeguards prevent false culls: screen-edge guard,
full VP row-vector AABB projection (Cauchy-Schwarz), 50% sphere
inflation, depth bias, mip+1, min screen size threshold, camera
motion dampening (auto-disable on fast rotations), and per-instance
previouslyVisible flag tracking
- Graceful fallback to frustum-only culling if HiZ init fails
Fix dark WMO interiors by gating shadow map sampling on isInterior==0
in the WMO fragment shader. Interior groups (flag 0x2000) now rely
solely on pre-baked MOCV vertex-color lighting + MOHD ambient color.
Disable interiorDarken globally (was incorrectly darkening outdoor M2s
when camera was inside a WMO). Use isInsideInteriorWMO() instead of
isInsideWMO() for correct indoor detection.
New files:
- hiz_system.hpp/cpp: pyramid image management, compute pipeline,
descriptors, mip-chain build dispatch, resize handling
- hiz_build.comp.glsl: MAX-depth 2x2 reduction compute shader
- m2_cull_hiz.comp.glsl: frustum + HiZ occlusion cull compute shader
- test_indoor_shadows.cpp: 14 unit tests for shadow/interior contracts
Modified:
- CullUniformsGPU expanded 128->272 bytes (HiZ params, viewProj,
prevViewProj)
- Depth buffer images gain VK_IMAGE_USAGE_SAMPLED_BIT for HiZ reads
- wmo.frag.glsl: interior branch before unlit, shadow skip for 0x2000
- Render graph: hiz_build + compute_cull disabled (run in early compute)
- .gitignore: ignore compiled .spv binaries
- MEGA_BONE_MAX_INSTANCES: 2048 -> 4096
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 16:40:59 +03:00
|
|
|
// HiZ occlusion culling — builds depth pyramid each frame
|
|
|
|
|
std::unique_ptr<HiZSystem> hizSystem_;
|
|
|
|
|
|
2026-02-03 16:21:48 -08:00
|
|
|
// CPU timing stats (last frame/update).
|
|
|
|
|
double lastUpdateMs = 0.0;
|
|
|
|
|
double lastRenderMs = 0.0;
|
|
|
|
|
double lastCameraUpdateMs = 0.0;
|
|
|
|
|
double lastTerrainRenderMs = 0.0;
|
|
|
|
|
double lastWMORenderMs = 0.0;
|
|
|
|
|
double lastM2RenderMs = 0.0;
|
2026-02-02 12:24:50 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace rendering
|
|
|
|
|
} // namespace wowee
|