Vulcan Nightmare

Experimentally bringing up vulcan support
This commit is contained in:
Kelsi 2026-02-21 19:41:21 -08:00
parent 863a786c48
commit 83b576e8d9
189 changed files with 12147 additions and 7820 deletions

View file

@ -1,142 +1,132 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
class VkContext;
/**
* Celestial body renderer
* Celestial body renderer (Vulkan)
*
* Renders sun and moon that move across the sky based on time of day.
* Sun rises at dawn, sets at dusk. Moon is visible at night.
*
* Pipeline layout:
* set 0 = perFrameLayout (camera UBO view, projection, etc.)
* push = CelestialPush (mat4 model + vec4 celestialColor + float intensity
* + float moonPhase + float animTime = 96 bytes)
*/
class Celestial {
public:
Celestial();
~Celestial();
bool initialize();
/**
* Initialize the renderer.
* @param ctx Vulkan context
* @param perFrameLayout Descriptor set layout for set 0 (camera UBO)
*/
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/**
* Render celestial bodies (sun and moon)
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
* @param sunDir Optional sun direction from lighting system (normalized)
* @param sunColor Optional sun color from lighting system
* @param gameTime Optional server game time in seconds (for deterministic moon phases)
* Render celestial bodies (sun and moons).
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, camera UBO)
* @param timeOfDay Time of day in hours (0-24)
* @param sunDir Optional sun direction from lighting system (normalized)
* @param sunColor Optional sun colour from lighting system
* @param gameTime Optional server game time in seconds (deterministic moon phases)
*/
void render(const Camera& camera, float timeOfDay,
const glm::vec3* sunDir = nullptr,
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
float timeOfDay,
const glm::vec3* sunDir = nullptr,
const glm::vec3* sunColor = nullptr,
float gameTime = -1.0f);
/**
* Enable/disable celestial rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Update celestial bodies (for moon phase cycling)
* Update celestial bodies (moon phase cycling, haze timer).
*/
void update(float deltaTime);
/**
* Set White Lady phase (primary moon, 0.0 = new, 0.5 = full, 1.0 = new)
*/
// --- Enable / disable ---
void setEnabled(bool enabled) { renderingEnabled_ = enabled; }
bool isEnabled() const { return renderingEnabled_; }
// --- Moon phases ---
/** Set White Lady phase (primary moon, 0 = new, 0.5 = full, 1 = new). */
void setMoonPhase(float phase);
float getMoonPhase() const { return whiteLadyPhase_; }
/**
* Set Blue Child phase (secondary moon, 0.0 = new, 0.5 = full, 1.0 = new)
*/
/** Set Blue Child phase (secondary moon, 0 = new, 0.5 = full, 1 = new). */
void setBlueChildPhase(float phase);
float getBlueChildPhase() const { return blueChildPhase_; }
/**
* Enable/disable automatic moon phase cycling
*/
void setMoonPhaseCycling(bool enabled) { moonPhaseCycling = enabled; }
bool isMoonPhaseCycling() const { return moonPhaseCycling; }
void setMoonPhaseCycling(bool enabled) { moonPhaseCycling_ = enabled; }
bool isMoonPhaseCycling() const { return moonPhaseCycling_; }
/**
* Enable/disable two-moon rendering (White Lady + Blue Child)
*/
/** Enable / disable two-moon rendering (White Lady + Blue Child). */
void setDualMoonMode(bool enabled) { dualMoonMode_ = enabled; }
bool isDualMoonMode() const { return dualMoonMode_; }
/**
* Get sun position in world space
*/
// --- Positional / colour queries (unchanged from GL version) ---
glm::vec3 getSunPosition(float timeOfDay) const;
/**
* Get moon position in world space
*/
glm::vec3 getMoonPosition(float timeOfDay) const;
/**
* Get sun color (changes with time of day)
*/
glm::vec3 getSunColor(float timeOfDay) const;
/**
* Get sun intensity (0-1, fades at dawn/dusk)
*/
float getSunIntensity(float timeOfDay) const;
float getSunIntensity(float timeOfDay) const;
private:
void createCelestialQuad();
void destroyCelestialQuad();
// Push constant block — MUST match celestial.vert.glsl / celestial.frag.glsl
struct CelestialPush {
glm::mat4 model; // 64 bytes
glm::vec4 celestialColor; // 16 bytes (xyz = colour, w unused)
float intensity; // 4 bytes
float moonPhase; // 4 bytes
float animTime; // 4 bytes
float _pad; // 4 bytes (round to 16-byte boundary = 96 bytes total)
};
static_assert(sizeof(CelestialPush) == 96, "CelestialPush size mismatch");
void renderSun(const Camera& camera, float timeOfDay,
const glm::vec3* sunDir = nullptr,
const glm::vec3* sunColor = nullptr);
void renderMoon(const Camera& camera, float timeOfDay); // White Lady (primary)
void renderBlueChild(const Camera& camera, float timeOfDay); // Blue Child (secondary)
void createQuad();
void destroyQuad();
void renderSun(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
float timeOfDay,
const glm::vec3* sunDir, const glm::vec3* sunColor);
void renderMoon(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay);
void renderBlueChild(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay);
float calculateCelestialAngle(float timeOfDay, float riseTime, float setTime) const;
/**
* Compute moon phase from game time (deterministic)
* @param gameTime Server game time in seconds
* @param cycleDays Lunar cycle length in game days
* @return Phase 0.0-1.0 (0=new, 0.5=full, 1.0=new)
*/
float computePhaseFromGameTime(float gameTime, float cycleDays) const;
void updatePhasesFromGameTime(float gameTime);
/**
* Update moon phases from game time (server-driven)
*/
void updatePhasesFromGameTime(float gameTime);
// Vulkan objects
VkContext* vkCtx_ = nullptr;
VkPipeline pipeline_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkBuffer vertexBuffer_ = VK_NULL_HANDLE;
VmaAllocation vertexAlloc_ = VK_NULL_HANDLE;
VkBuffer indexBuffer_ = VK_NULL_HANDLE;
VmaAllocation indexAlloc_ = VK_NULL_HANDLE;
std::unique_ptr<Shader> celestialShader;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
bool renderingEnabled = true;
bool renderingEnabled_ = true;
// Moon phase system (two moons in Azeroth lore)
float whiteLadyPhase_ = 0.5f; // 0.0-1.0 (0=new, 0.5=full) - primary moon
float blueChildPhase_ = 0.25f; // 0.0-1.0 (0=new, 0.5=full) - secondary moon
bool moonPhaseCycling = true;
float moonPhaseTimer = 0.0f; // Fallback for deltaTime mode (development)
float sunHazeTimer_ = 0.0f; // Always-running timer for sun haze animation
bool dualMoonMode_ = true; // Default: render both moons (Azeroth-specific)
float whiteLadyPhase_ = 0.5f; // 0-1, 0=new, 0.5=full
float blueChildPhase_ = 0.25f; // 0-1
bool moonPhaseCycling_ = true;
float moonPhaseTimer_ = 0.0f; // Fallback deltaTime mode
float sunHazeTimer_ = 0.0f; // Always-running haze animation timer
bool dualMoonMode_ = true;
// WoW lunar cycle constants (in game days)
// WoW day = 24 real minutes, so these are ~realistic game-world cycles
static constexpr float WHITE_LADY_CYCLE_DAYS = 30.0f; // ~12 real hours for full cycle
static constexpr float BLUE_CHILD_CYCLE_DAYS = 27.0f; // ~10.8 real hours (slightly faster)
static constexpr float MOON_CYCLE_DURATION = 240.0f; // Fallback: 4 minutes (deltaTime mode)
// WoW lunar cycle constants (game days; 1 game day = 24 real minutes)
static constexpr float WHITE_LADY_CYCLE_DAYS = 30.0f;
static constexpr float BLUE_CHILD_CYCLE_DAYS = 27.0f;
static constexpr float MOON_CYCLE_DURATION = 240.0f; // Fallback: 4 minutes
};
} // namespace rendering

View file

@ -1,7 +1,7 @@
#pragma once
#include "game/character.hpp"
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <memory>
#include <cstdint>
#include <string>
@ -13,6 +13,8 @@ namespace rendering {
class CharacterRenderer;
class Camera;
class VkContext;
class VkTexture;
class CharacterPreview {
public:
@ -34,7 +36,8 @@ public:
void render();
void rotate(float yawDelta);
GLuint getTextureId() const { return colorTexture_; }
// TODO: Vulkan offscreen render target for preview
VkTexture* getTextureId() const { return nullptr; }
int getWidth() const { return fboWidth_; }
int getHeight() const { return fboHeight_; }
@ -51,9 +54,8 @@ private:
std::unique_ptr<CharacterRenderer> charRenderer_;
std::unique_ptr<Camera> camera_;
GLuint fbo_ = 0;
GLuint colorTexture_ = 0;
GLuint depthRenderbuffer_ = 0;
// TODO: Vulkan offscreen render target
// VkRenderTarget* renderTarget_ = nullptr;
static constexpr int fboWidth_ = 400;
static constexpr int fboHeight_ = 500;

View file

@ -1,7 +1,8 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
@ -14,9 +15,9 @@ namespace pipeline { class AssetManager; }
namespace rendering {
// Forward declarations
class Shader;
class Texture;
class Camera;
class VkContext;
class VkTexture;
// Weapon attached to a character instance at a bone attachment point
struct WeaponAttachment {
@ -33,7 +34,7 @@ struct WeaponAttachment {
* Features:
* - Skeletal animation with bone transformations
* - Keyframe interpolation (linear position/scale, slerp rotation)
* - Vertex skinning (GPU-accelerated)
* - Vertex skinning (GPU-accelerated via bone SSBO)
* - Texture loading from BLP via AssetManager
*/
class CharacterRenderer {
@ -41,7 +42,7 @@ public:
CharacterRenderer();
~CharacterRenderer();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, pipeline::AssetManager* am);
void shutdown();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
@ -56,8 +57,8 @@ public:
void update(float deltaTime, const glm::vec3& cameraPos = glm::vec3(0.0f));
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void renderShadow(const glm::mat4& lightSpaceMatrix);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
void renderShadow(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
void setInstanceRotation(uint32_t instanceId, const glm::vec3& rotation);
@ -65,8 +66,8 @@ public:
void startFadeIn(uint32_t instanceId, float durationSeconds);
const pipeline::M2Model* getModelData(uint32_t modelId) const;
void setActiveGeosets(uint32_t instanceId, const std::unordered_set<uint16_t>& geosets);
void setGroupTextureOverride(uint32_t instanceId, uint16_t geosetGroup, GLuint textureId);
void setTextureSlotOverride(uint32_t instanceId, uint16_t textureSlot, GLuint textureId);
void setGroupTextureOverride(uint32_t instanceId, uint16_t geosetGroup, VkTexture* texture);
void setTextureSlotOverride(uint32_t instanceId, uint16_t textureSlot, VkTexture* texture);
void clearTextureSlotOverride(uint32_t instanceId, uint16_t textureSlot);
void setInstanceVisible(uint32_t instanceId, bool visible);
void removeInstance(uint32_t instanceId);
@ -88,45 +89,32 @@ public:
/** Detach a weapon from the given attachment point. */
void detachWeapon(uint32_t charInstanceId, uint32_t attachmentId);
/** Get the world-space transform of an attachment point on an instance.
* Used for mount seats, weapon positions, etc.
* @param instanceId The character/mount instance
* @param attachmentId The attachment point ID (0=Mount, 1=RightHand, 2=LeftHand, etc.)
* @param outTransform The resulting world-space transform matrix
* @return true if attachment found and matrix computed
*/
/** Get the world-space transform of an attachment point on an instance. */
bool getAttachmentTransform(uint32_t instanceId, uint32_t attachmentId, glm::mat4& outTransform);
size_t getInstanceCount() const { return instances.size(); }
void setFog(const glm::vec3& color, float start, float end) {
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDirIn[3], const float lightColorIn[3],
const float ambientColorIn[3]) {
lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]);
lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]);
ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]);
}
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
void clearShadowMap() { shadowEnabled = false; }
// Fog/lighting/shadow are now in per-frame UBO — keep stubs for callers that haven't been updated
void setFog(const glm::vec3&, float, float) {}
void setLighting(const float[3], const float[3], const float[3]) {}
void setShadowMap(VkTexture*, const glm::mat4&) {}
void clearShadowMap() {}
private:
// GPU representation of M2 model
struct M2ModelGPU {
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
pipeline::M2Model data; // Original model data
std::vector<glm::mat4> bindPose; // Inverse bind pose matrices
// Textures loaded from BLP (indexed by texture array position)
std::vector<GLuint> textureIds;
std::vector<VkTexture*> textureIds;
};
// Character instance
@ -151,11 +139,11 @@ private:
// Empty = render all (for non-character models)
std::unordered_set<uint16_t> activeGeosets;
// Per-geoset-group texture overrides (group → GL texture ID)
std::unordered_map<uint16_t, GLuint> groupTextureOverrides;
// Per-geoset-group texture overrides (group → VkTexture*)
std::unordered_map<uint16_t, VkTexture*> groupTextureOverrides;
// Per-texture-slot overrides (slot → GL texture ID)
std::unordered_map<uint16_t, GLuint> textureSlotOverrides;
// Per-texture-slot overrides (slot → VkTexture*)
std::unordered_map<uint16_t, VkTexture*> textureSlotOverrides;
// Weapon attachments (weapons parented to this instance's bones)
std::vector<WeaponAttachment> weaponAttachments;
@ -175,6 +163,12 @@ private:
// Override model matrix (used for weapon instances positioned by parent bone)
bool hasOverrideModelMatrix = false;
glm::mat4 overrideModelMatrix{1.0f};
// Per-instance bone SSBO (double-buffered per frame)
VkBuffer boneBuffer[2] = {};
VmaAllocation boneAlloc[2] = {};
void* boneMapped[2] = {};
VkDescriptorSet boneSet[2] = {};
};
void setupModelBuffers(M2ModelGPU& gpuModel);
@ -183,6 +177,8 @@ private:
void calculateBoneMatrices(CharacterInstance& instance);
glm::mat4 getBoneTransform(const pipeline::M2Bone& bone, float time, int sequenceIndex);
glm::mat4 getModelMatrix(const CharacterInstance& instance) const;
void destroyModelGPU(M2ModelGPU& gpuModel);
void destroyInstanceBones(CharacterInstance& inst);
// Keyframe interpolation helpers
static int findKeyframeIndex(const std::vector<uint32_t>& timestamps, float time);
@ -194,83 +190,76 @@ private:
public:
/**
* Build a composited character skin texture by alpha-blending overlay
* layers (e.g. underwear) onto a base skin BLP. Each overlay is placed
* at the correct CharComponentTextureSections region based on its
* filename (pelvis, torso, etc.). Returns the resulting GL texture ID.
* layers onto a base skin BLP. Returns the resulting VkTexture*.
*/
GLuint compositeTextures(const std::vector<std::string>& layerPaths);
VkTexture* compositeTextures(const std::vector<std::string>& layerPaths);
/**
* Build a composited character skin with explicit region-based equipment overlays.
* @param basePath Body skin texture path
* @param baseLayers Underwear overlay paths (placed by filename keyword)
* @param regionLayers Pairs of (region_index, blp_path) for equipment textures
* @return GL texture ID of the composited result
*/
GLuint compositeWithRegions(const std::string& basePath,
VkTexture* compositeWithRegions(const std::string& basePath,
const std::vector<std::string>& baseLayers,
const std::vector<std::pair<int, std::string>>& regionLayers);
/** Clear the composite texture cache (forces re-compositing on next call). */
void clearCompositeCache();
/** Load a BLP texture from MPQ and return the GL texture ID (cached). */
GLuint loadTexture(const std::string& path);
GLuint getTransparentTexture() const { return transparentTexture; }
/** Load a BLP texture from MPQ and return VkTexture* (cached). */
VkTexture* loadTexture(const std::string& path);
VkTexture* getTransparentTexture() const { return transparentTexture_.get(); }
/** Replace a loaded model's texture at the given slot with a new GL texture. */
void setModelTexture(uint32_t modelId, uint32_t textureSlot, GLuint textureId);
/** Replace a loaded model's texture at the given slot. */
void setModelTexture(uint32_t modelId, uint32_t textureSlot, VkTexture* texture);
/** Reset a model's texture slot back to white fallback. */
void resetModelTexture(uint32_t modelId, uint32_t textureSlot);
private:
std::unique_ptr<Shader> characterShader;
GLuint shadowCasterProgram = 0;
VkContext* vkCtx_ = nullptr;
pipeline::AssetManager* assetManager = nullptr;
// Fog parameters
glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f);
float fogStart = 400.0f;
float fogEnd = 1200.0f;
// Vulkan pipelines (one per blend mode)
VkPipeline opaquePipeline_ = VK_NULL_HANDLE;
VkPipeline alphaTestPipeline_ = VK_NULL_HANDLE;
VkPipeline alphaPipeline_ = VK_NULL_HANDLE;
VkPipeline additivePipeline_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
// Lighting parameters
glm::vec3 lightDir = glm::vec3(0.0f, -1.0f, 0.3f);
glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
// Descriptor set layouts
VkDescriptorSetLayout perFrameLayout_ = VK_NULL_HANDLE; // set 0 (owned by Renderer)
VkDescriptorSetLayout materialSetLayout_ = VK_NULL_HANDLE; // set 1
VkDescriptorSetLayout boneSetLayout_ = VK_NULL_HANDLE; // set 2
// Shadow mapping
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
bool shadowEnabled = false;
// Descriptor pool
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
VkDescriptorPool boneDescPool_ = VK_NULL_HANDLE;
// Texture cache
struct TextureCacheEntry {
GLuint id = 0;
std::unique_ptr<VkTexture> texture;
size_t approxBytes = 0;
uint64_t lastUse = 0;
bool hasAlpha = false;
bool colorKeyBlack = false;
};
std::unordered_map<std::string, TextureCacheEntry> textureCache;
std::unordered_map<GLuint, bool> textureHasAlphaById_;
std::unordered_map<GLuint, bool> textureColorKeyBlackById_;
std::unordered_map<std::string, GLuint> compositeCache_; // key → GPU texture for reuse
std::unordered_map<VkTexture*, bool> textureHasAlphaByPtr_;
std::unordered_map<VkTexture*, bool> textureColorKeyBlackByPtr_;
std::unordered_map<std::string, VkTexture*> compositeCache_; // key → texture for reuse
std::unordered_set<std::string> failedTextureCache_; // negative cache for missing textures
size_t textureCacheBytes_ = 0;
uint64_t textureCacheCounter_ = 0;
size_t textureCacheBudgetBytes_ = 1024ull * 1024 * 1024; // Default, overridden at init
GLuint whiteTexture = 0;
GLuint transparentTexture = 0;
size_t textureCacheBudgetBytes_ = 1024ull * 1024 * 1024;
std::unique_ptr<VkTexture> whiteTexture_;
std::unique_ptr<VkTexture> transparentTexture_;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::unordered_map<uint32_t, CharacterInstance> instances;
uint32_t nextInstanceId = 1;
// Maximum bones supported (GPU uniform limit)
// WoW character models can have 210+ bones; GPU reports 4096 components (~256 mat4)
// Maximum bones supported
static constexpr int MAX_BONES = 240;
};

