#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; } 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 AnimationController; class LevelUpEffect; class ChargeEffect; class SwimEffects; 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 — delegates to AnimationController (§4.2) 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; 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 — delegates to AnimationController (§4.2) void setTargetPosition(const glm::vec3* pos); void setInCombat(bool combat); void resetCombatVisualState(); bool isMoving() const; void triggerMeleeSwing(); void setEquippedWeaponType(uint32_t inventoryType); void setCharging(bool charging); bool isCharging() const; void startChargeEffect(const glm::vec3& position, const glm::vec3& direction); void emitChargeEffect(const glm::vec3& position, const glm::vec3& direction); void stopChargeEffect(); // Mount rendering — delegates to AnimationController (§4.2) void setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float heightOffset, const std::string& modelPath = ""); void setTaxiFlight(bool onTaxi); void setMountPitchRoll(float pitch, float roll); void clearMount(); bool isMounted() const; // AnimationController access (§4.2) AnimationController* getAnimationController() const { return animationController_.get(); } LevelUpEffect* getLevelUpEffect() const { return levelUpEffect.get(); } ChargeEffect* getChargeEffect() const { return chargeEffect.get(); } SwimEffects* getSwimEffects() const { return swimEffects.get(); } // 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 coordinator — owned by Application, set via setAudioCoordinator(). void setAudioCoordinator(audio::AudioCoordinator* ac) { audioCoordinator_ = ac; } audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_; } 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 animationController_; // §4.2 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; // 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); // 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 float lastDeltaTime_ = 0.0f; // cached for post-process pipeline 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