#pragma once #include #include #include #include #include #include #include #include #include #include #include #include "rendering/vk_frame_data.hpp" #include "rendering/vk_utils.hpp" #include "rendering/sky_system.hpp" namespace wowee { namespace core { class Window; } namespace rendering { class VkContext; } namespace game { class World; class ZoneManager; class GameHandler; } namespace audio { class AudioCoordinator; class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; class NpcVoiceManager; class AmbientSoundManager; class UiSoundManager; class CombatSoundManager; class SpellSoundManager; class MovementSoundManager; enum class FootstepSurface : uint8_t; enum class VoiceType; } 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; class Lightning; class LightingManager; class SwimEffects; class MountDust; class LevelUpEffect; class ChargeEffect; class CharacterRenderer; class WMORenderer; class M2Renderer; class Minimap; class WorldMap; class QuestMarkerRenderer; class CharacterPreview; class AmdFsr3Runtime; class SpellVisualSystem; class PostProcessPipeline; class Renderer { public: Renderer(); ~Renderer(); bool initialize(core::Window* window); void shutdown(); void beginFrame(); void endFrame(); void renderWorld(game::World* world, game::GameHandler* gameHandler = nullptr); /** * 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); /** * 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); /** * 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(); } 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; } Weather* getWeather() const { return weather.get(); } Lightning* getLightning() const { return lightning.get(); } CharacterRenderer* getCharacterRenderer() const { return characterRenderer.get(); } WMORenderer* getWMORenderer() const { return wmoRenderer.get(); } M2Renderer* getM2Renderer() const { return m2Renderer.get(); } Minimap* getMinimap() const { return minimap.get(); } WorldMap* getWorldMap() const { return worldMap.get(); } QuestMarkerRenderer* getQuestMarkerRenderer() const { return questMarkerRenderer.get(); } SkySystem* getSkySystem() const { return skySystem.get(); } const std::string& getCurrentZoneName() const { return currentZoneName; } bool isPlayerIndoors() const { return playerIndoors_; } VkContext* getVkContext() const { return vkCtx; } VkDescriptorSetLayout getPerFrameSetLayout() const { return perFrameSetLayout; } VkRenderPass getShadowRenderPass() const { return shadowRenderPass; } // 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; } void setCharacterYaw(float yawDeg) { characterYaw = yawDeg; } // Emote support void playEmote(const std::string& emoteName); void triggerLevelUpEffect(const glm::vec3& position); void cancelEmote(); // Screenshot capture — copies swapchain image to PNG file bool captureScreenshot(const std::string& outputPath); // Spell visual effects (SMSG_PLAY_SPELL_VISUAL / SMSG_PLAY_SPELL_IMPACT) // Delegates to SpellVisualSystem (owned by Renderer) void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition, bool useImpactKit = false); SpellVisualSystem* getSpellVisualSystem() const { return spellVisualSystem_.get(); } bool isEmoteActive() const { return emoteActive; } static std::string getEmoteText(const std::string& emoteName, const std::string* targetName = nullptr); static uint32_t getEmoteDbcId(const std::string& emoteName); static std::string getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName, const std::string* targetName = nullptr); static uint32_t getEmoteAnimByDbcId(uint32_t dbcId); // Targeting support void setTargetPosition(const glm::vec3* pos); void setInCombat(bool combat) { inCombat_ = combat; } void resetCombatVisualState(); bool isMoving() const; void triggerMeleeSwing(); void setEquippedWeaponType(uint32_t inventoryType) { equippedWeaponInvType_ = inventoryType; meleeAnimId = 0; } void setCharging(bool charging) { charging_ = charging; } bool isCharging() const { return charging_; } void startChargeEffect(const glm::vec3& position, const glm::vec3& direction); void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction); void stopChargeEffect(); // Mount rendering void setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float heightOffset, const std::string& modelPath = ""); void setTaxiFlight(bool onTaxi) { taxiFlight_ = onTaxi; } void setMountPitchRoll(float pitch, float roll) { mountPitch_ = pitch; mountRoll_ = roll; } void clearMount(); bool isMounted() const { return mountInstanceId_ != 0; } // Selection circle for targeted entity void setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color); void clearSelectionCircle(); // 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; } // Audio accessors — delegate to AudioCoordinator (owned by Application). // These pass-throughs remain until §4.2 moves animation audio out of Renderer. void setAudioCoordinator(audio::AudioCoordinator* ac) { audioCoordinator_ = ac; } audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_; } audio::MusicManager* getMusicManager(); audio::FootstepManager* getFootstepManager(); audio::ActivitySoundManager* getActivitySoundManager(); audio::MountSoundManager* getMountSoundManager(); audio::NpcVoiceManager* getNpcVoiceManager(); audio::AmbientSoundManager* getAmbientSoundManager(); audio::UiSoundManager* getUiSoundManager(); audio::CombatSoundManager* getCombatSoundManager(); audio::SpellSoundManager* getSpellSoundManager(); audio::MovementSoundManager* getMovementSoundManager(); game::ZoneManager* getZoneManager() { return zoneManager.get(); } LightingManager* getLightingManager() { return lightingManager.get(); } private: void runDeferredWorldInitStep(float deltaTime); core::Window* window = nullptr; std::unique_ptr camera; std::unique_ptr cameraController; std::unique_ptr terrainRenderer; std::unique_ptr terrainManager; std::unique_ptr performanceHUD; std::unique_ptr waterRenderer; std::unique_ptr skybox; std::unique_ptr celestial; std::unique_ptr starField; std::unique_ptr clouds; std::unique_ptr lensFlare; std::unique_ptr weather; std::unique_ptr lightning; std::unique_ptr lightingManager; std::unique_ptr skySystem; // Coordinator for sky rendering std::unique_ptr swimEffects; std::unique_ptr mountDust; std::unique_ptr levelUpEffect; std::unique_ptr chargeEffect; std::unique_ptr characterRenderer; std::unique_ptr wmoRenderer; std::unique_ptr m2Renderer; std::unique_ptr minimap; std::unique_ptr worldMap; std::unique_ptr questMarkerRenderer; audio::AudioCoordinator* audioCoordinator_ = nullptr; // Owned by Application std::unique_ptr zoneManager; // Shadow mapping (Vulkan) static constexpr uint32_t SHADOW_MAP_SIZE = 4096; // 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] = {}; VkSampler shadowSampler = VK_NULL_HANDLE; VkRenderPass shadowRenderPass = VK_NULL_HANDLE; VkFramebuffer shadowFramebuffer[2] = {}; VkImageLayout shadowDepthLayout_[2] = {}; glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); glm::vec3 shadowCenter = glm::vec3(0.0f); bool shadowCenterInitialized = false; bool shadowsEnabled = true; float shadowDistance_ = 300.0f; // Shadow frustum half-extent (default: 300 units) uint32_t shadowFrameCounter_ = 0; public: // Character preview registration (for off-screen composite pass) void registerPreview(CharacterPreview* preview); void unregisterPreview(CharacterPreview* preview); void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; } bool areShadowsEnabled() const { return shadowsEnabled; } void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 500.0f); } float getShadowDistance() const { return shadowDistance_; } void setMsaaSamples(VkSampleCountFlagBits samples); // Post-process pipeline API — delegates to PostProcessPipeline (§4.3) PostProcessPipeline* getPostProcessPipeline() const; void setFXAAEnabled(bool enabled); bool isFXAAEnabled() const; void setFSREnabled(bool enabled); bool isFSREnabled() const; void setFSRQuality(float scaleFactor); void setFSRSharpness(float sharpness); float getFSRScaleFactor() const; float getFSRSharpness() const; void setFSR2Enabled(bool enabled); bool isFSR2Enabled() const; void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY); void setAmdFsr3FramegenEnabled(bool enabled); bool isAmdFsr3FramegenEnabled() const; float getFSR2JitterSign() const; float getFSR2MotionVecScaleX() const; float getFSR2MotionVecScaleY() const; bool isAmdFsr2SdkAvailable() const; bool isAmdFsr3FramegenSdkAvailable() const; bool isAmdFsr3FramegenRuntimeActive() const; bool isAmdFsr3FramegenRuntimeReady() const; const char* getAmdFsr3FramegenRuntimePath() const; const std::string& getAmdFsr3FramegenRuntimeError() const; size_t getAmdFsr3UpscaleDispatchCount() const; size_t getAmdFsr3FramegenDispatchCount() const; size_t getAmdFsr3FallbackCount() const; void setBrightness(float b); float getBrightness() const; void setWaterRefractionEnabled(bool enabled); bool isWaterRefractionEnabled() const; private: void applyMsaaChange(); VkSampleCountFlagBits pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT; bool msaaChangePending_ = false; void renderShadowPass(); glm::mat4 computeLightSpaceMatrix(); pipeline::AssetManager* cachedAssetManager = nullptr; // Spell visual effects — owned SpellVisualSystem (extracted from Renderer §4.4) std::unique_ptr spellVisualSystem_; // Post-process pipeline — owns all FSR/FXAA/FSR2 state (extracted §4.3) std::unique_ptr postProcessPipeline_; uint32_t currentZoneId = 0; std::string currentZoneName; bool inTavern_ = false; bool inBlacksmith_ = false; bool playerIndoors_ = false; // Cached WMO inside state for macro conditionals float musicSwitchCooldown_ = 0.0f; bool deferredWorldInitEnabled_ = true; bool deferredWorldInitPending_ = false; uint8_t deferredWorldInitStage_ = 0; float deferredWorldInitCooldown_ = 0.0f; // Third-person character state glm::vec3 characterPosition = glm::vec3(0.0f); uint32_t characterInstanceId = 0; float characterYaw = 0.0f; // Character animation state enum class CharAnimState { IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING, EMOTE, SWIM_IDLE, SWIM, MELEE_SWING, MOUNT, CHARGE, COMBAT_IDLE }; CharAnimState charAnimState = CharAnimState::IDLE; float locomotionStopGraceTimer_ = 0.0f; bool locomotionWasSprinting_ = false; uint32_t lastPlayerAnimRequest_ = UINT32_MAX; bool lastPlayerAnimLoopRequest_ = true; void updateCharacterAnimation(); bool isFootstepAnimationState() const; bool shouldTriggerFootstepEvent(uint32_t animationId, float animationTimeMs, float animationDurationMs); audio::FootstepSurface resolveFootstepSurface() const; uint32_t resolveMeleeAnimId(); // Emote state bool emoteActive = false; uint32_t emoteAnimId = 0; bool emoteLoop = false; // Target facing const glm::vec3* targetPosition = nullptr; bool inCombat_ = false; // Selection circle rendering (Vulkan) VkPipeline selCirclePipeline = VK_NULL_HANDLE; VkPipelineLayout selCirclePipelineLayout = VK_NULL_HANDLE; ::VkBuffer selCircleVertBuf = VK_NULL_HANDLE; VmaAllocation selCircleVertAlloc = VK_NULL_HANDLE; ::VkBuffer selCircleIdxBuf = VK_NULL_HANDLE; VmaAllocation selCircleIdxAlloc = VK_NULL_HANDLE; int selCircleVertCount = 0; void initSelectionCircle(); void renderSelectionCircle(const glm::mat4& view, const glm::mat4& projection, VkCommandBuffer overrideCmd = VK_NULL_HANDLE); glm::vec3 selCirclePos{0.0f}; glm::vec3 selCircleColor{1.0f, 0.0f, 0.0f}; float selCircleRadius = 1.5f; bool selCircleVisible = false; // Fullscreen color overlay (underwater tint) VkPipeline overlayPipeline = VK_NULL_HANDLE; VkPipelineLayout overlayPipelineLayout = VK_NULL_HANDLE; void initOverlayPipeline(); void renderOverlay(const glm::vec4& color, VkCommandBuffer overrideCmd = VK_NULL_HANDLE); // Footstep event tracking (animation-driven) uint32_t footstepLastAnimationId = 0; float footstepLastNormTime = 0.0f; bool footstepNormInitialized = false; // Footstep surface cache (avoid expensive queries every step) mutable audio::FootstepSurface cachedFootstepSurface{}; mutable glm::vec3 cachedFootstepPosition{0.0f, 0.0f, 0.0f}; mutable float cachedFootstepUpdateTimer{999.0f}; // Force initial query // Mount footstep tracking (separate from player's) uint32_t mountFootstepLastAnimId = 0; float mountFootstepLastNormTime = 0.0f; bool mountFootstepNormInitialized = false; bool sfxStateInitialized = false; bool sfxPrevGrounded = true; bool sfxPrevJumping = false; bool sfxPrevFalling = false; bool sfxPrevSwimming = false; bool charging_ = false; float meleeSwingTimer = 0.0f; float meleeSwingCooldown = 0.0f; float meleeAnimDurationMs = 0.0f; uint32_t meleeAnimId = 0; uint32_t equippedWeaponInvType_ = 0; // Mount state // Mount animation capabilities (discovered at mount time, varies per model) struct MountAnimSet { uint32_t jumpStart = 0; // Jump start animation uint32_t jumpLoop = 0; // Jump airborne loop uint32_t jumpEnd = 0; // Jump landing uint32_t rearUp = 0; // Rear-up / special flourish uint32_t run = 0; // Run animation (discovered, don't assume) uint32_t stand = 0; // Stand animation (discovered) std::vector fidgets; // Idle fidget animations (head turn, tail swish, etc.) }; enum class MountAction { None, Jump, RearUp }; uint32_t mountInstanceId_ = 0; float mountHeightOffset_ = 0.0f; float mountPitch_ = 0.0f; // Up/down tilt (radians) float mountRoll_ = 0.0f; // Left/right banking (radians) int mountSeatAttachmentId_ = -1; // -1 unknown, -2 unavailable glm::vec3 smoothedMountSeatPos_ = glm::vec3(0.0f); bool mountSeatSmoothingInit_ = false; float prevMountYaw_ = 0.0f; // Previous yaw for turn rate calculation (procedural lean) float lastDeltaTime_ = 0.0f; // Cached for use in updateCharacterAnimation() MountAction mountAction_ = MountAction::None; // Current mount action (jump/rear-up) uint32_t mountActionPhase_ = 0; // 0=start, 1=loop, 2=end (for jump chaining) MountAnimSet mountAnims_; // Cached animation IDs for current mount float mountIdleFidgetTimer_ = 0.0f; // Timer for random idle fidgets float mountIdleSoundTimer_ = 0.0f; // Timer for ambient idle sounds uint32_t mountActiveFidget_ = 0; // Currently playing fidget animation ID (0 = none) bool taxiFlight_ = false; bool taxiAnimsLogged_ = false; // 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; // Per-frame reflection UBO (mirrors camera for planar reflections) VkBuffer reflPerFrameUBO = VK_NULL_HANDLE; VmaAllocation reflPerFrameUBOAlloc = VK_NULL_HANDLE; void* reflPerFrameUBOMapped = nullptr; VkDescriptorSet reflPerFrameDescSet = VK_NULL_HANDLE; bool createPerFrameResources(); void destroyPerFrameResources(); void updatePerFrameUBO(); void setupWater1xPass(); void renderReflectionPass(); // ── 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 bool endFrameInlineMode_ = false; // true when endFrame switched to INLINE render pass 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}; // Active character previews for off-screen rendering std::vector activePreviews_; bool terrainEnabled = true; bool terrainLoaded = false; bool ghostMode_ = false; // set each frame from gameHandler->isPlayerGhost() // 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; }; } // namespace rendering } // namespace wowee