View file

@ -1,8 +1,8 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <deque>
#include <cstdint>
@ -12,7 +12,7 @@ namespace pipeline { class AssetManager; }
namespace rendering {
class Camera;
class Shader;
class VkContext;
class M2Renderer;
/// Renders a red-orange ribbon streak trailing behind the warrior during Charge,
@ -22,7 +22,7 @@ public:
ChargeEffect();
~ChargeEffect();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/// Try to load M2 spell models (Charge_Caster.m2, etc.)
@ -41,7 +41,7 @@ public:
void triggerImpact(const glm::vec3& position);
void update(float deltaTime);
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
bool isActive() const { return emitting_ || !trail_.empty() || !dustPuffs_.empty(); }
@ -59,10 +59,17 @@ private:
static constexpr float TRAIL_SPAWN_DIST = 0.4f; // Min distance between trail points
std::deque<TrailPoint> trail_;
GLuint ribbonVao_ = 0;
GLuint ribbonVbo_ = 0;
std::unique_ptr<Shader> ribbonShader_;
std::vector<float> ribbonVerts_; // pos(3) + alpha(1) + heat(1) = 5 floats per vert
// Vulkan objects
VkContext* vkCtx_ = nullptr;
// Ribbon pipeline + dynamic buffer
VkPipeline ribbonPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout ribbonPipelineLayout_ = VK_NULL_HANDLE;
::VkBuffer ribbonDynamicVB_ = VK_NULL_HANDLE;
VmaAllocation ribbonDynamicVBAlloc_ = VK_NULL_HANDLE;
VmaAllocationInfo ribbonDynamicVBAllocInfo_{};
VkDeviceSize ribbonDynamicVBSize_ = 0;
std::vector<float> ribbonVerts_; // pos(3) + alpha(1) + heat(1) + height(1) = 6 floats per vert
// --- Dust puffs (small point sprites at feet) ---
struct DustPuff {
@ -77,9 +84,13 @@ private:
static constexpr int MAX_DUST = 80;
std::vector<DustPuff> dustPuffs_;
GLuint dustVao_ = 0;
GLuint dustVbo_ = 0;
std::unique_ptr<Shader> dustShader_;
// Dust pipeline + dynamic buffer
VkPipeline dustPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout dustPipelineLayout_ = VK_NULL_HANDLE;
::VkBuffer dustDynamicVB_ = VK_NULL_HANDLE;
VmaAllocation dustDynamicVBAlloc_ = VK_NULL_HANDLE;
VmaAllocationInfo dustDynamicVBAllocInfo_{};
VkDeviceSize dustDynamicVBSize_ = 0;
std::vector<float> dustVerts_;
bool emitting_ = false;

View file

@ -1,25 +1,27 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
class VkContext;
/**
* @brief Renders procedural animated clouds on a sky dome
* Procedural cloud renderer (Vulkan)
*
* Features:
* - Procedural cloud generation using multiple noise layers
* - Two cloud layers at different altitudes
* - Animated wind movement
* - Time-of-day color tinting (orange at sunrise/sunset)
* - Transparency and soft edges
* Renders animated procedural clouds on a sky hemisphere using FBM noise.
* Two noise layers at different frequencies produce realistic cloud shapes.
*
* Pipeline layout:
* set 0 = perFrameLayout (camera UBO view, projection, etc.)
* push = CloudPush (vec4 cloudColor + float density + float windOffset = 24 bytes)
*
* The vertex shader reads view/projection from set 0 directly; no per-object
* model matrix is needed (clouds are locked to the sky dome).
*/
class Clouds {
public:
@ -27,68 +29,79 @@ public:
~Clouds();
/**
* @brief Initialize cloud system (generate mesh and shaders)
* @return true if initialization succeeded
* Initialize the cloud system.
* @param ctx Vulkan context
* @param perFrameLayout Descriptor set layout for set 0 (camera UBO)
*/
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/**
* @brief Render clouds
* @param camera The camera to render from
* @param timeOfDay Current time (0-24 hours)
* Render clouds.
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, camera UBO)
* @param timeOfDay Time of day in hours (0-24)
*/
void render(const Camera& camera, float timeOfDay);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay);
/**
* @brief Update cloud animation
* @param deltaTime Time since last frame
* Update cloud animation (wind drift).
* @param deltaTime Seconds since last frame
*/
void update(float deltaTime);
/**
* @brief Enable or disable cloud rendering
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
// --- Enable / disable ---
void setEnabled(bool enabled) { enabled_ = enabled; }
bool isEnabled() const { return enabled_; }
/**
* @brief Set cloud density (0.0 = clear, 1.0 = overcast)
*/
// --- Cloud parameters ---
/** Cloud coverage, 0 = clear, 1 = overcast. */
void setDensity(float density);
float getDensity() const { return density; }
float getDensity() const { return density_; }
/**
* @brief Set wind speed multiplier
*/
void setWindSpeed(float speed) { windSpeed = speed; }
float getWindSpeed() const { return windSpeed; }
void setWindSpeed(float speed) { windSpeed_ = speed; }
float getWindSpeed() const { return windSpeed_; }
private:
// Push constant block — must match clouds.frag.glsl
struct CloudPush {
glm::vec4 cloudColor; // 16 bytes (xyz = colour, w unused)
float density; // 4 bytes
float windOffset; // 4 bytes
// total = 24 bytes
};
static_assert(sizeof(CloudPush) == 24, "CloudPush size mismatch");
void generateMesh();
void cleanup();
void createBuffers();
void destroyBuffers();
glm::vec3 getCloudColor(float timeOfDay) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
std::unique_ptr<Shader> shader;
// Vulkan objects
VkContext* vkCtx_ = nullptr;
VkPipeline pipeline_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkBuffer vertexBuffer_ = VK_NULL_HANDLE;
VmaAllocation vertexAlloc_ = VK_NULL_HANDLE;
VkBuffer indexBuffer_ = VK_NULL_HANDLE;
VmaAllocation indexAlloc_ = VK_NULL_HANDLE;
// Mesh data
std::vector<glm::vec3> vertices;
std::vector<unsigned int> indices;
int triangleCount = 0;
// Mesh data (CPU side, used during initialization only)
std::vector<glm::vec3> vertices_;
std::vector<uint32_t> indices_;
int indexCount_ = 0;
// Cloud parameters
bool enabled = true;
float density = 0.5f; // Cloud coverage
float windSpeed = 1.0f;
float windOffset = 0.0f; // Accumulated wind movement
bool enabled_ = true;
float density_ = 0.5f;
float windSpeed_ = 1.0f;
float windOffset_ = 0.0f; // Accumulated wind movement
// Mesh generation parameters
static constexpr int SEGMENTS = 32; // Horizontal segments
static constexpr int RINGS = 8; // Vertical rings (only upper hemisphere)
static constexpr float RADIUS = 900.0f; // Slightly smaller than skybox
static constexpr int SEGMENTS = 32;
static constexpr int RINGS = 8;
static constexpr float RADIUS = 900.0f; // Slightly smaller than skybox
};
} // namespace rendering

View file

