mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-03 20:03:50 +00:00
chore(renderer): refactor renderer and add post-process + spell visuals systems
- Updated core render pipeline and renderer integration in CMakeLists.txt, renderer.cpp, renderer.hpp - Added post-process pipeline module: - post_process_pipeline.hpp - post_process_pipeline.cpp - Added spell visual system module: - spell_visual_system.hpp - spell_visual_system.cpp - Adjusted application/audio integration: - application.cpp - audio_coordinator.cpp
This commit is contained in:
parent
1c0e9dd1df
commit
5ef600098a
9 changed files with 2803 additions and 2380 deletions
|
|
@ -611,6 +611,8 @@ set(WOWEE_SOURCES
|
|||
src/rendering/mount_dust.cpp
|
||||
src/rendering/levelup_effect.cpp
|
||||
src/rendering/charge_effect.cpp
|
||||
src/rendering/spell_visual_system.cpp
|
||||
src/rendering/post_process_pipeline.cpp
|
||||
src/rendering/loading_screen.cpp
|
||||
|
||||
# UI
|
||||
|
|
|
|||
282
include/rendering/post_process_pipeline.hpp
Normal file
282
include/rendering/post_process_pipeline.hpp
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <glm/glm.hpp>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include "rendering/vk_utils.hpp"
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
#include "ffx_fsr2.h"
|
||||
#include "ffx_fsr2_vk.h"
|
||||
#endif
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
class VkContext;
|
||||
class Camera;
|
||||
class AmdFsr3Runtime;
|
||||
|
||||
/// Returned by setFSREnabled/setFSR2Enabled when they need the Renderer
|
||||
/// to schedule an MSAA sample-count change (§4.3).
|
||||
struct MsaaChangeRequest {
|
||||
bool requested = false;
|
||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
};
|
||||
|
||||
/// PostProcessPipeline owns all FSR 1.0, FXAA, and FSR 2.2/3 state and
|
||||
/// orchestrates post-processing passes between the scene render pass and
|
||||
/// the final swapchain presentation (§4.3 extraction from Renderer).
|
||||
class PostProcessPipeline {
|
||||
public:
|
||||
PostProcessPipeline();
|
||||
~PostProcessPipeline();
|
||||
|
||||
void initialize(VkContext* ctx);
|
||||
void shutdown();
|
||||
|
||||
// --- Frame-loop integration (called from Renderer::beginFrame) ---
|
||||
|
||||
/// Lazy-create / lazy-destroy FSR/FXAA/FSR2 resources between frames.
|
||||
void manageResources();
|
||||
|
||||
/// Recreate post-process resources after swapchain resize.
|
||||
void handleSwapchainResize();
|
||||
|
||||
/// Apply FSR2 temporal jitter to the camera projection.
|
||||
void applyJitter(Camera* camera);
|
||||
|
||||
/// Returns the framebuffer the scene should render into.
|
||||
/// If no post-processing is active, returns VK_NULL_HANDLE (use swapchain).
|
||||
VkFramebuffer getSceneFramebuffer() const;
|
||||
|
||||
/// Returns the render extent for the active post-process pipeline.
|
||||
/// Falls back to swapchain extent if nothing is active.
|
||||
VkExtent2D getSceneRenderExtent() const;
|
||||
|
||||
/// True if any post-process pipeline is active (FSR/FXAA/FSR2).
|
||||
bool hasActivePostProcess() const;
|
||||
|
||||
/// True when FXAA alone (no FSR2) needs its own off-screen pass.
|
||||
bool useFXAAPostPass() const { return fxaa_.enabled; }
|
||||
|
||||
// --- Frame-loop integration (called from Renderer::endFrame) ---
|
||||
|
||||
/// Execute all post-processing passes. Returns true if an INLINE
|
||||
/// render pass was started (affects ImGui recording mode).
|
||||
bool executePostProcessing(VkCommandBuffer cmd, uint32_t imageIndex,
|
||||
Camera* camera, float deltaTime);
|
||||
|
||||
// --- MSAA interop (called from Renderer::applyMsaaChange) ---
|
||||
|
||||
/// Destroy FSR/FSR2/FXAA resources (they will be lazily recreated).
|
||||
void destroyAllResources();
|
||||
|
||||
/// True when FSR2 is active and MSAA changes should be blocked.
|
||||
bool isFsr2BlockingMsaa() const { return fsr2_.enabled; }
|
||||
|
||||
// --- Public API (delegated from Renderer) ---
|
||||
|
||||
// FXAA
|
||||
void setFXAAEnabled(bool enabled);
|
||||
bool isFXAAEnabled() const { return fxaa_.enabled; }
|
||||
|
||||
// FSR 1.0
|
||||
MsaaChangeRequest setFSREnabled(bool enabled);
|
||||
bool isFSREnabled() const { return fsr_.enabled; }
|
||||
void setFSRQuality(float scaleFactor);
|
||||
void setFSRSharpness(float sharpness);
|
||||
float getFSRScaleFactor() const { return fsr_.scaleFactor; }
|
||||
float getFSRSharpness() const { return fsr_.sharpness; }
|
||||
|
||||
// FSR 2.2
|
||||
MsaaChangeRequest setFSR2Enabled(bool enabled, Camera* camera);
|
||||
bool isFSR2Enabled() const { return fsr2_.enabled; }
|
||||
void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY);
|
||||
|
||||
// FSR3 Framegen
|
||||
void setAmdFsr3FramegenEnabled(bool enabled);
|
||||
bool isAmdFsr3FramegenEnabled() const { return fsr2_.amdFsr3FramegenEnabled; }
|
||||
float getFSR2JitterSign() const { return fsr2_.jitterSign; }
|
||||
float getFSR2MotionVecScaleX() const { return fsr2_.motionVecScaleX; }
|
||||
float getFSR2MotionVecScaleY() const { return fsr2_.motionVecScaleY; }
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
bool isAmdFsr2SdkAvailable() const { return true; }
|
||||
#else
|
||||
bool isAmdFsr2SdkAvailable() const { return false; }
|
||||
#endif
|
||||
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
||||
bool isAmdFsr3FramegenSdkAvailable() const { return true; }
|
||||
#else
|
||||
bool isAmdFsr3FramegenSdkAvailable() const { return false; }
|
||||
#endif
|
||||
bool isAmdFsr3FramegenRuntimeActive() const { return fsr2_.amdFsr3FramegenRuntimeActive; }
|
||||
bool isAmdFsr3FramegenRuntimeReady() const { return fsr2_.amdFsr3FramegenRuntimeReady; }
|
||||
const char* getAmdFsr3FramegenRuntimePath() const;
|
||||
const std::string& getAmdFsr3FramegenRuntimeError() const { return fsr2_.amdFsr3RuntimeLastError; }
|
||||
size_t getAmdFsr3UpscaleDispatchCount() const { return fsr2_.amdFsr3UpscaleDispatchCount; }
|
||||
size_t getAmdFsr3FramegenDispatchCount() const { return fsr2_.amdFsr3FramegenDispatchCount; }
|
||||
size_t getAmdFsr3FallbackCount() const { return fsr2_.amdFsr3FallbackCount; }
|
||||
|
||||
// Brightness (1.0 = default, <1 darkens, >1 brightens)
|
||||
void setBrightness(float b) { brightness_ = b; }
|
||||
float getBrightness() const { return brightness_; }
|
||||
|
||||
private:
|
||||
VkContext* vkCtx_ = nullptr;
|
||||
|
||||
// Per-frame state set during executePostProcessing
|
||||
VkCommandBuffer currentCmd_ = VK_NULL_HANDLE;
|
||||
Camera* camera_ = nullptr;
|
||||
float lastDeltaTime_ = 0.0f;
|
||||
|
||||
// Brightness
|
||||
float brightness_ = 1.0f;
|
||||
|
||||
// FSR 1.0 upscaling state
|
||||
struct FSRState {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
float scaleFactor = 1.00f; // Native default
|
||||
float sharpness = 1.6f;
|
||||
uint32_t internalWidth = 0;
|
||||
uint32_t internalHeight = 0;
|
||||
|
||||
// Off-screen scene target (reduced resolution)
|
||||
AllocatedImage sceneColor{}; // 1x color (non-MSAA render target / MSAA resolve target)
|
||||
AllocatedImage sceneDepth{}; // Depth (matches current MSAA sample count)
|
||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (only when MSAA > 1x)
|
||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (only when MSAA + depth resolve)
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||
|
||||
// Upscale pipeline
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||
};
|
||||
FSRState fsr_;
|
||||
bool initFSRResources();
|
||||
void destroyFSRResources();
|
||||
void renderFSRUpscale();
|
||||
|
||||
// FXAA post-process state
|
||||
struct FXAAState {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
|
||||
// Off-screen scene target (same resolution as swapchain — no scaling)
|
||||
AllocatedImage sceneColor{}; // 1x resolved color target
|
||||
AllocatedImage sceneDepth{}; // Depth (matches MSAA sample count)
|
||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (when MSAA > 1x)
|
||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (MSAA + depth resolve)
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||
|
||||
// FXAA fullscreen pipeline
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||
};
|
||||
FXAAState fxaa_;
|
||||
bool initFXAAResources();
|
||||
void destroyFXAAResources();
|
||||
void renderFXAAPass();
|
||||
|
||||
// FSR 2.2 temporal upscaling state
|
||||
struct FSR2State {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
float scaleFactor = 0.77f;
|
||||
float sharpness = 3.0f; // Very strong RCAS to counteract upscale softness
|
||||
uint32_t internalWidth = 0;
|
||||
uint32_t internalHeight = 0;
|
||||
|
||||
// Off-screen scene targets (internal resolution, no MSAA — FSR2 replaces AA)
|
||||
AllocatedImage sceneColor{};
|
||||
AllocatedImage sceneDepth{};
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
|
||||
// Samplers
|
||||
VkSampler linearSampler = VK_NULL_HANDLE; // For color
|
||||
VkSampler nearestSampler = VK_NULL_HANDLE; // For depth / motion vectors
|
||||
|
||||
// Motion vector buffer (internal resolution)
|
||||
AllocatedImage motionVectors{};
|
||||
|
||||
// History buffers (display resolution, ping-pong)
|
||||
AllocatedImage history[2]{};
|
||||
AllocatedImage framegenOutput{};
|
||||
bool framegenOutputValid = false;
|
||||
uint32_t currentHistory = 0; // Output index (0 or 1)
|
||||
|
||||
// Compute pipelines
|
||||
VkPipeline motionVecPipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout motionVecPipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout motionVecDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool motionVecDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet motionVecDescSet = VK_NULL_HANDLE;
|
||||
|
||||
VkPipeline accumulatePipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout accumulatePipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout accumulateDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool accumulateDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet accumulateDescSets[2] = {}; // Per ping-pong
|
||||
|
||||
// RCAS sharpening pass (display resolution)
|
||||
VkPipeline sharpenPipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout sharpenPipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout sharpenDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool sharpenDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sharpenDescSets[2] = {};
|
||||
|
||||
// Previous frame state for motion vector reprojection
|
||||
glm::mat4 prevViewProjection = glm::mat4(1.0f);
|
||||
glm::vec2 prevJitter = glm::vec2(0.0f);
|
||||
uint32_t frameIndex = 0;
|
||||
bool needsHistoryReset = true;
|
||||
bool useAmdBackend = false;
|
||||
bool amdFsr3FramegenEnabled = false;
|
||||
bool amdFsr3FramegenRuntimeActive = false;
|
||||
bool amdFsr3FramegenRuntimeReady = false;
|
||||
std::string amdFsr3RuntimePath = "Path C";
|
||||
std::string amdFsr3RuntimeLastError{};
|
||||
size_t amdFsr3UpscaleDispatchCount = 0;
|
||||
size_t amdFsr3FramegenDispatchCount = 0;
|
||||
size_t amdFsr3FallbackCount = 0;
|
||||
uint64_t amdFsr3InteropSyncValue = 1;
|
||||
float jitterSign = 0.38f;
|
||||
float motionVecScaleX = 1.0f;
|
||||
float motionVecScaleY = 1.0f;
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
FfxFsr2Context amdContext{};
|
||||
FfxFsr2Interface amdInterface{};
|
||||
void* amdScratchBuffer = nullptr;
|
||||
size_t amdScratchBufferSize = 0;
|
||||
#endif
|
||||
std::unique_ptr<AmdFsr3Runtime> amdFsr3Runtime;
|
||||
|
||||
// Convergent accumulation: jitter for N frames then freeze
|
||||
int convergenceFrame = 0;
|
||||
static constexpr int convergenceMaxFrames = 8;
|
||||
glm::mat4 lastStableVP = glm::mat4(1.0f);
|
||||
};
|
||||
FSR2State fsr2_;
|
||||
bool initFSR2Resources();
|
||||
void destroyFSR2Resources();
|
||||
void dispatchMotionVectors();
|
||||
void dispatchTemporalAccumulate();
|
||||
void dispatchAmdFsr2();
|
||||
void dispatchAmdFsr3Framegen();
|
||||
void renderFSR2Sharpen();
|
||||
static float halton(uint32_t index, uint32_t base);
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
@ -14,16 +14,12 @@
|
|||
#include "rendering/vk_frame_data.hpp"
|
||||
#include "rendering/vk_utils.hpp"
|
||||
#include "rendering/sky_system.hpp"
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
#include "ffx_fsr2.h"
|
||||
#include "ffx_fsr2_vk.h"
|
||||
#endif
|
||||
|
||||
namespace wowee {
|
||||
namespace core { class Window; }
|
||||
namespace rendering { class VkContext; }
|
||||
namespace game { class World; class ZoneManager; class GameHandler; }
|
||||
namespace audio { 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 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 {
|
||||
|
|
@ -54,6 +50,8 @@ class WorldMap;
|
|||
class QuestMarkerRenderer;
|
||||
class CharacterPreview;
|
||||
class AmdFsr3Runtime;
|
||||
class SpellVisualSystem;
|
||||
class PostProcessPipeline;
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
|
|
@ -157,9 +155,10 @@ public:
|
|||
bool captureScreenshot(const std::string& outputPath);
|
||||
|
||||
// Spell visual effects (SMSG_PLAY_SPELL_VISUAL / SMSG_PLAY_SPELL_IMPACT)
|
||||
// useImpactKit=false → CastKit path; useImpactKit=true → ImpactKit path
|
||||
// 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);
|
||||
|
|
@ -197,17 +196,21 @@ public:
|
|||
double getLastTerrainRenderMs() const { return lastTerrainRenderMs; }
|
||||
double getLastWMORenderMs() const { return lastWMORenderMs; }
|
||||
double getLastM2RenderMs() const { return lastM2RenderMs; }
|
||||
audio::MusicManager* getMusicManager() { return musicManager.get(); }
|
||||
// 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(); }
|
||||
audio::FootstepManager* getFootstepManager() { return footstepManager.get(); }
|
||||
audio::ActivitySoundManager* getActivitySoundManager() { return activitySoundManager.get(); }
|
||||
audio::MountSoundManager* getMountSoundManager() { return mountSoundManager.get(); }
|
||||
audio::NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager.get(); }
|
||||
audio::AmbientSoundManager* getAmbientSoundManager() { return ambientSoundManager.get(); }
|
||||
audio::UiSoundManager* getUiSoundManager() { return uiSoundManager.get(); }
|
||||
audio::CombatSoundManager* getCombatSoundManager() { return combatSoundManager.get(); }
|
||||
audio::SpellSoundManager* getSpellSoundManager() { return spellSoundManager.get(); }
|
||||
audio::MovementSoundManager* getMovementSoundManager() { return movementSoundManager.get(); }
|
||||
LightingManager* getLightingManager() { return lightingManager.get(); }
|
||||
|
||||
private:
|
||||
|
|
@ -239,16 +242,7 @@ private:
|
|||
std::unique_ptr<Minimap> minimap;
|
||||
std::unique_ptr<WorldMap> worldMap;
|
||||
std::unique_ptr<QuestMarkerRenderer> questMarkerRenderer;
|
||||
std::unique_ptr<audio::MusicManager> musicManager;
|
||||
std::unique_ptr<audio::FootstepManager> footstepManager;
|
||||
std::unique_ptr<audio::ActivitySoundManager> activitySoundManager;
|
||||
std::unique_ptr<audio::MountSoundManager> mountSoundManager;
|
||||
std::unique_ptr<audio::NpcVoiceManager> npcVoiceManager;
|
||||
std::unique_ptr<audio::AmbientSoundManager> ambientSoundManager;
|
||||
std::unique_ptr<audio::UiSoundManager> uiSoundManager;
|
||||
std::unique_ptr<audio::CombatSoundManager> combatSoundManager;
|
||||
std::unique_ptr<audio::SpellSoundManager> spellSoundManager;
|
||||
std::unique_ptr<audio::MovementSoundManager> movementSoundManager;
|
||||
audio::AudioCoordinator* audioCoordinator_ = nullptr; // Owned by Application
|
||||
std::unique_ptr<game::ZoneManager> zoneManager;
|
||||
// Shadow mapping (Vulkan)
|
||||
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
||||
|
|
@ -282,42 +276,35 @@ public:
|
|||
float getShadowDistance() const { return shadowDistance_; }
|
||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||
|
||||
// FXAA post-process anti-aliasing (combinable with MSAA)
|
||||
// Post-process pipeline API — delegates to PostProcessPipeline (§4.3)
|
||||
PostProcessPipeline* getPostProcessPipeline() const;
|
||||
void setFXAAEnabled(bool enabled);
|
||||
bool isFXAAEnabled() const { return fxaa_.enabled; }
|
||||
|
||||
// FSR (FidelityFX Super Resolution) upscaling
|
||||
bool isFXAAEnabled() const;
|
||||
void setFSREnabled(bool enabled);
|
||||
bool isFSREnabled() const { return fsr_.enabled; }
|
||||
void setFSRQuality(float scaleFactor); // 0.59=Balanced, 0.67=Quality, 0.77=UltraQuality, 1.00=Native
|
||||
void setFSRSharpness(float sharpness); // 0.0 - 2.0
|
||||
float getFSRScaleFactor() const { return fsr_.scaleFactor; }
|
||||
float getFSRSharpness() const { return fsr_.sharpness; }
|
||||
bool isFSREnabled() const;
|
||||
void setFSRQuality(float scaleFactor);
|
||||
void setFSRSharpness(float sharpness);
|
||||
float getFSRScaleFactor() const;
|
||||
float getFSRSharpness() const;
|
||||
void setFSR2Enabled(bool enabled);
|
||||
bool isFSR2Enabled() const { return fsr2_.enabled; }
|
||||
bool isFSR2Enabled() const;
|
||||
void setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY);
|
||||
void setAmdFsr3FramegenEnabled(bool enabled);
|
||||
bool isAmdFsr3FramegenEnabled() const { return fsr2_.amdFsr3FramegenEnabled; }
|
||||
float getFSR2JitterSign() const { return fsr2_.jitterSign; }
|
||||
float getFSR2MotionVecScaleX() const { return fsr2_.motionVecScaleX; }
|
||||
float getFSR2MotionVecScaleY() const { return fsr2_.motionVecScaleY; }
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
bool isAmdFsr2SdkAvailable() const { return true; }
|
||||
#else
|
||||
bool isAmdFsr2SdkAvailable() const { return false; }
|
||||
#endif
|
||||
#if WOWEE_HAS_AMD_FSR3_FRAMEGEN
|
||||
bool isAmdFsr3FramegenSdkAvailable() const { return true; }
|
||||
#else
|
||||
bool isAmdFsr3FramegenSdkAvailable() const { return false; }
|
||||
#endif
|
||||
bool isAmdFsr3FramegenRuntimeActive() const { return fsr2_.amdFsr3FramegenRuntimeActive; }
|
||||
bool isAmdFsr3FramegenRuntimeReady() const { return fsr2_.amdFsr3FramegenRuntimeReady; }
|
||||
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 { return fsr2_.amdFsr3RuntimeLastError; }
|
||||
size_t getAmdFsr3UpscaleDispatchCount() const { return fsr2_.amdFsr3UpscaleDispatchCount; }
|
||||
size_t getAmdFsr3FramegenDispatchCount() const { return fsr2_.amdFsr3FramegenDispatchCount; }
|
||||
size_t getAmdFsr3FallbackCount() const { return fsr2_.amdFsr3FallbackCount; }
|
||||
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;
|
||||
|
|
@ -331,23 +318,11 @@ private:
|
|||
|
||||
pipeline::AssetManager* cachedAssetManager = nullptr;
|
||||
|
||||
// Spell visual effects — transient M2 instances spawned by SMSG_PLAY_SPELL_VISUAL/IMPACT
|
||||
struct SpellVisualInstance {
|
||||
uint32_t instanceId;
|
||||
float elapsed;
|
||||
float duration; // per-instance lifetime in seconds (from M2 anim or default)
|
||||
};
|
||||
std::vector<SpellVisualInstance> activeSpellVisuals_;
|
||||
std::unordered_map<uint32_t, std::string> spellVisualCastPath_; // visualId → cast M2 path
|
||||
std::unordered_map<uint32_t, std::string> spellVisualImpactPath_; // visualId → impact M2 path
|
||||
std::unordered_map<std::string, uint32_t> spellVisualModelIds_; // M2 path → M2Renderer modelId
|
||||
std::unordered_set<uint32_t> spellVisualFailedModels_; // modelIds that failed to load (negative cache)
|
||||
uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799
|
||||
bool spellVisualDbcLoaded_ = false;
|
||||
void loadSpellVisualDbc();
|
||||
void updateSpellVisuals(float deltaTime);
|
||||
static constexpr float SPELL_VISUAL_MAX_DURATION = 5.0f;
|
||||
static constexpr float SPELL_VISUAL_DEFAULT_DURATION = 2.0f;
|
||||
// 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_;
|
||||
|
||||
uint32_t currentZoneId = 0;
|
||||
std::string currentZoneName;
|
||||
|
|
@ -408,155 +383,6 @@ private:
|
|||
void initOverlayPipeline();
|
||||
void renderOverlay(const glm::vec4& color, VkCommandBuffer overrideCmd = VK_NULL_HANDLE);
|
||||
|
||||
// Brightness (1.0 = default, <1 darkens, >1 brightens)
|
||||
float brightness_ = 1.0f;
|
||||
public:
|
||||
void setBrightness(float b) { brightness_ = b; }
|
||||
float getBrightness() const { return brightness_; }
|
||||
private:
|
||||
|
||||
// FSR 1.0 upscaling state
|
||||
struct FSRState {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
float scaleFactor = 1.00f; // Native default
|
||||
float sharpness = 1.6f;
|
||||
uint32_t internalWidth = 0;
|
||||
uint32_t internalHeight = 0;
|
||||
|
||||
// Off-screen scene target (reduced resolution)
|
||||
AllocatedImage sceneColor{}; // 1x color (non-MSAA render target / MSAA resolve target)
|
||||
AllocatedImage sceneDepth{}; // Depth (matches current MSAA sample count)
|
||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (only when MSAA > 1x)
|
||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (only when MSAA + depth resolve)
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||
|
||||
// Upscale pipeline
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||
};
|
||||
FSRState fsr_;
|
||||
bool initFSRResources();
|
||||
void destroyFSRResources();
|
||||
void renderFSRUpscale();
|
||||
|
||||
// FXAA post-process state
|
||||
struct FXAAState {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
|
||||
// Off-screen scene target (same resolution as swapchain — no scaling)
|
||||
AllocatedImage sceneColor{}; // 1x resolved color target
|
||||
AllocatedImage sceneDepth{}; // Depth (matches MSAA sample count)
|
||||
AllocatedImage sceneMsaaColor{}; // MSAA color target (when MSAA > 1x)
|
||||
AllocatedImage sceneDepthResolve{}; // Depth resolve (MSAA + depth resolve)
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
VkSampler sceneSampler = VK_NULL_HANDLE;
|
||||
|
||||
// FXAA fullscreen pipeline
|
||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet descSet = VK_NULL_HANDLE;
|
||||
};
|
||||
FXAAState fxaa_;
|
||||
bool initFXAAResources();
|
||||
void destroyFXAAResources();
|
||||
void renderFXAAPass();
|
||||
|
||||
// FSR 2.2 temporal upscaling state
|
||||
struct FSR2State {
|
||||
bool enabled = false;
|
||||
bool needsRecreate = false;
|
||||
float scaleFactor = 0.77f;
|
||||
float sharpness = 3.0f; // Very strong RCAS to counteract upscale softness
|
||||
uint32_t internalWidth = 0;
|
||||
uint32_t internalHeight = 0;
|
||||
|
||||
// Off-screen scene targets (internal resolution, no MSAA — FSR2 replaces AA)
|
||||
AllocatedImage sceneColor{};
|
||||
AllocatedImage sceneDepth{};
|
||||
VkFramebuffer sceneFramebuffer = VK_NULL_HANDLE;
|
||||
|
||||
// Samplers
|
||||
VkSampler linearSampler = VK_NULL_HANDLE; // For color
|
||||
VkSampler nearestSampler = VK_NULL_HANDLE; // For depth / motion vectors
|
||||
|
||||
// Motion vector buffer (internal resolution)
|
||||
AllocatedImage motionVectors{};
|
||||
|
||||
// History buffers (display resolution, ping-pong)
|
||||
AllocatedImage history[2]{};
|
||||
AllocatedImage framegenOutput{};
|
||||
bool framegenOutputValid = false;
|
||||
uint32_t currentHistory = 0; // Output index (0 or 1)
|
||||
|
||||
// Compute pipelines
|
||||
VkPipeline motionVecPipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout motionVecPipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout motionVecDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool motionVecDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet motionVecDescSet = VK_NULL_HANDLE;
|
||||
|
||||
VkPipeline accumulatePipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout accumulatePipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout accumulateDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool accumulateDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet accumulateDescSets[2] = {}; // Per ping-pong
|
||||
|
||||
// RCAS sharpening pass (display resolution)
|
||||
VkPipeline sharpenPipeline = VK_NULL_HANDLE;
|
||||
VkPipelineLayout sharpenPipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout sharpenDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool sharpenDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sharpenDescSets[2] = {};
|
||||
|
||||
// Previous frame state for motion vector reprojection
|
||||
glm::mat4 prevViewProjection = glm::mat4(1.0f);
|
||||
glm::vec2 prevJitter = glm::vec2(0.0f);
|
||||
uint32_t frameIndex = 0;
|
||||
bool needsHistoryReset = true;
|
||||
bool useAmdBackend = false;
|
||||
bool amdFsr3FramegenEnabled = false;
|
||||
bool amdFsr3FramegenRuntimeActive = false;
|
||||
bool amdFsr3FramegenRuntimeReady = false;
|
||||
std::string amdFsr3RuntimePath = "Path C";
|
||||
std::string amdFsr3RuntimeLastError{};
|
||||
size_t amdFsr3UpscaleDispatchCount = 0;
|
||||
size_t amdFsr3FramegenDispatchCount = 0;
|
||||
size_t amdFsr3FallbackCount = 0;
|
||||
uint64_t amdFsr3InteropSyncValue = 1;
|
||||
float jitterSign = 0.38f;
|
||||
float motionVecScaleX = 1.0f;
|
||||
float motionVecScaleY = 1.0f;
|
||||
#if WOWEE_HAS_AMD_FSR2
|
||||
FfxFsr2Context amdContext{};
|
||||
FfxFsr2Interface amdInterface{};
|
||||
void* amdScratchBuffer = nullptr;
|
||||
size_t amdScratchBufferSize = 0;
|
||||
#endif
|
||||
std::unique_ptr<AmdFsr3Runtime> amdFsr3Runtime;
|
||||
|
||||
// Convergent accumulation: jitter for N frames then freeze
|
||||
int convergenceFrame = 0;
|
||||
static constexpr int convergenceMaxFrames = 8;
|
||||
glm::mat4 lastStableVP = glm::mat4(1.0f);
|
||||
};
|
||||
FSR2State fsr2_;
|
||||
bool initFSR2Resources();
|
||||
void destroyFSR2Resources();
|
||||
void dispatchMotionVectors();
|
||||
void dispatchTemporalAccumulate();
|
||||
void dispatchAmdFsr2();
|
||||
void dispatchAmdFsr3Framegen();
|
||||
void renderFSR2Sharpen();
|
||||
static float halton(uint32_t index, uint32_t base);
|
||||
|
||||
// Footstep event tracking (animation-driven)
|
||||
uint32_t footstepLastAnimationId = 0;
|
||||
float footstepLastNormTime = 0.0f;
|
||||
|
|
|
|||
62
include/rendering/spell_visual_system.hpp
Normal file
62
include/rendering/spell_visual_system.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline { class AssetManager; }
|
||||
namespace rendering {
|
||||
|
||||
class M2Renderer;
|
||||
|
||||
class SpellVisualSystem {
|
||||
public:
|
||||
SpellVisualSystem() = default;
|
||||
~SpellVisualSystem() = default;
|
||||
|
||||
// Initialize with references to the M2 renderer (for model loading/instance spawning)
|
||||
void initialize(M2Renderer* m2Renderer);
|
||||
void shutdown();
|
||||
|
||||
// Spawn a spell visual at a world position.
|
||||
// useImpactKit=false → CastKit path; useImpactKit=true → ImpactKit path
|
||||
void playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
||||
bool useImpactKit = false);
|
||||
|
||||
// Advance lifetime timers and remove expired instances.
|
||||
void update(float deltaTime);
|
||||
|
||||
// Remove all active spell visual instances and reset caches.
|
||||
// Called on map change / combat reset.
|
||||
void reset();
|
||||
|
||||
private:
|
||||
// Spell visual effects — transient M2 instances spawned by SMSG_PLAY_SPELL_VISUAL/IMPACT
|
||||
struct SpellVisualInstance {
|
||||
uint32_t instanceId;
|
||||
float elapsed;
|
||||
float duration; // per-instance lifetime in seconds (from M2 anim or default)
|
||||
};
|
||||
|
||||
void loadSpellVisualDbc();
|
||||
|
||||
M2Renderer* m2Renderer_ = nullptr;
|
||||
pipeline::AssetManager* cachedAssetManager_ = nullptr;
|
||||
|
||||
std::vector<SpellVisualInstance> activeSpellVisuals_;
|
||||
std::unordered_map<uint32_t, std::string> spellVisualCastPath_; // visualId → cast M2 path
|
||||
std::unordered_map<uint32_t, std::string> spellVisualImpactPath_; // visualId → impact M2 path
|
||||
std::unordered_map<std::string, uint32_t> spellVisualModelIds_; // M2 path → M2Renderer modelId
|
||||
std::unordered_set<uint32_t> spellVisualFailedModels_; // modelIds that failed to load (negative cache)
|
||||
uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799
|
||||
bool spellVisualDbcLoaded_ = false;
|
||||
static constexpr float SPELL_VISUAL_MAX_DURATION = 5.0f;
|
||||
static constexpr float SPELL_VISUAL_DEFAULT_DURATION = 2.0f;
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
@ -50,13 +50,16 @@ bool AudioCoordinator::initialize() {
|
|||
void AudioCoordinator::initializeWithAssets(pipeline::AssetManager* assetManager) {
|
||||
if (!audioAvailable_ || !assetManager) return;
|
||||
|
||||
// MusicManager needs asset manager for zone music lookups
|
||||
if (musicManager_) {
|
||||
musicManager_->initialize(assetManager);
|
||||
}
|
||||
|
||||
// Other managers may need asset manager for sound bank loading
|
||||
// (Add similar calls as needed for other managers)
|
||||
if (musicManager_) musicManager_->initialize(assetManager);
|
||||
if (footstepManager_) footstepManager_->initialize(assetManager);
|
||||
if (activitySoundManager_) activitySoundManager_->initialize(assetManager);
|
||||
if (mountSoundManager_) mountSoundManager_->initialize(assetManager);
|
||||
if (npcVoiceManager_) npcVoiceManager_->initialize(assetManager);
|
||||
if (ambientSoundManager_) ambientSoundManager_->initialize(assetManager);
|
||||
if (uiSoundManager_) uiSoundManager_->initialize(assetManager);
|
||||
if (combatSoundManager_) combatSoundManager_->initialize(assetManager);
|
||||
if (spellSoundManager_) spellSoundManager_->initialize(assetManager);
|
||||
if (movementSoundManager_) movementSoundManager_->initialize(assetManager);
|
||||
|
||||
LOG_INFO("AudioCoordinator initialized with asset manager");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ bool Application::initialize() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Create and initialize audio coordinator (owns all audio managers)
|
||||
audioCoordinator_ = std::make_unique<audio::AudioCoordinator>();
|
||||
audioCoordinator_->initialize();
|
||||
renderer->setAudioCoordinator(audioCoordinator_.get());
|
||||
|
||||
// Create UI manager
|
||||
uiManager = std::make_unique<ui::UIManager>();
|
||||
if (!uiManager->initialize(window.get())) {
|
||||
|
|
@ -845,6 +850,12 @@ void Application::shutdown() {
|
|||
LOG_WARNING("Renderer shutdown complete, resetting...");
|
||||
renderer.reset();
|
||||
|
||||
// Shutdown audio coordinator after renderer (renderer may reference audio during shutdown)
|
||||
if (audioCoordinator_) {
|
||||
audioCoordinator_->shutdown();
|
||||
}
|
||||
audioCoordinator_.reset();
|
||||
|
||||
LOG_WARNING("Resetting world...");
|
||||
world.reset();
|
||||
LOG_WARNING("Resetting gameHandler...");
|
||||
|
|
|
|||
1879
src/rendering/post_process_pipeline.cpp
Normal file
1879
src/rendering/post_process_pipeline.cpp
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
231
src/rendering/spell_visual_system.cpp
Normal file
231
src/rendering/spell_visual_system.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "rendering/spell_visual_system.hpp"
|
||||
#include "rendering/m2_renderer.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "pipeline/dbc_layout.hpp"
|
||||
#include "pipeline/m2_loader.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
void SpellVisualSystem::initialize(M2Renderer* m2Renderer) {
|
||||
m2Renderer_ = m2Renderer;
|
||||
}
|
||||
|
||||
void SpellVisualSystem::shutdown() {
|
||||
reset();
|
||||
m2Renderer_ = nullptr;
|
||||
cachedAssetManager_ = nullptr;
|
||||
}
|
||||
|
||||
// Load SpellVisual DBC chain: SpellVisualEffectName → SpellVisualKit → SpellVisual
|
||||
// to build cast/impact M2 path lookup maps.
|
||||
void SpellVisualSystem::loadSpellVisualDbc() {
|
||||
if (spellVisualDbcLoaded_) return;
|
||||
spellVisualDbcLoaded_ = true; // Set early to prevent re-entry on failure
|
||||
|
||||
if (!cachedAssetManager_) {
|
||||
cachedAssetManager_ = core::Application::getInstance().getAssetManager();
|
||||
}
|
||||
if (!cachedAssetManager_) return;
|
||||
|
||||
auto* layout = pipeline::getActiveDBCLayout();
|
||||
const pipeline::DBCFieldMap* svLayout = layout ? layout->getLayout("SpellVisual") : nullptr;
|
||||
const pipeline::DBCFieldMap* kitLayout = layout ? layout->getLayout("SpellVisualKit") : nullptr;
|
||||
const pipeline::DBCFieldMap* fxLayout = layout ? layout->getLayout("SpellVisualEffectName") : nullptr;
|
||||
|
||||
uint32_t svCastKitField = svLayout ? (*svLayout)["CastKit"] : 2;
|
||||
uint32_t svImpactKitField = svLayout ? (*svLayout)["ImpactKit"] : 3;
|
||||
uint32_t svMissileField = svLayout ? (*svLayout)["MissileModel"] : 8;
|
||||
uint32_t kitSpecial0Field = kitLayout ? (*kitLayout)["SpecialEffect0"] : 11;
|
||||
uint32_t kitBaseField = kitLayout ? (*kitLayout)["BaseEffect"] : 5;
|
||||
uint32_t fxFilePathField = fxLayout ? (*fxLayout)["FilePath"] : 2;
|
||||
|
||||
// Helper to look up effectName path from a kit ID
|
||||
// Load SpellVisualEffectName.dbc — ID → M2 path
|
||||
auto fxDbc = cachedAssetManager_->loadDBC("SpellVisualEffectName.dbc");
|
||||
if (!fxDbc || !fxDbc->isLoaded() || fxDbc->getFieldCount() <= fxFilePathField) {
|
||||
LOG_DEBUG("SpellVisual: SpellVisualEffectName.dbc unavailable (fc=",
|
||||
fxDbc ? fxDbc->getFieldCount() : 0, ")");
|
||||
return;
|
||||
}
|
||||
std::unordered_map<uint32_t, std::string> effectPaths; // effectNameId → path
|
||||
for (uint32_t i = 0; i < fxDbc->getRecordCount(); ++i) {
|
||||
uint32_t id = fxDbc->getUInt32(i, 0);
|
||||
std::string p = fxDbc->getString(i, fxFilePathField);
|
||||
if (id && !p.empty()) effectPaths[id] = p;
|
||||
}
|
||||
|
||||
// Load SpellVisualKit.dbc — kitId → best SpellVisualEffectName ID
|
||||
auto kitDbc = cachedAssetManager_->loadDBC("SpellVisualKit.dbc");
|
||||
std::unordered_map<uint32_t, uint32_t> kitToEffectName; // kitId → effectNameId
|
||||
if (kitDbc && kitDbc->isLoaded()) {
|
||||
uint32_t fc = kitDbc->getFieldCount();
|
||||
for (uint32_t i = 0; i < kitDbc->getRecordCount(); ++i) {
|
||||
uint32_t kitId = kitDbc->getUInt32(i, 0);
|
||||
if (!kitId) continue;
|
||||
// Prefer SpecialEffect0, fall back to BaseEffect
|
||||
uint32_t eff = 0;
|
||||
if (kitSpecial0Field < fc) eff = kitDbc->getUInt32(i, kitSpecial0Field);
|
||||
if (!eff && kitBaseField < fc) eff = kitDbc->getUInt32(i, kitBaseField);
|
||||
if (eff) kitToEffectName[kitId] = eff;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: resolve path for a given kit ID
|
||||
auto kitPath = [&](uint32_t kitId) -> std::string {
|
||||
if (!kitId) return {};
|
||||
auto kitIt = kitToEffectName.find(kitId);
|
||||
if (kitIt == kitToEffectName.end()) return {};
|
||||
auto fxIt = effectPaths.find(kitIt->second);
|
||||
return (fxIt != effectPaths.end()) ? fxIt->second : std::string{};
|
||||
};
|
||||
auto missilePath = [&](uint32_t effId) -> std::string {
|
||||
if (!effId) return {};
|
||||
auto fxIt = effectPaths.find(effId);
|
||||
return (fxIt != effectPaths.end()) ? fxIt->second : std::string{};
|
||||
};
|
||||
|
||||
// Load SpellVisual.dbc — visualId → cast/impact M2 paths via kit chain
|
||||
auto svDbc = cachedAssetManager_->loadDBC("SpellVisual.dbc");
|
||||
if (!svDbc || !svDbc->isLoaded()) {
|
||||
LOG_DEBUG("SpellVisual: SpellVisual.dbc unavailable");
|
||||
return;
|
||||
}
|
||||
uint32_t svFc = svDbc->getFieldCount();
|
||||
uint32_t loadedCast = 0, loadedImpact = 0;
|
||||
for (uint32_t i = 0; i < svDbc->getRecordCount(); ++i) {
|
||||
uint32_t vid = svDbc->getUInt32(i, 0);
|
||||
if (!vid) continue;
|
||||
|
||||
// Cast path: CastKit → SpecialEffect0/BaseEffect, fallback to MissileModel
|
||||
{
|
||||
std::string path;
|
||||
if (svCastKitField < svFc)
|
||||
path = kitPath(svDbc->getUInt32(i, svCastKitField));
|
||||
if (path.empty() && svMissileField < svFc)
|
||||
path = missilePath(svDbc->getUInt32(i, svMissileField));
|
||||
if (!path.empty()) { spellVisualCastPath_[vid] = path; ++loadedCast; }
|
||||
}
|
||||
// Impact path: ImpactKit → SpecialEffect0/BaseEffect, fallback to MissileModel
|
||||
{
|
||||
std::string path;
|
||||
if (svImpactKitField < svFc)
|
||||
path = kitPath(svDbc->getUInt32(i, svImpactKitField));
|
||||
if (path.empty() && svMissileField < svFc)
|
||||
path = missilePath(svDbc->getUInt32(i, svMissileField));
|
||||
if (!path.empty()) { spellVisualImpactPath_[vid] = path; ++loadedImpact; }
|
||||
}
|
||||
}
|
||||
LOG_INFO("SpellVisual: loaded cast=", loadedCast, " impact=", loadedImpact,
|
||||
" visual→M2 mappings (of ", svDbc->getRecordCount(), " records)");
|
||||
}
|
||||
|
||||
void SpellVisualSystem::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
||||
bool useImpactKit) {
|
||||
if (!m2Renderer_ || visualId == 0) return;
|
||||
|
||||
if (!cachedAssetManager_)
|
||||
cachedAssetManager_ = core::Application::getInstance().getAssetManager();
|
||||
if (!cachedAssetManager_) return;
|
||||
|
||||
if (!spellVisualDbcLoaded_) loadSpellVisualDbc();
|
||||
|
||||
// Select cast or impact path map
|
||||
auto& pathMap = useImpactKit ? spellVisualImpactPath_ : spellVisualCastPath_;
|
||||
auto pathIt = pathMap.find(visualId);
|
||||
if (pathIt == pathMap.end()) return; // No model for this visual
|
||||
|
||||
const std::string& modelPath = pathIt->second;
|
||||
|
||||
// Get or assign a model ID for this path
|
||||
auto midIt = spellVisualModelIds_.find(modelPath);
|
||||
uint32_t modelId = 0;
|
||||
if (midIt != spellVisualModelIds_.end()) {
|
||||
modelId = midIt->second;
|
||||
} else {
|
||||
if (nextSpellVisualModelId_ >= 999800) {
|
||||
LOG_WARNING("SpellVisual: model ID pool exhausted");
|
||||
return;
|
||||
}
|
||||
modelId = nextSpellVisualModelId_++;
|
||||
spellVisualModelIds_[modelPath] = modelId;
|
||||
}
|
||||
|
||||
// Skip models that have previously failed to load (avoid repeated I/O)
|
||||
if (spellVisualFailedModels_.count(modelId)) return;
|
||||
|
||||
// Load the M2 model if not already loaded
|
||||
if (!m2Renderer_->hasModel(modelId)) {
|
||||
auto m2Data = cachedAssetManager_->readFile(modelPath);
|
||||
if (m2Data.empty()) {
|
||||
LOG_DEBUG("SpellVisual: could not read model: ", modelPath);
|
||||
spellVisualFailedModels_.insert(modelId);
|
||||
return;
|
||||
}
|
||||
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
||||
if (model.vertices.empty() && model.particleEmitters.empty()) {
|
||||
LOG_DEBUG("SpellVisual: empty model: ", modelPath);
|
||||
spellVisualFailedModels_.insert(modelId);
|
||||
return;
|
||||
}
|
||||
// Load skin file for WotLK-format M2s
|
||||
if (model.version >= 264) {
|
||||
std::string skinPath = modelPath.substr(0, modelPath.rfind('.')) + "00.skin";
|
||||
auto skinData = cachedAssetManager_->readFile(skinPath);
|
||||
if (!skinData.empty()) pipeline::M2Loader::loadSkin(skinData, model);
|
||||
}
|
||||
if (!m2Renderer_->loadModel(model, modelId)) {
|
||||
LOG_WARNING("SpellVisual: failed to load model to GPU: ", modelPath);
|
||||
spellVisualFailedModels_.insert(modelId);
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("SpellVisual: loaded model id=", modelId, " path=", modelPath);
|
||||
}
|
||||
|
||||
// Spawn instance at world position
|
||||
uint32_t instanceId = m2Renderer_->createInstance(modelId, worldPosition,
|
||||
glm::vec3(0.0f), 1.0f);
|
||||
if (instanceId == 0) {
|
||||
LOG_WARNING("SpellVisual: failed to create instance for visualId=", visualId);
|
||||
return;
|
||||
}
|
||||
// Determine lifetime from M2 animation duration (clamp to reasonable range)
|
||||
float animDurMs = m2Renderer_->getInstanceAnimDuration(instanceId);
|
||||
float duration = (animDurMs > 100.0f)
|
||||
? std::clamp(animDurMs / 1000.0f, 0.5f, SPELL_VISUAL_MAX_DURATION)
|
||||
: SPELL_VISUAL_DEFAULT_DURATION;
|
||||
activeSpellVisuals_.push_back({instanceId, 0.0f, duration});
|
||||
LOG_DEBUG("SpellVisual: spawned visualId=", visualId, " instanceId=", instanceId,
|
||||
" duration=", duration, "s model=", modelPath);
|
||||
}
|
||||
|
||||
void SpellVisualSystem::update(float deltaTime) {
|
||||
if (activeSpellVisuals_.empty() || !m2Renderer_) return;
|
||||
for (auto it = activeSpellVisuals_.begin(); it != activeSpellVisuals_.end(); ) {
|
||||
it->elapsed += deltaTime;
|
||||
if (it->elapsed >= it->duration) {
|
||||
m2Renderer_->removeInstance(it->instanceId);
|
||||
it = activeSpellVisuals_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpellVisualSystem::reset() {
|
||||
// Clear lingering spell visual instances from the previous map/combat session.
|
||||
// Without this, old effects could remain visible after teleport or map change.
|
||||
for (auto& sv : activeSpellVisuals_) {
|
||||
if (m2Renderer_) m2Renderer_->removeInstance(sv.instanceId);
|
||||
}
|
||||
activeSpellVisuals_.clear();
|
||||
// Reset the negative cache so models that failed during asset loading can retry.
|
||||
spellVisualFailedModels_.clear();
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue