Files
WoWee/include/rendering/renderer.hpp
Kelsi 2229742e25 refactor(render): draw the UI in its own single-sampled overlay pass
The UI now always renders in a colour-only, single-sampled pass opened
after the scene pass closes and after water refraction takes its copy.
Three things fall out of that:

The MSAA fallback is gone. Refraction previously captured after the UI
whenever MSAA was on, because a second multisampled pass would have had
to resolve again and could not preserve the resolved image. A separate
single-sampled UI pass sidesteps that entirely, so the capture is
UI-free at any sample count.

The UI is no longer multisampled. ImGui draws axis-aligned rectangles
and pre-antialiased glyphs, which MSAA does almost nothing for, so it
was paying the scene's sample count for no visible gain.

Both render passes are now built in one place and recreated together.
The previous overlay pass was created in createImGuiResources but not in
recreateSwapchain, so a single window resize silently disabled it.

ImGui initialises against the overlay pass at one sample; its pipelines
stay valid across swapchain recreation because the replacement pass is
render-pass-compatible. Drops endFrameInlineMode_, which tracked which
pass the UI should record into and no longer has a reader.
2026-07-31 07:04:19 -07:00

398 lines
16 KiB
C++

#pragma once
#include <memory>
#include <string>
#include <cstdint>
#include <vector>
#include <future>
#include <cstddef>
#include <unordered_map>
#include <unordered_set>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include "rendering/vk_frame_data.hpp"
#include "rendering/vk_utils.hpp"
#include "rendering/sky_system.hpp"
#include "pipeline/custom_zone_discovery.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;
namespace world_map { class WorldMapFacade; }
using WorldMap = world_map::WorldMapFacade;
class QuestMarkerRenderer;
class FootprintRenderer;
class CharacterPreview;
class AmdFsr3Runtime;
class SpellVisualSystem;
class PostProcessPipeline;
class AnimationController;
class LevelUpEffect;
class ChargeEffect;
class SwimEffects;
class RenderGraph;
class OverlaySystem;
class HiZSystem;
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(); }
FootprintRenderer* getFootprintRenderer() const { return footprintRenderer.get(); }
SkySystem* getSkySystem() const { return skySystem.get(); }
const std::string& getCurrentZoneName() const;
uint32_t getCurrentZoneId() const;
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; }
// 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)
SpellVisualSystem* getSpellVisualSystem() const { return spellVisualSystem_.get(); }
// Combat visual state (compound: resets AnimationController + SpellVisualSystem)
void resetCombatVisualState();
// Sub-system accessors (§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(); }
const std::vector<pipeline::CustomZoneInfo>& getCustomZones() const { return customZones_; }
private:
void runDeferredWorldInitStep(float deltaTime);
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;
std::unique_ptr<Lightning> lightning;
std::unique_ptr<LightingManager> lightingManager;
std::unique_ptr<SkySystem> skySystem; // Coordinator for sky rendering
std::unique_ptr<SwimEffects> swimEffects;
std::unique_ptr<MountDust> mountDust;
std::unique_ptr<LevelUpEffect> levelUpEffect;
std::unique_ptr<ChargeEffect> chargeEffect;
std::unique_ptr<CharacterRenderer> characterRenderer;
std::unique_ptr<WMORenderer> wmoRenderer;
std::unique_ptr<M2Renderer> m2Renderer;
std::unique_ptr<M2Renderer> outlandSkyRenderer_;
std::string outlandSkyPath_;
uint32_t outlandSkyInstanceId_ = 0;
std::unique_ptr<Minimap> minimap;
std::unique_ptr<WorldMap> worldMap;
std::unique_ptr<QuestMarkerRenderer> questMarkerRenderer;
std::unique_ptr<FootprintRenderer> footprintRenderer;
audio::AudioCoordinator* audioCoordinator_ = nullptr; // Owned by Application
std::unique_ptr<AnimationController> animationController_; // §4.2
std::unique_ptr<game::ZoneManager> 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)
float viewDistance_ = 1200.0f;
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 setViewDistance(float distance);
float getViewDistance() const { return viewDistance_; }
int getTerrainLoadRadius() const;
int getTerrainUnloadRadius() const { return getTerrainLoadRadius() + 3; }
void setMsaaSamples(VkSampleCountFlagBits samples);
// Post-process pipeline API — delegates to PostProcessPipeline (§4.3)
PostProcessPipeline* getPostProcessPipeline() const;
void setFSREnabled(bool enabled);
void setFSR2Enabled(bool enabled);
void setWaterRefractionEnabled(bool enabled);
bool isWaterRefractionEnabled() const;
private:
void applyMsaaChange();
bool ensureOutlandSkybox();
VkSampleCountFlagBits pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
bool msaaChangePending_ = false;
void renderShadowPass();
glm::mat4 computeLightSpaceMatrix();
std::vector<pipeline::CustomZoneInfo> customZones_;
pipeline::AssetManager* cachedAssetManager = nullptr;
// 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_;
bool playerIndoors_ = false; // Cached WMO inside state for macro conditionals
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 + overlay rendering (owned by OverlaySystem)
std::unique_ptr<OverlaySystem> overlaySystem_;
// 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[MAX_FRAMES] = {};
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_SELECTION = 3; // selection circle (main thread)
static constexpr uint32_t SEC_CHARS = 4; // characters (worker 2)
static constexpr uint32_t SEC_M2 = 5; // M2 + particles + glow (worker 3)
static constexpr uint32_t SEC_POST = 6; // water + weather + effects (worker 4)
static constexpr uint32_t SEC_IMGUI = 7; // ImGui (main thread, non-FSR only)
static constexpr uint32_t NUM_SECONDARIES = 8;
static constexpr uint32_t NUM_WORKERS = 5;
// 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
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<CharacterPreview*> activePreviews_;
bool terrainEnabled = true;
bool terrainLoaded = false;
bool ghostMode_ = false; // set each frame from gameHandler->isPlayerGhost()
// Render Graph — declarative pass ordering with automatic barriers
std::unique_ptr<RenderGraph> renderGraph_;
void buildFrameGraph(game::GameHandler* gameHandler);
// HiZ occlusion culling — builds depth pyramid each frame
std::unique_ptr<HiZSystem> hizSystem_;
// 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