@ -1,15 +1,15 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
class VkContext;
/**
* @brief Renders lens flare effect when looking at the sun
@ -28,17 +28,25 @@ public:
/**
* @brief Initialize lens flare system
* @param ctx Vulkan context
* @param perFrameLayout Per-frame descriptor set layout (unused, kept for API consistency)
* @return true if initialization succeeded
*/
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
/**
* @brief Destroy Vulkan resources
*/
void shutdown();
/**
* @brief Render lens flare effect
* @param cmd Command buffer to record into
* @param camera The camera to render from
* @param sunPosition World-space sun position
* @param timeOfDay Current time (0-24 hours)
*/
void render(const Camera& camera, const glm::vec3& sunPosition, float timeOfDay);
void render(VkCommandBuffer cmd, const Camera& camera, const glm::vec3& sunPosition, float timeOfDay);
/**
* @brief Enable or disable lens flare rendering
@ -60,15 +68,24 @@ private:
float brightness; // Brightness multiplier
};
struct FlarePushConstants {
glm::vec2 position; // Screen-space position (-1 to 1)
float size; // Size in screen space
float aspectRatio; // Viewport aspect ratio
glm::vec4 colorBrightness; // RGB color + brightness in w
};
void generateFlareElements();
void cleanup();
float calculateSunVisibility(const Camera& camera, const glm::vec3& sunPosition) const;
glm::vec2 worldToScreen(const Camera& camera, const glm::vec3& worldPos) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
std::unique_ptr<Shader> shader;
VkContext* vkCtx = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
// Flare elements
std::vector<FlareElement> flareElements;

View file

@ -1,15 +1,15 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
// Forward declarations
class Shader;
class Camera;
class VkContext;
/**
* Lightning system for thunder storm effects
@ -26,11 +26,11 @@ public:
Lightning();
~Lightning();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
void update(float deltaTime, const Camera& camera);
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
// Control
void setEnabled(bool enabled);
@ -68,8 +68,8 @@ private:
void updateFlash(float deltaTime);
void spawnRandomStrike(const glm::vec3& cameraPos);
void renderBolts(const glm::mat4& viewProj);
void renderFlash();
void renderBolts(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
void renderFlash(VkCommandBuffer cmd);
bool enabled = true;
float intensity = 0.5f; // Strike frequency multiplier
@ -82,13 +82,22 @@ private:
std::vector<LightningBolt> bolts;
Flash flash;
// Rendering
std::unique_ptr<Shader> boltShader;
std::unique_ptr<Shader> flashShader;
unsigned int boltVAO = 0;
unsigned int boltVBO = 0;
unsigned int flashVAO = 0;
unsigned int flashVBO = 0;
// Vulkan objects
VkContext* vkCtx = nullptr;
// Bolt pipeline + dynamic buffer
VkPipeline boltPipeline = VK_NULL_HANDLE;
VkPipelineLayout boltPipelineLayout = VK_NULL_HANDLE;
::VkBuffer boltDynamicVB = VK_NULL_HANDLE;
VmaAllocation boltDynamicVBAlloc = VK_NULL_HANDLE;
VmaAllocationInfo boltDynamicVBAllocInfo{};
VkDeviceSize boltDynamicVBSize = 0;
// Flash pipeline + static quad buffer
VkPipeline flashPipeline = VK_NULL_HANDLE;
VkPipelineLayout flashPipelineLayout = VK_NULL_HANDLE;
::VkBuffer flashQuadVB = VK_NULL_HANDLE;
VmaAllocation flashQuadVBAlloc = VK_NULL_HANDLE;
// Configuration
static constexpr int MAX_BOLTS = 3;

View file

@ -1,12 +1,14 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <string>
#include <vector>
namespace wowee {
namespace rendering {
class VkContext;
class LoadingScreen {
public:
LoadingScreen();
@ -15,32 +17,28 @@ public:
bool initialize();
void shutdown();
// Select a random loading screen image
void selectRandomImage();
// Render the loading screen with progress bar and status text
// Render the loading screen with progress bar and status text (pure ImGui)
void render();
// Update loading progress (0.0 to 1.0)
void setProgress(float progress) { loadProgress = progress; }
// Set loading status text
void setStatus(const std::string& status) { statusText = status; }
// Must be set before initialize() for Vulkan texture upload
void setVkContext(VkContext* ctx) { vkCtx = ctx; }
private:
bool loadImage(const std::string& path);
void createQuad();
void createBarQuad();
GLuint textureId = 0;
GLuint vao = 0;
GLuint vbo = 0;
GLuint shaderId = 0;
VkContext* vkCtx = nullptr;
// Progress bar GL objects
GLuint barVao = 0;
GLuint barVbo = 0;
GLuint barShaderId = 0;
// Vulkan texture for background image
VkImage bgImage = VK_NULL_HANDLE;
VkDeviceMemory bgMemory = VK_NULL_HANDLE;
VkImageView bgImageView = VK_NULL_HANDLE;
VkSampler bgSampler = VK_NULL_HANDLE;
VkDescriptorSet bgDescriptorSet = VK_NULL_HANDLE; // ImGui texture handle
std::vector<std::string> imagePaths;
int currentImageIndex = 0;

View file

@ -1,7 +1,8 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
@ -20,15 +21,19 @@ namespace pipeline {
namespace rendering {
class Shader;
class Camera;
class VkContext;
class VkTexture;
/**
* GPU representation of an M2 model
*/
struct M2ModelGPU {
struct BatchGPU {
GLuint texture = 0;
VkTexture* texture = nullptr; // from cache, NOT owned
VkDescriptorSet materialSet = VK_NULL_HANDLE; // set 1
::VkBuffer materialUBO = VK_NULL_HANDLE;
VmaAllocation materialUBOAlloc = VK_NULL_HANDLE;
uint32_t indexStart = 0; // offset in indices (not bytes)
uint32_t indexCount = 0;
bool hasAlpha = false;
@ -47,9 +52,10 @@ struct M2ModelGPU {
float glowSize = 1.0f; // Approx radius of batch geometry
};
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
::VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
::VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
std::vector<BatchGPU> batches;
@ -109,14 +115,14 @@ struct M2ModelGPU {
// Particle emitter data (kept from M2Model)
std::vector<pipeline::M2ParticleEmitter> particleEmitters;
std::vector<GLuint> particleTextures; // Resolved GL textures per emitter
std::vector<VkTexture*> particleTextures; // Resolved Vulkan textures per emitter
// Texture transform data for UV animation
std::vector<pipeline::M2TextureTransform> textureTransforms;
std::vector<uint16_t> textureTransformLookup;
std::vector<int> idleVariationIndices; // Sequence indices for idle variations (animId 0)
bool isValid() const { return vao != 0 && indexCount > 0; }
bool isValid() const { return vertexBuffer != VK_NULL_HANDLE && indexCount > 0; }
};
/**
@ -164,6 +170,12 @@ struct M2Instance {
// Frame-skip optimization (update distant animations less frequently)
uint8_t frameSkipCounter = 0;
// Per-instance bone SSBO (double-buffered)
::VkBuffer boneBuffer[2] = {};
VmaAllocation boneAlloc[2] = {};
void* boneMapped[2] = {};
VkDescriptorSet boneSet[2] = {};
void updateModelMatrix();
};
@ -180,8 +192,29 @@ struct SmokeParticle {
uint32_t instanceId = 0;
};
// M2 material UBO — matches M2Material in m2.frag.glsl (set 1, binding 2)
struct M2MaterialUBO {
int32_t hasTexture;
int32_t alphaTest;
int32_t colorKeyBlack;
float colorKeyThreshold;
int32_t unlit;
int32_t blendMode;
float fadeAlpha;
float interiorDarken;
float specularIntensity;
};
// M2 params UBO — matches M2Params in m2.vert.glsl (set 1, binding 1)
struct M2ParamsUBO {
float uvOffsetX;
float uvOffsetY;
int32_t texCoordSet;
int32_t useBones;
};
/**
* M2 Model Renderer
* M2 Model Renderer (Vulkan)
*
* Handles rendering of M2 models (doodads like trees, rocks, bushes)
*/
@ -190,137 +223,57 @@ public:
M2Renderer();
~M2Renderer();
bool initialize(pipeline::AssetManager* assets);
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout,
pipeline::AssetManager* assets);
void shutdown();
/**
* Check if a model is already loaded
* @param modelId ID to check
* @return True if model is loaded
*/
bool hasModel(uint32_t modelId) const;
/**
* Load an M2 model to GPU
* @param model Parsed M2 model data
* @param modelId Unique ID for this model
* @return True if successful
*/
bool loadModel(const pipeline::M2Model& model, uint32_t modelId);
/**
* Create an instance of a loaded model
* @param modelId ID of the loaded model
* @param position World position
* @param rotation Rotation in degrees (x, y, z)
* @param scale Scale factor (1.0 = normal)
* @return Instance ID
*/
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
/**
* Create an instance with a pre-computed model matrix
* Used for WMO doodads where the full transform is computed externally
*/
uint32_t createInstanceWithMatrix(uint32_t modelId, const glm::mat4& modelMatrix,
const glm::vec3& position);
/**
* Update animation state for all instances
* @param deltaTime Time since last frame
* @param cameraPos Camera world position (for frustum-culling bones)
* @param viewProjection Combined view*projection matrix
*/
void update(float deltaTime, const glm::vec3& cameraPos, const glm::mat4& viewProjection);
/**
* Render all visible instances
* Render all visible instances (Vulkan)
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
/**
* Initialize shadow pipeline (Phase 7)
*/
bool initializeShadow(VkRenderPass shadowRenderPass);
/**
* Render depth-only pass for shadow casting
*/
void renderShadow(GLuint shadowShaderProgram, const glm::vec3& shadowCenter, float halfExtent);
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix);
/**
* Render smoke particles (call after render())
* Render M2 particle emitters (point sprites)
*/
void renderSmokeParticles(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void renderM2Particles(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
/**
* Render M2 particle emitter particles (call after renderSmokeParticles())
* Render smoke particles from chimneys etc.
*/
void renderM2Particles(const glm::mat4& view, const glm::mat4& proj);
void renderSmokeParticles(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
/**
* Update the world position of an existing instance (e.g., for transports)
* @param instanceId Instance ID returned by createInstance()
* @param position New world position
*/
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
/**
* Update the full transform of an existing instance (e.g., for WMO doodads following parent WMO)
* @param instanceId Instance ID returned by createInstance()
* @param transform New world transform matrix
*/
void setInstanceTransform(uint32_t instanceId, const glm::mat4& transform);
/**
* Remove a specific instance by ID
* @param instanceId Instance ID returned by createInstance()
*/
void removeInstance(uint32_t instanceId);
/**
* Remove multiple instances with one spatial-index rebuild.
*/
void removeInstances(const std::vector<uint32_t>& instanceIds);
/**
* Clear all models and instances
*/
void clear();
/**
* Remove models that have no instances referencing them
* Call periodically to free GPU memory
*/
void cleanupUnusedModels();
/**
* Check collision with M2 objects and adjust position
* @param from Starting position
* @param to Desired position
* @param adjustedPos Output adjusted position
* @param playerRadius Collision radius of player
* @return true if collision occurred
*/
bool checkCollision(const glm::vec3& from, const glm::vec3& to,
glm::vec3& adjustedPos, float playerRadius = 0.5f) const;
/**
* Approximate top surface height for standing/jumping on doodads.
* @param glX World X
* @param glY World Y
* @param glZ Query/reference Z (used to ignore unreachable tops)
*/
std::optional<float> getFloorHeight(float glX, float glY, float glZ, float* outNormalZ = nullptr) const;
/**
* Raycast against M2 bounding boxes for camera collision
* @param origin Ray origin (e.g., character head position)
* @param direction Ray direction (normalized)
* @param maxDistance Maximum ray distance to check
* @return Distance to first intersection, or maxDistance if no hit
*/
float raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3& direction, float maxDistance) const;
/**
* Limit expensive collision/raycast queries to objects near a focus point.
*/
void setCollisionFocus(const glm::vec3& worldPos, float radius);
void clearCollisionFocus();
@ -335,21 +288,12 @@ public:
uint32_t getTotalTriangleCount() const;
uint32_t getDrawCallCount() const { return lastDrawCallCount; }
void setFog(const glm::vec3& color, float start, float end) {
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDirIn[3], const float lightColorIn[3],
const float ambientColorIn[3]) {
lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]);
lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]);
ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]);
}
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
void clearShadowMap() { shadowEnabled = false; }
// Lighting/fog/shadow are now in per-frame UBO; these are no-ops for API compat
void setFog(const glm::vec3& /*color*/, float /*start*/, float /*end*/) {}
void setLighting(const float /*lightDirIn*/[3], const float /*lightColorIn*/[3],
const float /*ambientColorIn*/[3]) {}
void setShadowMap(uint32_t /*depthTex*/, const glm::mat4& /*lightSpace*/) {}
void clearShadowMap() {}
void setInsideInterior(bool inside) { insideInterior = inside; }
void setOnTaxi(bool onTaxi) { onTaxi_ = onTaxi; }
@ -359,7 +303,51 @@ private:
bool insideInterior = false;
bool onTaxi_ = false;
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
// Vulkan context
VkContext* vkCtx_ = nullptr;
// Vulkan pipelines (one per blend mode)
VkPipeline opaquePipeline_ = VK_NULL_HANDLE; // blend mode 0
VkPipeline alphaTestPipeline_ = VK_NULL_HANDLE; // blend mode 1
VkPipeline alphaPipeline_ = VK_NULL_HANDLE; // blend mode 2
VkPipeline additivePipeline_ = VK_NULL_HANDLE; // blend mode 3+
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
// Shadow rendering (Phase 7)
VkPipeline shadowPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout shadowPipelineLayout_ = VK_NULL_HANDLE;
VkDescriptorSetLayout shadowParamsLayout_ = VK_NULL_HANDLE;
VkDescriptorPool shadowParamsPool_ = VK_NULL_HANDLE;
VkDescriptorSet shadowParamsSet_ = VK_NULL_HANDLE;
::VkBuffer shadowParamsUBO_ = VK_NULL_HANDLE;
VmaAllocation shadowParamsAlloc_ = VK_NULL_HANDLE;
// Particle pipelines
VkPipeline particlePipeline_ = VK_NULL_HANDLE; // M2 emitter particles
VkPipeline particleAdditivePipeline_ = VK_NULL_HANDLE; // Additive particle blend
VkPipelineLayout particlePipelineLayout_ = VK_NULL_HANDLE;
VkPipeline smokePipeline_ = VK_NULL_HANDLE; // Smoke particles
VkPipelineLayout smokePipelineLayout_ = VK_NULL_HANDLE;
// Descriptor set layouts
VkDescriptorSetLayout materialSetLayout_ = VK_NULL_HANDLE; // set 1
VkDescriptorSetLayout boneSetLayout_ = VK_NULL_HANDLE; // set 2
VkDescriptorSetLayout particleTexLayout_ = VK_NULL_HANDLE; // particle set 1 (texture only)
// Descriptor pools
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
VkDescriptorPool boneDescPool_ = VK_NULL_HANDLE;
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
static constexpr uint32_t MAX_BONE_SETS = 2048;
// Dynamic particle buffers
::VkBuffer smokeVB_ = VK_NULL_HANDLE;
VmaAllocation smokeVBAlloc_ = VK_NULL_HANDLE;
void* smokeVBMapped_ = nullptr;
::VkBuffer m2ParticleVB_ = VK_NULL_HANDLE;
VmaAllocation m2ParticleVBAlloc_ = VK_NULL_HANDLE;
void* m2ParticleVBMapped_ = nullptr;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::vector<M2Instance> instances;
@ -367,37 +355,22 @@ private:
uint32_t nextInstanceId = 1;
uint32_t lastDrawCallCount = 0;
GLuint loadTexture(const std::string& path, uint32_t texFlags = 0);
VkTexture* loadTexture(const std::string& path, uint32_t texFlags = 0);
struct TextureCacheEntry {
GLuint id = 0;
std::unique_ptr<VkTexture> texture;
size_t approxBytes = 0;
uint64_t lastUse = 0;
bool hasAlpha = true;
bool colorKeyBlack = false;
};
std::unordered_map<std::string, TextureCacheEntry> textureCache;
std::unordered_map<GLuint, bool> textureHasAlphaById_;
std::unordered_map<GLuint, bool> textureColorKeyBlackById_;
std::unordered_map<VkTexture*, bool> textureHasAlphaByPtr_;
std::unordered_map<VkTexture*, bool> textureColorKeyBlackByPtr_;
size_t textureCacheBytes_ = 0;
uint64_t textureCacheCounter_ = 0;
size_t textureCacheBudgetBytes_ = 2048ull * 1024 * 1024; // Default, overridden at init
GLuint whiteTexture = 0;
GLuint glowTexture = 0; // Soft radial gradient for glow sprites
// Lighting uniforms
glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f);
glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
// Fog parameters
glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f);
float fogStart = 400.0f;
float fogEnd = 1200.0f;
// Shadow mapping
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
bool shadowEnabled = false;
size_t textureCacheBudgetBytes_ = 2048ull * 1024 * 1024;
std::unique_ptr<VkTexture> whiteTexture_;
std::unique_ptr<VkTexture> glowTexture_;
// Optional query-space culling for collision/raycast hot paths.
bool collisionFocusEnabled = false;
@ -458,17 +431,11 @@ private:
// Smoke particle system
std::vector<SmokeParticle> smokeParticles;
GLuint smokeVAO = 0;
GLuint smokeVBO = 0;
std::unique_ptr<Shader> smokeShader;
static constexpr int MAX_SMOKE_PARTICLES = 1000;
float smokeEmitAccum = 0.0f;
std::mt19937 smokeRng{42};
// M2 particle emitter system
GLuint m2ParticleShader_ = 0;
GLuint m2ParticleVAO_ = 0;
GLuint m2ParticleVBO_ = 0;
static constexpr size_t MAX_M2_PARTICLES = 4000;
std::mt19937 particleRng_{123};
@ -486,6 +453,15 @@ private:
glm::vec3 interpFBlockVec3(const pipeline::M2FBlock& fb, float lifeRatio);
void emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt);
void updateParticles(M2Instance& inst, float dt);
// Helper to allocate descriptor sets
VkDescriptorSet allocateMaterialSet();
VkDescriptorSet allocateBoneSet();
// Helper to destroy model GPU resources
void destroyModelGPU(M2ModelGPU& model);
// Helper to destroy instance bone buffers
void destroyInstanceBones(M2Instance& inst);
};
} // namespace rendering

View file

@ -1,6 +1,7 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <chrono>
#include <memory>
@ -12,22 +13,28 @@ namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class Shader;
class Camera;
class VkContext;
class VkTexture;
class VkRenderTarget;
class Minimap {
public:
Minimap();
~Minimap();
bool initialize(int size = 200);
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, int size = 200);
void shutdown();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
void setMapName(const std::string& name);
void render(const Camera& playerCamera, const glm::vec3& centerWorldPos,
int screenWidth, int screenHeight);
/// Off-screen composite pass — call BEFORE the main render pass begins.
void compositePass(VkCommandBuffer cmd, const glm::vec3& centerWorldPos);
/// Display quad — call INSIDE the main render pass.
void render(VkCommandBuffer cmd, const Camera& playerCamera,
const glm::vec3& centerWorldPos, int screenWidth, int screenHeight);
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
@ -45,17 +52,15 @@ public:
void zoomOut() { viewRadius = std::min(800.0f, viewRadius + 50.0f); }
// Public accessors for WorldMap
GLuint getOrLoadTileTexture(int tileX, int tileY);
VkTexture* getOrLoadTileTexture(int tileX, int tileY);
void ensureTRSParsed() { if (!trsParsed) parseTRS(); }
GLuint getTileQuadVAO() const { return tileQuadVAO; }
const std::string& getMapName() const { return mapName; }
private:
void parseTRS();
void compositeTilesToFBO(const glm::vec3& centerWorldPos);
void renderQuad(const Camera& playerCamera, const glm::vec3& centerWorldPos,
int screenWidth, int screenHeight);
void updateTileDescriptors(uint32_t frameIdx, int centerTileX, int centerTileY);
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
std::string mapName = "Azeroth";
@ -63,28 +68,36 @@ private:
std::unordered_map<std::string, std::string> trsLookup;
bool trsParsed = false;
// Tile texture cache: hash → GL texture ID
std::unordered_map<std::string, GLuint> tileTextureCache;
GLuint noDataTexture = 0; // dark fallback for missing tiles
// Tile texture cache: hash → VkTexture
std::unordered_map<std::string, std::unique_ptr<VkTexture>> tileTextureCache;
std::unique_ptr<VkTexture> noDataTexture;
// Composite FBO (3x3 tiles = 768x768)
GLuint compositeFBO = 0;
GLuint compositeTexture = 0;
// Composite render target (3x3 tiles = 768x768)
std::unique_ptr<VkRenderTarget> compositeTarget;
static constexpr int TILE_PX = 256;
static constexpr int COMPOSITE_PX = TILE_PX * 3; // 768
// Tile compositing quad
GLuint tileQuadVAO = 0;
GLuint tileQuadVBO = 0;
std::unique_ptr<Shader> tileShader;
// Shared quad vertex buffer (6 verts, pos2 + uv2 = 16 bytes/vert)
::VkBuffer quadVB = VK_NULL_HANDLE;
VmaAllocation quadVBAlloc = VK_NULL_HANDLE;
// Screen quad
GLuint quadVAO = 0;
GLuint quadVBO = 0;
std::unique_ptr<Shader> quadShader;
// Descriptor resources (shared layout: 1 combined image sampler at binding 0)
VkDescriptorSetLayout samplerSetLayout = VK_NULL_HANDLE;
VkDescriptorPool descPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_DESC_SETS = 24;
// Tile composite pipeline (renders into VkRenderTarget)
VkPipeline tilePipeline = VK_NULL_HANDLE;
VkPipelineLayout tilePipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet tileDescSets[2][9] = {}; // [frameInFlight][tileSlot]
// Display pipeline (renders into main render pass)
VkPipeline displayPipeline = VK_NULL_HANDLE;
VkPipelineLayout displayPipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet displayDescSet = VK_NULL_HANDLE;
int mapSize = 200;
float viewRadius = 400.0f; // world units visible in minimap radius
float viewRadius = 400.0f;
bool enabled = true;
bool rotateWithCamera = false;
bool squareShape = false;

View file

@ -1,29 +1,29 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
class VkContext;
class MountDust {
public:
MountDust();
~MountDust();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
// Spawn dust particles at mount feet when moving on ground
void spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving);
void update(float deltaTime);
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
private:
struct Particle {
@ -38,11 +38,18 @@ private:
static constexpr int MAX_DUST_PARTICLES = 300;
std::vector<Particle> particles;
GLuint vao = 0;
GLuint vbo = 0;
std::unique_ptr<Shader> shader;
std::vector<float> vertexData;
// Vulkan objects
VkContext* vkCtx = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
// Dynamic mapped buffer for particle vertex data (updated every frame)
::VkBuffer dynamicVB = VK_NULL_HANDLE;
VmaAllocation dynamicVBAlloc = VK_NULL_HANDLE;
VmaAllocationInfo dynamicVBAllocInfo{};
VkDeviceSize dynamicVBSize = 0;
std::vector<float> vertexData;
float spawnAccum = 0.0f;
};

View file

@ -1,15 +1,20 @@
#pragma once
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <cstdint>
#include <vector>
#include <unordered_map>
#include "rendering/vk_texture.hpp"
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class Camera;
class VkContext;
/**
* Renders quest markers as billboarded sprites above NPCs
@ -20,7 +25,7 @@ public:
QuestMarkerRenderer();
~QuestMarkerRenderer();
bool initialize(pipeline::AssetManager* assetManager);
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout, pipeline::AssetManager* assetManager);
void shutdown();
/**
@ -44,8 +49,11 @@ public:
/**
* Render all quest markers (call after world rendering, before UI)
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, contains camera UBO)
* @param camera Camera for billboard calculation (CPU-side view matrix)
*/
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
private:
struct Marker {
@ -55,16 +63,29 @@ private:
};
std::unordered_map<uint64_t, Marker> markers_;
// OpenGL resources
uint32_t vao_ = 0;
uint32_t vbo_ = 0;
uint32_t shaderProgram_ = 0;
uint32_t textures_[3] = {0, 0, 0}; // available, turnin, incomplete
// Vulkan context
VkContext* vkCtx_ = nullptr;
// Pipeline
VkPipeline pipeline_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
// Descriptor resources for per-material texture (set 1)
VkDescriptorSetLayout materialSetLayout_ = VK_NULL_HANDLE;
VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE;
VkDescriptorSet texDescSets_[3] = {VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE};
// Textures: available, turnin, incomplete
VkTexture textures_[3];
// Quad vertex buffer
VkBuffer quadVB_ = VK_NULL_HANDLE;
VmaAllocation quadVBAlloc_ = VK_NULL_HANDLE;
void createQuad();
void loadTextures(pipeline::AssetManager* assetManager);
void createShader();
void createDescriptorResources();
};
} // namespace rendering

View file

@ -5,9 +5,14 @@
#include <cstdint>
#include <vector>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include "rendering/vk_frame_data.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 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; }
@ -28,7 +33,6 @@ class Clouds;
class LensFlare;
class Weather;
class LightingManager;
class SkySystem;
class SwimEffects;
class MountDust;
class LevelUpEffect;
@ -37,6 +41,7 @@ class CharacterRenderer;
class WMORenderer;
class M2Renderer;
class Minimap;
class WorldMap;
class QuestMarkerRenderer;
class Shader;
@ -101,19 +106,23 @@ public:
TerrainManager* getTerrainManager() const { return terrainManager.get(); }
PerformanceHUD* getPerformanceHUD() { return performanceHUD.get(); }
WaterRenderer* getWaterRenderer() const { return waterRenderer.get(); }
Skybox* getSkybox() const { return skybox.get(); }
Celestial* getCelestial() const { return celestial.get(); }
StarField* getStarField() const { return starField.get(); }
Clouds* getClouds() const { return clouds.get(); }
LensFlare* getLensFlare() const { return lensFlare.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(); }
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; }
VkContext* getVkContext() const { return vkCtx; }
VkDescriptorSetLayout getPerFrameSetLayout() const { return perFrameSetLayout; }
VkRenderPass getShadowRenderPass() const { return shadowRenderPass; }
// Third-person character follow
void setCharacterFollow(uint32_t instanceId);
@ -202,6 +211,7 @@ private:
std::unique_ptr<WMORenderer> wmoRenderer;
std::unique_ptr<M2Renderer> m2Renderer;
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;
@ -214,31 +224,14 @@ private:
std::unique_ptr<audio::SpellSoundManager> spellSoundManager;
std::unique_ptr<audio::MovementSoundManager> movementSoundManager;
std::unique_ptr<game::ZoneManager> zoneManager;
std::unique_ptr<Shader> underwaterOverlayShader;
uint32_t underwaterOverlayVAO = 0;
uint32_t underwaterOverlayVBO = 0;
// Post-process FBO pipeline (HDR MSAA → resolve → tonemap)
uint32_t sceneFBO = 0; // MSAA render target
uint32_t sceneColorRBO = 0; // GL_RGBA16F multisampled renderbuffer
uint32_t sceneDepthRBO = 0; // GL_DEPTH_COMPONENT24 multisampled renderbuffer
uint32_t resolveFBO = 0; // Non-MSAA resolve target
uint32_t resolveColorTex = 0; // GL_RGBA16F resolved texture (sampled by post-process)
uint32_t resolveDepthTex = 0; // GL_DEPTH_COMPONENT24 resolved texture (for future SSAO)
uint32_t screenQuadVAO = 0;
uint32_t screenQuadVBO = 0;
std::unique_ptr<Shader> postProcessShader;
int fbWidth = 0, fbHeight = 0;
void initPostProcess(int w, int h);
void resizePostProcess(int w, int h);
void shutdownPostProcess();
// Shadow mapping
static constexpr int SHADOW_MAP_SIZE = 2048;
uint32_t shadowFBO = 0;
uint32_t shadowDepthTex = 0;
uint32_t shadowShaderProgram = 0;
// Shadow mapping (Vulkan)
static constexpr uint32_t SHADOW_MAP_SIZE = 2048;
VkImage shadowDepthImage = VK_NULL_HANDLE;
VmaAllocation shadowDepthAlloc = VK_NULL_HANDLE;
VkImageView shadowDepthView = VK_NULL_HANDLE;
VkSampler shadowSampler = VK_NULL_HANDLE;
VkRenderPass shadowRenderPass = VK_NULL_HANDLE;
VkFramebuffer shadowFramebuffer = VK_NULL_HANDLE;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
glm::vec3 shadowCenter = glm::vec3(0.0f);
bool shadowCenterInitialized = false;
@ -250,9 +243,7 @@ public:
bool areShadowsEnabled() const { return shadowsEnabled; }
private:
void initShadowMap();
void renderShadowPass();
uint32_t compileShadowShader();
glm::mat4 computeLightSpaceMatrix();
pipeline::AssetManager* cachedAssetManager = nullptr;
@ -289,10 +280,13 @@ private:
const glm::vec3* targetPosition = nullptr;
bool inCombat_ = false;
// Selection circle rendering
uint32_t selCircleVAO = 0;
uint32_t selCircleVBO = 0;
uint32_t selCircleShader = 0;
// 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);
@ -360,6 +354,26 @@ private:
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;
bool createPerFrameResources();
void destroyPerFrameResources();
void updatePerFrameUBO();
bool terrainEnabled = true;
bool terrainLoaded = false;

View file

@ -2,11 +2,13 @@
#include <memory>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
namespace wowee {
namespace rendering {
class Camera;
class VkContext;
class Skybox;
class Celestial;
class StarField;
@ -59,9 +61,11 @@ public:
~SkySystem();
/**
* Initialize sky system components
* Initialize sky system components.
* @param ctx Vulkan context (required for Vulkan renderers)
* @param perFrameLayout Descriptor set layout for set 0 (camera UBO)
*/
bool initialize();
bool initialize(VkContext* ctx = nullptr, VkDescriptorSetLayout perFrameLayout = VK_NULL_HANDLE);
void shutdown();
/**
@ -70,11 +74,14 @@ public:
void update(float deltaTime);
/**
* Render complete sky
* @param camera Camera for view/projection
* @param params Sky parameters from lighting system
* Render complete sky.
* @param cmd Active Vulkan command buffer
* @param perFrameSet Per-frame descriptor set (set 0, camera UBO)
* @param camera Camera for legacy sub-renderers (lens flare, etc.)
* @param params Sky parameters from lighting system
*/
void render(const Camera& camera, const SkyParams& params);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
const Camera& camera, const SkyParams& params);
/**
* Enable/disable procedural stars (DEBUG/FALLBACK)
@ -109,21 +116,21 @@ public:
float getBlueChildPhase() const;
// Component accessors (for direct control if needed)
Skybox* getSkybox() const { return skybox_.get(); }
Skybox* getSkybox() const { return skybox_.get(); }
Celestial* getCelestial() const { return celestial_.get(); }
StarField* getStarField() const { return starField_.get(); }
Clouds* getClouds() const { return clouds_.get(); }
Clouds* getClouds() const { return clouds_.get(); }
LensFlare* getLensFlare() const { return lensFlare_.get(); }
private:
std::unique_ptr<Skybox> skybox_; // Authoritative sky (gradient now, M2 models later)
std::unique_ptr<Celestial> celestial_; // Sun + 2 moons
std::unique_ptr<StarField> starField_; // Fallback procedural stars
std::unique_ptr<Clouds> clouds_; // Cloud layer
std::unique_ptr<LensFlare> lensFlare_; // Sun lens flare
std::unique_ptr<Skybox> skybox_; // Authoritative sky
std::unique_ptr<Celestial> celestial_; // Sun + 2 moons
std::unique_ptr<StarField> starField_; // Fallback procedural stars
std::unique_ptr<Clouds> clouds_; // Cloud layer
std::unique_ptr<LensFlare> lensFlare_; // Sun lens flare
bool proceduralStarsEnabled_ = false; // Default: OFF (skybox is authoritative)
bool debugSkyMode_ = false; // Force procedural stars for debugging
bool proceduralStarsEnabled_ = false;
bool debugSkyMode_ = false;
bool initialized_ = false;
};

View file

@ -2,12 +2,13 @@
#include <memory>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
class VkContext;
/**
* Skybox renderer
@ -20,15 +21,16 @@ public:
Skybox();
~Skybox();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/**
* Render the skybox
* @param camera Camera for view matrix (position is ignored for skybox)
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, contains camera UBO)
* @param timeOfDay Time of day in hours (0-24), affects sky color
*/
void render(const Camera& camera, float timeOfDay = 12.0f);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay = 12.0f);
/**
* Enable/disable skybox rendering
@ -66,11 +68,16 @@ private:
glm::vec3 getSkyColor(float altitude, float time) const;
glm::vec3 getZenithColor(float time) const;
std::unique_ptr<Shader> skyShader;
VkContext* vkCtx = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
int indexCount = 0;
float timeOfDay = 12.0f; // Default: noon

View file

@ -1,14 +1,14 @@
#pragma once
#include <memory>
#include <vector>
#include <glm/glm.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
class VkContext;
/**
* Star field renderer
@ -21,17 +21,18 @@ public:
StarField();
~StarField();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/**
* Render the star field
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, contains camera UBO)
* @param timeOfDay Time of day in hours (0-24)
* @param cloudDensity Optional cloud density from lighting (0-1, reduces star visibility)
* @param fogDensity Optional fog density from lighting (reduces star visibility)
* @param fogDensity Optional fog density from lighting (reduces star visibility)
*/
void render(const Camera& camera, float timeOfDay,
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay,
float cloudDensity = 0.0f, float fogDensity = 0.0f);
/**
@ -57,8 +58,6 @@ private:
float getStarIntensity(float timeOfDay) const;
std::unique_ptr<Shader> starShader;
struct Star {
glm::vec3 position;
float brightness; // 0.3 to 1.0
@ -68,8 +67,13 @@ private:
std::vector<Star> stars;
int starCount = 1000;
uint32_t vao = 0;
uint32_t vbo = 0;
VkContext* vkCtx = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
float twinkleTime = 0.0f;
bool renderingEnabled = true;

View file

@ -1,8 +1,8 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
@ -11,18 +11,18 @@ namespace rendering {
class Camera;
class CameraController;
class WaterRenderer;
class Shader;
class VkContext;
class SwimEffects {
public:
SwimEffects();
~SwimEffects();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
void update(const Camera& camera, const CameraController& cc,
const WaterRenderer& water, float deltaTime);
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
private:
struct Particle {
@ -40,10 +40,24 @@ private:
std::vector<Particle> ripples;
std::vector<Particle> bubbles;
GLuint rippleVAO = 0, rippleVBO = 0;
GLuint bubbleVAO = 0, bubbleVBO = 0;
std::unique_ptr<Shader> rippleShader;
std::unique_ptr<Shader> bubbleShader;
// Vulkan objects
VkContext* vkCtx = nullptr;
// Ripple pipeline + dynamic buffer
VkPipeline ripplePipeline = VK_NULL_HANDLE;
VkPipelineLayout ripplePipelineLayout = VK_NULL_HANDLE;
::VkBuffer rippleDynamicVB = VK_NULL_HANDLE;
VmaAllocation rippleDynamicVBAlloc = VK_NULL_HANDLE;
VmaAllocationInfo rippleDynamicVBAllocInfo{};
VkDeviceSize rippleDynamicVBSize = 0;
// Bubble pipeline + dynamic buffer
VkPipeline bubblePipeline = VK_NULL_HANDLE;
VkPipelineLayout bubblePipelineLayout = VK_NULL_HANDLE;
::VkBuffer bubbleDynamicVB = VK_NULL_HANDLE;
VmaAllocation bubbleDynamicVBAlloc = VK_NULL_HANDLE;
VmaAllocationInfo bubbleDynamicVBAllocInfo{};
VkDeviceSize bubbleDynamicVBSize = 0;
std::vector<float> rippleVertexData;
std::vector<float> bubbleVertexData;

View file

@ -2,10 +2,9 @@
#include "pipeline/terrain_mesh.hpp"
#include "pipeline/blp_loader.hpp"
#include "rendering/shader.hpp"
#include "rendering/texture.hpp"
#include "rendering/camera.hpp"
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
@ -18,21 +17,35 @@ namespace pipeline { class AssetManager; }
namespace rendering {
class VkContext;
class VkTexture;
class Frustum;
/**
* GPU-side terrain chunk data
* GPU-side terrain chunk data (Vulkan)
*/
struct TerrainChunkGPU {
GLuint vao = 0; // Vertex array object
GLuint vbo = 0; // Vertex buffer
GLuint ibo = 0; // Index buffer
uint32_t indexCount = 0; // Number of indices to draw
::VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
::VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
uint32_t indexCount = 0;
// Texture IDs for this chunk
GLuint baseTexture = 0;
std::vector<GLuint> layerTextures;
std::vector<GLuint> alphaTextures;
// Material descriptor set (set 1: 7 samplers + params UBO)
VkDescriptorSet materialSet = VK_NULL_HANDLE;
// Per-chunk params UBO (hasLayer1/2/3)
::VkBuffer paramsUBO = VK_NULL_HANDLE;
VmaAllocation paramsAlloc = VK_NULL_HANDLE;
// Texture handles (owned by cache, NOT destroyed per-chunk)
VkTexture* baseTexture = nullptr;
VkTexture* layerTextures[3] = {nullptr, nullptr, nullptr};
VkTexture* alphaTextures[3] = {nullptr, nullptr, nullptr};
int layerCount = 0;
// Per-chunk alpha textures (owned by this chunk, destroyed on removal)
std::vector<std::unique_ptr<VkTexture>> ownedAlphaTextures;
// World position for culling
float worldX = 0.0f;
@ -46,13 +59,11 @@ struct TerrainChunkGPU {
float boundingSphereRadius = 0.0f;
glm::vec3 boundingSphereCenter = glm::vec3(0.0f);
bool isValid() const { return vao != 0 && vbo != 0 && ibo != 0; }
bool isValid() const { return vertexBuffer != VK_NULL_HANDLE && indexBuffer != VK_NULL_HANDLE; }
};
/**
* Terrain renderer
*
* Handles uploading terrain meshes to GPU and rendering them
* Terrain renderer (Vulkan)
*/
class TerrainRenderer {
public:
@ -61,150 +72,92 @@ public:
/**
* Initialize terrain renderer
* @param ctx Vulkan context
* @param perFrameLayout Descriptor set layout for set 0 (per-frame UBO)
* @param assetManager Asset manager for loading textures
*/
bool initialize(pipeline::AssetManager* assetManager);
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout,
pipeline::AssetManager* assetManager);
/**
* Shutdown and cleanup GPU resources
*/
void shutdown();
/**
* Load terrain mesh and upload to GPU
* @param mesh Terrain mesh to load
* @param texturePaths Texture file paths from ADT
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
bool loadTerrain(const pipeline::TerrainMesh& mesh,
const std::vector<std::string>& texturePaths,
int tileX = -1, int tileY = -1);
/**
* Remove all chunks belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Upload pre-loaded BLP textures to the GL texture cache.
* Called before loadTerrain() so texture loading avoids file I/O.
*/
void uploadPreloadedTextures(const std::unordered_map<std::string, pipeline::BLPImage>& textures);
/**
* Render loaded terrain
* @param camera Camera for view/projection matrices
* Render terrain
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0)
* @param camera Camera for frustum culling
*/
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
/**
* Clear all loaded terrain
* Render terrain into shadow depth map (Phase 6 stub)
*/
void renderShadow(VkCommandBuffer cmd, const glm::vec3& shadowCenter, float halfExtent);
void clear();
/**
* Set lighting parameters
*/
void setLighting(const float lightDir[3], const float lightColor[3],
const float ambientColor[3]);
/**
* Set fog parameters
*/
void setFog(const float fogColor[3], float fogStart, float fogEnd);
/**
* Enable/disable wireframe rendering
*/
void setWireframe(bool enabled) { wireframe = enabled; }
/**
* Enable/disable frustum culling
*/
void setFrustumCulling(bool enabled) { frustumCullingEnabled = enabled; }
/**
* Enable/disable distance fog
*/
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
bool isFogEnabled() const { return fogEnabled; }
/**
* Render terrain geometry into shadow depth map
*/
void renderShadow(GLuint shaderProgram, const glm::vec3& shadowCenter, float halfExtent);
// Shadow mapping stubs (Phase 6)
void setShadowMap(VkDescriptorImageInfo /*depthInfo*/, const glm::mat4& /*lightSpaceMat*/) {}
void clearShadowMap() {}
/**
* Set shadow map for receiving shadows
*/
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpaceMat) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpaceMat; shadowEnabled = true;
}
void clearShadowMap() { shadowEnabled = false; }
/**
* Get statistics
*/
int getChunkCount() const { return static_cast<int>(chunks.size()); }
int getRenderedChunkCount() const { return renderedChunks; }
int getCulledChunkCount() const { return culledChunks; }
int getTriangleCount() const;
private:
/**
* Upload single chunk to GPU
*/
TerrainChunkGPU uploadChunk(const pipeline::ChunkMesh& chunk);
/**
* Load texture from asset manager
*/
GLuint loadTexture(const std::string& path);
/**
* Create alpha texture from raw alpha data
*/
GLuint createAlphaTexture(const std::vector<uint8_t>& alphaData);
/**
* Check if chunk is in view frustum
*/
VkTexture* loadTexture(const std::string& path);
VkTexture* createAlphaTexture(const std::vector<uint8_t>& alphaData);
bool isChunkVisible(const TerrainChunkGPU& chunk, const Frustum& frustum);
/**
* Calculate bounding sphere for chunk
*/
void calculateBoundingSphere(TerrainChunkGPU& chunk, const pipeline::ChunkMesh& meshChunk);
VkDescriptorSet allocateMaterialSet();
void writeMaterialDescriptors(VkDescriptorSet set, const TerrainChunkGPU& chunk);
void destroyChunkGPU(TerrainChunkGPU& chunk);
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
// Pipeline
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipeline wireframePipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE;
// Descriptor pool for material sets
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
// Loaded terrain chunks
std::vector<TerrainChunkGPU> chunks;
// Texture cache (path -> GL texture ID)
// Texture cache (path -> VkTexture)
struct TextureCacheEntry {
GLuint id = 0;
std::unique_ptr<VkTexture> texture;
size_t approxBytes = 0;
uint64_t lastUse = 0;
};
std::unordered_map<std::string, TextureCacheEntry> textureCache;
size_t textureCacheBytes_ = 0;
uint64_t textureCacheCounter_ = 0;
size_t textureCacheBudgetBytes_ = 4096ull * 1024 * 1024; // Default, overridden at init
size_t textureCacheBudgetBytes_ = 4096ull * 1024 * 1024;
// Lighting parameters
float lightDir[3] = {-0.5f, -1.0f, -0.5f};
float lightColor[3] = {1.0f, 1.0f, 0.9f};
float ambientColor[3] = {0.3f, 0.3f, 0.35f};
// Fog parameters
float fogColor[3] = {0.5f, 0.6f, 0.7f};
float fogStart = 400.0f;
float fogEnd = 800.0f;
// Fallback textures
std::unique_ptr<VkTexture> whiteTexture;
std::unique_ptr<VkTexture> opaqueAlphaTexture;
// Rendering state
bool wireframe = false;
@ -212,16 +165,6 @@ private:
bool fogEnabled = true;
int renderedChunks = 0;
int culledChunks = 0;
// Default white texture (fallback)
GLuint whiteTexture = 0;
// Opaque alpha fallback for missing/invalid layer alpha maps
GLuint opaqueAlphaTexture = 0;
// Shadow mapping (receiving)
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
bool shadowEnabled = false;
};
} // namespace rendering

View file

@ -0,0 +1,55 @@
#pragma once
#include "rendering/vk_utils.hpp"
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <cstdint>
#include <cstring>
namespace wowee {
namespace rendering {
class VkContext;
// RAII wrapper for a Vulkan buffer with VMA allocation.
// Supports vertex, index, uniform, and storage buffer usage.
class VkBuffer {
public:
VkBuffer() = default;
~VkBuffer();
VkBuffer(const VkBuffer&) = delete;
VkBuffer& operator=(const VkBuffer&) = delete;
VkBuffer(VkBuffer&& other) noexcept;
VkBuffer& operator=(VkBuffer&& other) noexcept;
// Create a GPU-local buffer and upload data via staging
bool uploadToGPU(VkContext& ctx, const void* data, VkDeviceSize size,
VkBufferUsageFlags usage);
// Create a host-visible buffer (for uniform/dynamic data updated each frame)
bool createMapped(VmaAllocator allocator, VkDeviceSize size,
VkBufferUsageFlags usage);
// Update mapped buffer contents (only valid for mapped buffers)
void updateMapped(const void* data, VkDeviceSize size, VkDeviceSize offset = 0);
void destroy();
::VkBuffer getBuffer() const { return buf_.buffer; }
VkDeviceSize getSize() const { return size_; }
void* getMappedData() const { return buf_.info.pMappedData; }
bool isValid() const { return buf_.buffer != VK_NULL_HANDLE; }
// Descriptor info for uniform/storage buffer binding
VkDescriptorBufferInfo descriptorInfo(VkDeviceSize offset = 0,
VkDeviceSize range = VK_WHOLE_SIZE) const;
private:
AllocatedBuffer buf_{};
VmaAllocator allocator_ = VK_NULL_HANDLE;
VkDeviceSize size_ = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,141 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <VkBootstrap.h>
#include <SDL2/SDL.h>
#include <vector>
#include <functional>
#include <cstdint>
namespace wowee {
namespace rendering {
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
struct FrameData {
VkCommandPool commandPool = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
VkSemaphore renderFinishedSemaphore = VK_NULL_HANDLE;
VkFence inFlightFence = VK_NULL_HANDLE;
};
class VkContext {
public:
VkContext() = default;
~VkContext();
VkContext(const VkContext&) = delete;
VkContext& operator=(const VkContext&) = delete;
bool initialize(SDL_Window* window);
void shutdown();
// Swapchain management
bool recreateSwapchain(int width, int height);
// Frame operations
VkCommandBuffer beginFrame(uint32_t& imageIndex);
void endFrame(VkCommandBuffer cmd, uint32_t imageIndex);
// Single-time command buffer helpers
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer cmd);
// Immediate submit for one-off GPU work (descriptor pool creation, etc.)
void immediateSubmit(std::function<void(VkCommandBuffer cmd)>&& function);
// Accessors
VkInstance getInstance() const { return instance; }
VkPhysicalDevice getPhysicalDevice() const { return physicalDevice; }
VkDevice getDevice() const { return device; }
VkQueue getGraphicsQueue() const { return graphicsQueue; }
uint32_t getGraphicsQueueFamily() const { return graphicsQueueFamily; }
VmaAllocator getAllocator() const { return allocator; }
VkSurfaceKHR getSurface() const { return surface; }
VkSwapchainKHR getSwapchain() const { return swapchain; }
VkFormat getSwapchainFormat() const { return swapchainFormat; }
VkExtent2D getSwapchainExtent() const { return swapchainExtent; }
const std::vector<VkImageView>& getSwapchainImageViews() const { return swapchainImageViews; }
uint32_t getSwapchainImageCount() const { return static_cast<uint32_t>(swapchainImages.size()); }
uint32_t getCurrentFrame() const { return currentFrame; }
const FrameData& getCurrentFrameData() const { return frames[currentFrame]; }
// For ImGui
VkRenderPass getImGuiRenderPass() const { return imguiRenderPass; }
VkDescriptorPool getImGuiDescriptorPool() const { return imguiDescriptorPool; }
const std::vector<VkFramebuffer>& getSwapchainFramebuffers() const { return swapchainFramebuffers; }
bool isSwapchainDirty() const { return swapchainDirty; }
private:
bool createInstance(SDL_Window* window);
bool createSurface(SDL_Window* window);
bool selectPhysicalDevice();
bool createLogicalDevice();
bool createAllocator();
bool createSwapchain(int width, int height);
void destroySwapchain();
bool createCommandPools();
bool createSyncObjects();
bool createImGuiResources();
void destroyImGuiResources();
// vk-bootstrap objects (kept alive for swapchain recreation etc.)
vkb::Instance vkbInstance_;
vkb::PhysicalDevice vkbPhysicalDevice_;
VkInstance instance = VK_NULL_HANDLE;
VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VmaAllocator allocator = VK_NULL_HANDLE;
VkQueue graphicsQueue = VK_NULL_HANDLE;
VkQueue presentQueue = VK_NULL_HANDLE;
uint32_t graphicsQueueFamily = 0;
uint32_t presentQueueFamily = 0;
// Swapchain
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkFormat swapchainFormat = VK_FORMAT_UNDEFINED;
VkExtent2D swapchainExtent = {0, 0};
std::vector<VkImage> swapchainImages;
std::vector<VkImageView> swapchainImageViews;
std::vector<VkFramebuffer> swapchainFramebuffers;
bool swapchainDirty = false;
// Per-frame resources
FrameData frames[MAX_FRAMES_IN_FLIGHT];
uint32_t currentFrame = 0;
// Immediate submit resources
VkCommandPool immCommandPool = VK_NULL_HANDLE;
VkFence immFence = VK_NULL_HANDLE;
// Depth buffer (shared across all framebuffers)
VkImage depthImage = VK_NULL_HANDLE;
VkImageView depthImageView = VK_NULL_HANDLE;
VmaAllocation depthAllocation = VK_NULL_HANDLE;
VkFormat depthFormat = VK_FORMAT_D32_SFLOAT;
bool createDepthBuffer();
void destroyDepthBuffer();
// ImGui resources
VkRenderPass imguiRenderPass = VK_NULL_HANDLE;
VkDescriptorPool imguiDescriptorPool = VK_NULL_HANDLE;
#ifndef NDEBUG
bool enableValidation = true;
#else
bool enableValidation = false;
#endif
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,29 @@
#pragma once
#include <vulkan/vulkan.h>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
// Must match the PerFrame UBO layout in all shaders (std140 alignment)
struct GPUPerFrameData {
glm::mat4 view;
glm::mat4 projection;
glm::mat4 lightSpaceMatrix;
glm::vec4 lightDir; // xyz = direction, w = unused
glm::vec4 lightColor; // xyz = color, w = unused
glm::vec4 ambientColor; // xyz = color, w = unused
glm::vec4 viewPos; // xyz = camera pos, w = unused
glm::vec4 fogColor; // xyz = color, w = unused
glm::vec4 fogParams; // x = fogStart, y = fogEnd, z = time, w = unused
glm::vec4 shadowParams; // x = enabled(0/1), y = strength, z = unused, w = unused
};
// Push constants for the model matrix (most common case)
struct GPUPushConstants {
glm::mat4 model;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,118 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vector>
#include <string>
namespace wowee {
namespace rendering {
// Builder pattern for VkGraphicsPipeline creation.
// Usage:
// auto pipeline = PipelineBuilder()
// .setShaders(vertStage, fragStage)
// .setVertexInput(bindings, attributes)
// .setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
// .setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT)
// .setDepthTest(true, true, VK_COMPARE_OP_LESS)
// .setColorBlendAttachment(PipelineBuilder::blendAlpha())
// .setLayout(pipelineLayout)
// .setRenderPass(renderPass)
// .build(device);
class PipelineBuilder {
public:
PipelineBuilder();
// Shader stages
PipelineBuilder& setShaders(VkPipelineShaderStageCreateInfo vert,
VkPipelineShaderStageCreateInfo frag);
// Vertex input
PipelineBuilder& setVertexInput(
const std::vector<VkVertexInputBindingDescription>& bindings,
const std::vector<VkVertexInputAttributeDescription>& attributes);
// No vertex input (fullscreen quad generated in vertex shader)
PipelineBuilder& setNoVertexInput();
// Input assembly
PipelineBuilder& setTopology(VkPrimitiveTopology topology,
VkBool32 primitiveRestart = VK_FALSE);
// Rasterization
PipelineBuilder& setRasterization(VkPolygonMode polygonMode,
VkCullModeFlags cullMode,
VkFrontFace frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE);
// Depth test/write
PipelineBuilder& setDepthTest(bool enable, bool writeEnable,
VkCompareOp compareOp = VK_COMPARE_OP_LESS);
// No depth test (default)
PipelineBuilder& setNoDepthTest();
// Depth bias (for shadow maps)
PipelineBuilder& setDepthBias(float constantFactor, float slopeFactor);
// Color blend attachment
PipelineBuilder& setColorBlendAttachment(
VkPipelineColorBlendAttachmentState blendState);
// No color attachment (depth-only pass)
PipelineBuilder& setNoColorAttachment();
// Multisampling
PipelineBuilder& setMultisample(VkSampleCountFlagBits samples);
// Pipeline layout
PipelineBuilder& setLayout(VkPipelineLayout layout);
// Render pass
PipelineBuilder& setRenderPass(VkRenderPass renderPass, uint32_t subpass = 0);
// Dynamic state
PipelineBuilder& setDynamicStates(const std::vector<VkDynamicState>& states);
// Build the pipeline
VkPipeline build(VkDevice device) const;
// Common blend states
static VkPipelineColorBlendAttachmentState blendDisabled();
static VkPipelineColorBlendAttachmentState blendAlpha();
static VkPipelineColorBlendAttachmentState blendAdditive();
private:
std::vector<VkPipelineShaderStageCreateInfo> shaderStages_;
std::vector<VkVertexInputBindingDescription> vertexBindings_;
std::vector<VkVertexInputAttributeDescription> vertexAttributes_;
VkPrimitiveTopology topology_ = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkBool32 primitiveRestart_ = VK_FALSE;
VkPolygonMode polygonMode_ = VK_POLYGON_MODE_FILL;
VkCullModeFlags cullMode_ = VK_CULL_MODE_NONE;
VkFrontFace frontFace_ = VK_FRONT_FACE_COUNTER_CLOCKWISE;
bool depthTestEnable_ = false;
bool depthWriteEnable_ = false;
VkCompareOp depthCompareOp_ = VK_COMPARE_OP_LESS;
bool depthBiasEnable_ = false;
float depthBiasConstant_ = 0.0f;
float depthBiasSlope_ = 0.0f;
VkSampleCountFlagBits msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
std::vector<VkPipelineColorBlendAttachmentState> colorBlendAttachments_;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkRenderPass renderPass_ = VK_NULL_HANDLE;
uint32_t subpass_ = 0;
std::vector<VkDynamicState> dynamicStates_;
};
// Helper to create a pipeline layout from descriptor set layouts and push constant ranges
VkPipelineLayout createPipelineLayout(VkDevice device,
const std::vector<VkDescriptorSetLayout>& setLayouts,
const std::vector<VkPushConstantRange>& pushConstants = {});
// Helper to create a descriptor set layout from bindings
VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
const std::vector<VkDescriptorSetLayoutBinding>& bindings);
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,71 @@
#pragma once
#include "rendering/vk_utils.hpp"
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <cstdint>
namespace wowee {
namespace rendering {
class VkContext;
/**
* Off-screen render target encapsulating VkRenderPass + VkFramebuffer + color VkImage.
* Used for minimap compositing, world map compositing, and other off-screen passes.
*/
class VkRenderTarget {
public:
VkRenderTarget() = default;
~VkRenderTarget();
VkRenderTarget(const VkRenderTarget&) = delete;
VkRenderTarget& operator=(const VkRenderTarget&) = delete;
/**
* Create the render target with given dimensions and format.
* Creates: color image, image view, sampler, render pass, framebuffer.
*/
bool create(VkContext& ctx, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM);
/**
* Destroy all Vulkan resources.
*/
void destroy(VkDevice device, VmaAllocator allocator);
/**
* Begin the off-screen render pass (clears to given color).
* Must be called outside any other active render pass.
*/
void beginPass(VkCommandBuffer cmd,
const VkClearColorValue& clear = {{0.0f, 0.0f, 0.0f, 1.0f}});
/**
* End the off-screen render pass.
* After this, the color image is in SHADER_READ_ONLY_OPTIMAL layout.
*/
void endPass(VkCommandBuffer cmd);
// Accessors
VkImageView getColorImageView() const { return colorImage_.imageView; }
VkSampler getSampler() const { return sampler_; }
VkRenderPass getRenderPass() const { return renderPass_; }
VkExtent2D getExtent() const { return { colorImage_.extent.width, colorImage_.extent.height }; }
VkFormat getFormat() const { return colorImage_.format; }
bool isValid() const { return framebuffer_ != VK_NULL_HANDLE; }
/**
* Descriptor info for binding the color attachment as a texture in a shader.
*/
VkDescriptorImageInfo descriptorInfo() const;
private:
AllocatedImage colorImage_{};
VkSampler sampler_ = VK_NULL_HANDLE;
VkRenderPass renderPass_ = VK_NULL_HANDLE;
VkFramebuffer framebuffer_ = VK_NULL_HANDLE;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,45 @@
#pragma once
#include <vulkan/vulkan.h>
#include <string>
#include <vector>
namespace wowee {
namespace rendering {
class VkShaderModule {
public:
VkShaderModule() = default;
~VkShaderModule();
VkShaderModule(const VkShaderModule&) = delete;
VkShaderModule& operator=(const VkShaderModule&) = delete;
VkShaderModule(VkShaderModule&& other) noexcept;
VkShaderModule& operator=(VkShaderModule&& other) noexcept;
// Load a SPIR-V file from disk
bool loadFromFile(VkDevice device, const std::string& path);
// Load from raw SPIR-V bytes
bool loadFromMemory(VkDevice device, const uint32_t* code, size_t sizeBytes);
void destroy();
::VkShaderModule getModule() const { return module_; }
bool isValid() const { return module_ != VK_NULL_HANDLE; }
// Create a VkPipelineShaderStageCreateInfo for this module
VkPipelineShaderStageCreateInfo stageInfo(VkShaderStageFlagBits stage,
const char* entryPoint = "main") const;
private:
VkDevice device_ = VK_NULL_HANDLE;
::VkShaderModule module_ = VK_NULL_HANDLE;
};
// Convenience: load a shader stage directly from a .spv file
VkPipelineShaderStageCreateInfo loadShaderStage(VkDevice device,
const std::string& path, VkShaderStageFlagBits stage);
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,78 @@
#pragma once
#include "rendering/vk_utils.hpp"
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <string>
#include <cstdint>
namespace wowee {
namespace rendering {
class VkContext;
class VkTexture {
public:
VkTexture() = default;
~VkTexture();
VkTexture(const VkTexture&) = delete;
VkTexture& operator=(const VkTexture&) = delete;
VkTexture(VkTexture&& other) noexcept;
VkTexture& operator=(VkTexture&& other) noexcept;
// Upload RGBA8 pixel data to GPU
bool upload(VkContext& ctx, const uint8_t* pixels, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, bool generateMips = true);
// Upload with pre-existing mip data (array of mip levels)
bool uploadMips(VkContext& ctx, const uint8_t* const* mipData, const uint32_t* mipSizes,
uint32_t mipCount, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM);
// Create a depth/stencil texture (no upload)
bool createDepth(VkContext& ctx, uint32_t width, uint32_t height,
VkFormat format = VK_FORMAT_D32_SFLOAT);
// Create sampler with specified filtering
bool createSampler(VkDevice device,
VkFilter minFilter = VK_FILTER_LINEAR,
VkFilter magFilter = VK_FILTER_LINEAR,
VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT,
float maxAnisotropy = 16.0f);
// Overload with separate S/T address modes
bool createSampler(VkDevice device,
VkFilter filter,
VkSamplerAddressMode addressModeU,
VkSamplerAddressMode addressModeV,
float maxAnisotropy = 16.0f);
// Create a comparison sampler (for shadow mapping)
bool createShadowSampler(VkDevice device);
void destroy(VkDevice device, VmaAllocator allocator);
VkImage getImage() const { return image_.image; }
VkImageView getImageView() const { return image_.imageView; }
VkSampler getSampler() const { return sampler_; }
uint32_t getWidth() const { return image_.extent.width; }
uint32_t getHeight() const { return image_.extent.height; }
VkFormat getFormat() const { return image_.format; }
uint32_t getMipLevels() const { return mipLevels_; }
bool isValid() const { return image_.image != VK_NULL_HANDLE; }
// Write descriptor info for binding
VkDescriptorImageInfo descriptorInfo(VkImageLayout layout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) const;
private:
void generateMipmaps(VkContext& ctx, VkFormat format, uint32_t width, uint32_t height);
AllocatedImage image_{};
VkSampler sampler_ = VK_NULL_HANDLE;
uint32_t mipLevels_ = 1;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,60 @@
#pragma once
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <cstdint>
namespace wowee {
namespace rendering {
class VkContext;
struct AllocatedBuffer {
VkBuffer buffer = VK_NULL_HANDLE;
VmaAllocation allocation = VK_NULL_HANDLE;
VmaAllocationInfo info{};
};
struct AllocatedImage {
VkImage image = VK_NULL_HANDLE;
VmaAllocation allocation = VK_NULL_HANDLE;
VkImageView imageView = VK_NULL_HANDLE;
VkExtent2D extent{};
VkFormat format = VK_FORMAT_UNDEFINED;
};
// Buffer creation
AllocatedBuffer createBuffer(VmaAllocator allocator, VkDeviceSize size,
VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage);
void destroyBuffer(VmaAllocator allocator, AllocatedBuffer& buffer);
// Image creation
AllocatedImage createImage(VkDevice device, VmaAllocator allocator,
uint32_t width, uint32_t height, VkFormat format,
VkImageUsageFlags usage, VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT,
uint32_t mipLevels = 1);
void destroyImage(VkDevice device, VmaAllocator allocator, AllocatedImage& image);
// Image layout transitions
void transitionImageLayout(VkCommandBuffer cmd, VkImage image,
VkImageLayout oldLayout, VkImageLayout newLayout,
VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
// Staging upload helper — copies CPU data to a GPU-local buffer
AllocatedBuffer uploadBuffer(VkContext& ctx, const void* data, VkDeviceSize size,
VkBufferUsageFlags usage);
// Check VkResult and log on failure
inline bool vkCheck(VkResult result, const char* msg) {
if (result != VK_SUCCESS) {
// Caller should log the message
return false;
}
return true;
}
} // namespace rendering
} // namespace wowee

View file

@ -4,6 +4,8 @@
#include <memory>
#include <optional>
#include <cstdint>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
namespace wowee {
@ -16,127 +18,77 @@ namespace pipeline {
namespace rendering {
class Camera;
class Shader;
class VkContext;
/**
* Water surface for a single map chunk
*/
struct WaterSurface {
glm::vec3 position; // World position
glm::vec3 origin; // Mesh origin (world)
glm::vec3 stepX; // Mesh X step vector in world space
glm::vec3 stepY; // Mesh Y step vector in world space
float minHeight; // Minimum water height
float maxHeight; // Maximum water height
uint16_t liquidType; // LiquidType.dbc ID (WotLK)
glm::vec3 position;
glm::vec3 origin;
glm::vec3 stepX;
glm::vec3 stepY;
float minHeight;
float maxHeight;
uint16_t liquidType;
// Owning tile coordinates (for per-tile removal)
int tileX = -1, tileY = -1;
// Owning WMO instance ID (for WMO liquid removal, 0 = terrain water)
uint32_t wmoId = 0;
// Water layer dimensions within chunk (0-7 offset, 1-8 size)
uint8_t xOffset = 0;
uint8_t yOffset = 0;
uint8_t width = 8; // Width in tiles (1-8)
uint8_t height = 8; // Height in tiles (1-8)
uint8_t width = 8;
uint8_t height = 8;
// Height map for water surface ((width+1) x (height+1) vertices)
std::vector<float> heights;
// Render mask (which tiles have water)
std::vector<uint8_t> mask;
// Render data
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
// Vulkan render data
::VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
::VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
int indexCount = 0;
// Per-surface material UBO
::VkBuffer materialUBO = VK_NULL_HANDLE;
VmaAllocation materialAlloc = VK_NULL_HANDLE;
// Material descriptor set (set 1)
VkDescriptorSet materialSet = VK_NULL_HANDLE;
bool hasHeightData() const { return !heights.empty(); }
};
/**
* Water renderer
*
* Renders water surfaces with transparency and animation.
* Supports multiple liquid types (water, ocean, magma, slime).
* Water renderer (Vulkan)
*/
class WaterRenderer {
public:
WaterRenderer();
~WaterRenderer();
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
void shutdown();
/**
* Load water surfaces from ADT terrain
* @param terrain The ADT terrain data
* @param append If true, add to existing water instead of replacing
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
void loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append = false,
int tileX = -1, int tileY = -1);
/**
* Load water surface from WMO liquid data
* @param liquid WMO liquid data from MLIQ chunk
* @param modelMatrix WMO instance model matrix for transforming to world space
* @param wmoId WMO instance ID for tracking ownership
*/
void loadFromWMO(const pipeline::WMOLiquid& liquid, const glm::mat4& modelMatrix, uint32_t wmoId);
/**
* Remove all water surfaces belonging to a specific WMO instance
* @param wmoId WMO instance ID
*/
void removeWMO(uint32_t wmoId);
/**
* Remove all water surfaces belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Clear all water surfaces
*/
void clear();
/**
* Render all water surfaces
*/
void render(const Camera& camera, float time);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time);
/**
* Enable/disable water rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Query the water height at a given world position.
* Returns the highest water surface height at that XY, or nullopt if no water.
*/
std::optional<float> getWaterHeightAt(float glX, float glY) const;
std::optional<uint16_t> getWaterTypeAt(float glX, float glY) const;
/**
* Get water surface count
*/
int getSurfaceCount() const { return static_cast<int>(surfaces.size()); }
/**
* Set fog parameters
*/
void setFog(const glm::vec3& color, float start, float end) {
fogColor = color; fogStart = start; fogEnd = end;
}
private:
void createWaterMesh(WaterSurface& surface);
void destroyWaterMesh(WaterSurface& surface);
@ -144,14 +96,20 @@ private:
glm::vec4 getLiquidColor(uint16_t liquidType) const;
float getLiquidAlpha(uint16_t liquidType) const;
std::unique_ptr<Shader> waterShader;
void updateMaterialUBO(WaterSurface& surface);
VkDescriptorSet allocateMaterialSet();
VkContext* vkCtx = nullptr;
// Pipeline
VkPipeline waterPipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE;
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_WATER_SETS = 2048;
std::vector<WaterSurface> surfaces;
bool renderingEnabled = true;
// Fog parameters
glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f);
float fogStart = 800.0f; // Match WMO renderer fog settings
float fogEnd = 1500.0f;
};
} // namespace rendering

View file

@ -1,15 +1,15 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
class VkContext;
/**
* @brief Weather particle system for rain and snow
@ -20,7 +20,7 @@ class Shader;
* - Particle recycling for efficiency
* - Camera-relative positioning (follows player)
* - Adjustable intensity (light, medium, heavy)
* - GPU instanced rendering
* - Vulkan point-sprite rendering
*/
class Weather {
public:
@ -35,9 +35,11 @@ public:
/**
* @brief Initialize weather system
* @param ctx Vulkan context
* @param perFrameLayout Descriptor set layout for the per-frame UBO (set 0)
* @return true if initialization succeeded
*/
bool initialize();
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
/**
* @brief Update weather particles
@ -48,9 +50,10 @@ public:
/**
* @brief Render weather particles
* @param camera Camera for rendering
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0, contains camera UBO)
*/
void render(const Camera& camera);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet);
/**
* @brief Set weather type
@ -75,6 +78,11 @@ public:
*/
int getParticleCount() const;
/**
* @brief Clean up Vulkan resources
*/
void shutdown();
private:
struct Particle {
glm::vec3 position;
@ -83,15 +91,20 @@ private:
float maxLifetime;
};
void cleanup();
void resetParticles(const Camera& camera);
void updateParticle(Particle& particle, const Camera& camera, float deltaTime);
glm::vec3 getRandomPosition(const glm::vec3& center) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0; // Instance buffer
std::unique_ptr<Shader> shader;
// Vulkan objects
VkContext* vkCtx = nullptr;
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
// Dynamic mapped buffer for particle positions (updated every frame)
::VkBuffer dynamicVB = VK_NULL_HANDLE;
VmaAllocation dynamicVBAlloc = VK_NULL_HANDLE;
VmaAllocationInfo dynamicVBAllocInfo{};
VkDeviceSize dynamicVBSize = 0;
// Particles
std::vector<Particle> particles;

View file

@ -1,6 +1,7 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
@ -19,12 +20,13 @@ namespace pipeline {
namespace rendering {
class Camera;
class Shader;
class Frustum;
class M2Renderer;
class VkContext;
class VkTexture;
/**
* WMO (World Model Object) Renderer
* WMO (World Model Object) Renderer (Vulkan)
*
* Renders buildings, dungeons, and large structures from WMO files.
* Features:
@ -32,7 +34,6 @@ class M2Renderer;
* - Batched rendering per group
* - Frustum culling
* - Portal visibility (future)
* - Dynamic lighting support (future)
*/
class WMORenderer {
public:
@ -40,10 +41,13 @@ public:
~WMORenderer();
/**
* Initialize renderer and create shaders
* Initialize renderer (Vulkan)
* @param ctx Vulkan context
* @param perFrameLayout Descriptor set layout for set 0 (per-frame UBO)
* @param assetManager Asset manager for loading textures (optional)
*/
bool initialize(pipeline::AssetManager* assetManager = nullptr);
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout,
pipeline::AssetManager* assetManager = nullptr);
/**
* Cleanup GPU resources
@ -132,12 +136,22 @@ public:
void clearInstances();
/**
* Render all WMO instances
* @param camera Camera for view/projection matrices
* @param view View matrix
* @param projection Projection matrix
* Render all WMO instances (Vulkan)
* @param cmd Command buffer to record into
* @param perFrameSet Per-frame descriptor set (set 0)
* @param camera Camera for frustum culling
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera);
/**
* Initialize shadow pipeline (Phase 7)
*/
bool initializeShadow(VkRenderPass shadowRenderPass);
/**
* Render depth-only for shadow casting
*/
void renderShadow(VkCommandBuffer cmd, const glm::mat4& lightSpaceMatrix);
/**
* Get number of loaded models
@ -204,32 +218,22 @@ public:
uint32_t getDistanceCulledGroups() const { return lastDistanceCulledGroups; }
/**
* Enable/disable GPU occlusion query culling
* Enable/disable GPU occlusion query culling (stubbed in Vulkan)
*/
void setOcclusionCulling(bool enabled) { occlusionCulling = enabled; }
bool isOcclusionCullingEnabled() const { return occlusionCulling; }
void setOcclusionCulling(bool /*enabled*/) { /* stubbed */ }
bool isOcclusionCullingEnabled() const { return false; }
/**
* Get number of groups culled by occlusion queries last frame
*/
uint32_t getOcclusionCulledGroups() const { return lastOcclusionCulledGroups; }
uint32_t getOcclusionCulledGroups() const { return 0; }
void setFog(const glm::vec3& color, float start, float end) {
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDir[3], const float lightColor[3],
const float ambientColor[3]);
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
void clearShadowMap() { shadowEnabled = false; }
/**
* Render depth-only for shadow casting (reuses VAOs)
*/
void renderShadow(const glm::mat4& lightView, const glm::mat4& lightProj, Shader& shadowShader);
// Lighting/fog/shadow are now in the per-frame UBO; these are no-ops for API compat
void setFog(const glm::vec3& /*color*/, float /*start*/, float /*end*/) {}
void setLighting(const float /*lightDir*/[3], const float /*lightColor*/[3],
const float /*ambientColor*/[3]) {}
void setShadowMap(uint32_t /*depthTex*/, const glm::mat4& /*lightSpace*/) {}
void clearShadowMap() {}
/**
* Get floor height at a GL position via ray-triangle intersection.
@ -297,13 +301,23 @@ public:
void precomputeFloorCache();
private:
// WMO material UBO — matches WMOMaterial in wmo.frag.glsl
struct WMOMaterialUBO {
int32_t hasTexture;
int32_t alphaTest;
int32_t unlit;
int32_t isInterior;
float specularIntensity;
};
/**
* WMO group GPU resources
*/
struct GroupResources {
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
::VkBuffer vertexBuffer = VK_NULL_HANDLE;
VmaAllocation vertexAlloc = VK_NULL_HANDLE;
::VkBuffer indexBuffer = VK_NULL_HANDLE;
VmaAllocation indexAlloc = VK_NULL_HANDLE;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
glm::vec3 boundingBoxMin;
@ -322,13 +336,17 @@ private:
// Pre-merged batches for efficient rendering (computed at load time)
struct MergedBatch {
GLuint texId;
bool hasTexture;
bool alphaTest;
VkTexture* texture = nullptr; // from cache, NOT owned
VkDescriptorSet materialSet = VK_NULL_HANDLE; // set 1
::VkBuffer materialUBO = VK_NULL_HANDLE;
VmaAllocation materialUBOAlloc = VK_NULL_HANDLE;
bool hasTexture = false;
bool alphaTest = false;
bool unlit = false;
uint32_t blendMode = 0;
std::vector<GLsizei> counts;
std::vector<const void*> offsets;
bool isTransparent = false; // blendMode >= 2
// For multi-draw: store index ranges
struct DrawRange { uint32_t firstIndex; uint32_t indexCount; };
std::vector<DrawRange> draws;
};
std::vector<MergedBatch> mergedBatches;
@ -401,7 +419,7 @@ private:
std::vector<DoodadTemplate> doodadTemplates;
// Texture handles for this model (indexed by texture path order)
std::vector<GLuint> textures;
std::vector<VkTexture*> textures; // non-owning, from cache
// Material texture indices (materialId -> texture index)
std::vector<uint32_t> materialTextureIndices;
@ -458,13 +476,6 @@ private:
*/
bool createGroupResources(const pipeline::WMOGroup& group, GroupResources& resources, uint32_t groupFlags = 0);
/**
* Render a single group
*/
void renderGroup(const GroupResources& group, const ModelData& model,
const glm::mat4& modelMatrix,
const glm::mat4& view, const glm::mat4& projection);
/**
* Check if group is visible in frustum
*/
@ -479,11 +490,6 @@ private:
/**
* Get visible groups via portal traversal
* @param model The WMO model data
* @param cameraLocalPos Camera position in model space
* @param frustum Frustum for portal visibility testing
* @param modelMatrix Transform for world-space frustum test
* @param outVisibleGroups Output set of visible group indices
*/
void getVisibleGroupsViaPortals(const ModelData& model,
const glm::vec3& cameraLocalPos,
@ -502,23 +508,17 @@ private:
/**
* Load a texture from path
*/
GLuint loadTexture(const std::string& path);
VkTexture* loadTexture(const std::string& path);
/**
* Initialize occlusion query resources (bbox VAO, shader)
* Allocate a material descriptor set from the pool
*/
void initOcclusionResources();
VkDescriptorSet allocateMaterialSet();
/**
* Run occlusion query pre-pass for an instance
* Destroy GPU resources for a single group
*/
void runOcclusionQueries(const WMOInstance& instance, const ModelData& model,
const glm::mat4& view, const glm::mat4& projection);
/**
* Check if a group passed occlusion test (uses previous frame results)
*/
bool isGroupOccluded(uint32_t instanceId, uint32_t groupIndex) const;
void destroyGroupGPU(GroupResources& group);
struct GridCell {
int x;
@ -541,8 +541,8 @@ private:
void rebuildSpatialIndex();
void gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax, std::vector<size_t>& outIndices) const;
// Shader
std::unique_ptr<Shader> shader;
// Vulkan context
VkContext* vkCtx_ = nullptr;
// Asset manager for loading textures
pipeline::AssetManager* assetManager = nullptr;
@ -553,9 +553,31 @@ private:
// Current map name for zone-specific floor cache
std::string mapName_;
// Texture cache (path -> texture ID)
// Vulkan pipelines
VkPipeline opaquePipeline_ = VK_NULL_HANDLE;
VkPipeline transparentPipeline_ = VK_NULL_HANDLE;
VkPipeline wireframePipeline_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
// Shadow rendering (Phase 7)
VkPipeline shadowPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout shadowPipelineLayout_ = VK_NULL_HANDLE;
VkDescriptorSetLayout shadowParamsLayout_ = VK_NULL_HANDLE;
VkDescriptorPool shadowParamsPool_ = VK_NULL_HANDLE;
VkDescriptorSet shadowParamsSet_ = VK_NULL_HANDLE;
::VkBuffer shadowParamsUBO_ = VK_NULL_HANDLE;
VmaAllocation shadowParamsAlloc_ = VK_NULL_HANDLE;
// Descriptor set layouts
VkDescriptorSetLayout materialSetLayout_ = VK_NULL_HANDLE;
// Descriptor pool for material sets
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
// Texture cache (path -> VkTexture)
struct TextureCacheEntry {
GLuint id = 0;
std::unique_ptr<VkTexture> texture;
size_t approxBytes = 0;
uint64_t lastUse = 0;
};
@ -565,7 +587,7 @@ private:
size_t textureCacheBudgetBytes_ = 2048ull * 1024 * 1024; // Default, overridden at init
// Default white texture
GLuint whiteTexture = 0;
std::unique_ptr<VkTexture> whiteTexture_;
// Loaded models (modelId -> ModelData)
std::unordered_map<uint32_t, ModelData> loadedModels;
@ -581,38 +603,11 @@ private:
bool frustumCulling = true;
bool portalCulling = false; // Disabled by default - needs debugging
bool distanceCulling = false; // Disabled - causes ground to disappear
bool occlusionCulling = false; // GPU occlusion queries - disabled, adds overhead
float maxGroupDistance = 500.0f;
float maxGroupDistanceSq = 250000.0f; // maxGroupDistance^2
uint32_t lastDrawCalls = 0;
mutable uint32_t lastPortalCulledGroups = 0;
mutable uint32_t lastDistanceCulledGroups = 0;
mutable uint32_t lastOcclusionCulledGroups = 0;
// Occlusion query resources
GLuint bboxVao = 0;
GLuint bboxVbo = 0;
std::unique_ptr<Shader> occlusionShader;
// Query objects per (instance, group) - reused each frame
// Key: (instanceId << 16) | groupIndex
mutable std::unordered_map<uint32_t, GLuint> occlusionQueries;
// Results from previous frame (1 frame latency to avoid GPU stalls)
mutable std::unordered_map<uint32_t, bool> occlusionResults;
// Fog parameters
glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f);
float fogStart = 3000.0f; // Increased to allow clearer visibility at distance
float fogEnd = 4000.0f; // Increased to match extended view distance
// Lighting parameters
float lightDir[3] = {-0.3f, -0.7f, -0.6f};
float lightColor[3] = {1.5f, 1.4f, 1.3f};
float ambientColor[3] = {0.55f, 0.55f, 0.6f};
// Shadow mapping
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
bool shadowEnabled = false;
// Optional query-space culling for collision/raycast hot paths.
bool collisionFocusEnabled = false;
@ -636,7 +631,6 @@ private:
std::vector<uint32_t> visibleGroups; // group indices that passed culling
uint32_t portalCulled = 0;
uint32_t distanceCulled = 0;
uint32_t occlusionCulled = 0;
};
// Collision query profiling (per frame).

View file

@ -1,9 +1,11 @@
#pragma once
#include <GL/glew.h>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -11,7 +13,9 @@ namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class Shader;
class VkContext;
class VkTexture;
class VkRenderTarget;
struct WorldMapZone {
uint32_t wmaID = 0;
@ -22,8 +26,8 @@ struct WorldMapZone {
uint32_t parentWorldMapID = 0;
uint32_t exploreFlag = 0;
// Per-zone cached textures
GLuint tileTextures[12] = {};
// Per-zone cached textures (owned by WorldMap::zoneTextures)
VkTexture* tileTextures[12] = {};
bool tilesLoaded = false;
};
@ -32,8 +36,15 @@ public:
WorldMap();
~WorldMap();
void initialize(pipeline::AssetManager* assetManager);
bool initialize(VkContext* ctx, pipeline::AssetManager* assetManager);
void shutdown();
/// Off-screen composite pass — call BEFORE the main render pass begins.
void compositePass(VkCommandBuffer cmd);
/// ImGui overlay — call INSIDE the main render pass (during ImGui frame).
void render(const glm::vec3& playerRenderPos, int screenWidth, int screenHeight);
void setMapName(const std::string& name);
void setServerExplorationMask(const std::vector<uint32_t>& masks, bool hasData);
bool isOpen() const { return open; }
@ -42,9 +53,6 @@ public:
private:
enum class ViewLevel { WORLD, CONTINENT, ZONE };
void createFBO();
void createTileShader();
void createQuad();
void enterWorldView();
void loadZonesFromDBC();
int findBestContinentForPlayer(const glm::vec3& playerRenderPos) const;
@ -53,15 +61,15 @@ private:
bool getContinentProjectionBounds(int contIdx, float& left, float& right,
float& top, float& bottom) const;
void loadZoneTextures(int zoneIdx);
void compositeZone(int zoneIdx);
void requestComposite(int zoneIdx);
void renderImGuiOverlay(const glm::vec3& playerRenderPos, int screenWidth, int screenHeight);
void updateExploration(const glm::vec3& playerRenderPos);
void zoomIn(const glm::vec3& playerRenderPos);
void zoomOut();
// World pos → map UV using a specific zone's bounds
glm::vec2 renderPosToMapUV(const glm::vec3& renderPos, int zoneIdx) const;
void destroyZoneTextures();
VkContext* vkCtx = nullptr;
pipeline::AssetManager* assetManager = nullptr;
bool initialized = false;
bool open = false;
@ -70,28 +78,45 @@ private:
// All zones for current map
std::vector<WorldMapZone> zones;
int continentIdx = -1; // index of AreaID=0 entry in zones
int currentIdx = -1; // currently displayed zone index
int continentIdx = -1;
int currentIdx = -1;
ViewLevel viewLevel = ViewLevel::CONTINENT;
int compositedIdx = -1; // which zone is currently composited in FBO
int compositedIdx = -1;
int pendingCompositeIdx = -1;
// FBO for composited map (4x3 tiles = 1024x768)
// FBO replacement (4x3 tiles = 1024x768)
static constexpr int GRID_COLS = 4;
static constexpr int GRID_ROWS = 3;
static constexpr int TILE_PX = 256;
static constexpr int FBO_W = GRID_COLS * TILE_PX; // 1024
static constexpr int FBO_H = GRID_ROWS * TILE_PX; // 768
static constexpr int FBO_W = GRID_COLS * TILE_PX;
static constexpr int FBO_H = GRID_ROWS * TILE_PX;
GLuint fbo = 0;
GLuint fboTexture = 0;
std::unique_ptr<Shader> tileShader;
GLuint tileQuadVAO = 0;
GLuint tileQuadVBO = 0;
std::unique_ptr<VkRenderTarget> compositeTarget;
// Quad vertex buffer (pos2 + uv2)
::VkBuffer quadVB = VK_NULL_HANDLE;
VmaAllocation quadVBAlloc = VK_NULL_HANDLE;
// Descriptor resources
VkDescriptorSetLayout samplerSetLayout = VK_NULL_HANDLE;
VkDescriptorPool descPool = VK_NULL_HANDLE;
static constexpr uint32_t MAX_DESC_SETS = 32;
// Tile composite pipeline
VkPipeline tilePipeline = VK_NULL_HANDLE;
VkPipelineLayout tilePipelineLayout = VK_NULL_HANDLE;
VkDescriptorSet tileDescSets[2][12] = {}; // [frameInFlight][tileSlot]
// ImGui display descriptor set (points to composite render target)
VkDescriptorSet imguiDisplaySet = VK_NULL_HANDLE;
// Texture storage (owns all VkTexture objects for zone tiles)
std::vector<std::unique_ptr<VkTexture>> zoneTextures;
// Exploration / fog of war
std::vector<uint32_t> serverExplorationMask;
bool hasServerExplorationMask = false;
std::unordered_set<int> exploredZones; // zone indices the player has visited
std::unordered_set<int> exploredZones;
};
} // namespace rendering