mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Fix VK_ERROR_DEVICE_LOST crash by allocating per-frame scene history images (color + depth) instead of a single shared image that raced between frames in flight. Water refraction can now be toggled via Settings > Video > Water Refraction. Without refraction: richer blue base colors, animated caustic shimmer, and normal-based color shifts give the water visible life. With refraction: clean screen-space refraction with Beer-Lambert absorption. Disabling clears scene history to black for immediate fallback.
216 lines
8 KiB
C++
216 lines
8 KiB
C++
#pragma once
|
|
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <vulkan/vulkan.h>
|
|
#include <vk_mem_alloc.h>
|
|
#include <glm/glm.hpp>
|
|
|
|
namespace wowee {
|
|
namespace pipeline {
|
|
struct ADTTerrain;
|
|
struct LiquidData;
|
|
struct WMOLiquid;
|
|
}
|
|
|
|
namespace rendering {
|
|
|
|
class Camera;
|
|
class VkContext;
|
|
|
|
/**
|
|
* Water surface for a single map chunk
|
|
*/
|
|
struct WaterSurface {
|
|
glm::vec3 position;
|
|
glm::vec3 origin;
|
|
glm::vec3 stepX;
|
|
glm::vec3 stepY;
|
|
float minHeight;
|
|
float maxHeight;
|
|
uint16_t liquidType;
|
|
|
|
int tileX = -1, tileY = -1;
|
|
uint32_t wmoId = 0;
|
|
|
|
uint8_t xOffset = 0;
|
|
uint8_t yOffset = 0;
|
|
uint8_t width = 8;
|
|
uint8_t height = 8;
|
|
|
|
std::vector<float> heights;
|
|
std::vector<uint8_t> mask;
|
|
|
|
// 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 (Vulkan) with planar reflections, Gerstner waves,
|
|
* GGX specular, shoreline foam, and subsurface scattering.
|
|
*/
|
|
class WaterRenderer {
|
|
public:
|
|
WaterRenderer();
|
|
~WaterRenderer();
|
|
|
|
bool initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout);
|
|
void shutdown();
|
|
|
|
void loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append = false,
|
|
int tileX = -1, int tileY = -1);
|
|
|
|
void loadFromWMO(const pipeline::WMOLiquid& liquid, const glm::mat4& modelMatrix, uint32_t wmoId);
|
|
void removeWMO(uint32_t wmoId);
|
|
void removeTile(int tileX, int tileY);
|
|
void clear();
|
|
|
|
void recreatePipelines();
|
|
|
|
// Separate 1x pass for MSAA mode — water rendered after MSAA resolve
|
|
bool createWater1xPass(VkFormat colorFormat, VkFormat depthFormat);
|
|
void createWater1xFramebuffers(const std::vector<VkImageView>& swapViews,
|
|
VkImageView depthView, VkExtent2D extent);
|
|
void destroyWater1xResources();
|
|
bool beginWater1xPass(VkCommandBuffer cmd, uint32_t imageIndex, VkExtent2D extent);
|
|
void endWater1xPass(VkCommandBuffer cmd);
|
|
bool hasWater1xPass() const { return water1xRenderPass != VK_NULL_HANDLE; }
|
|
VkRenderPass getWater1xRenderPass() const { return water1xRenderPass; }
|
|
|
|
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time, bool use1x = false, uint32_t frameIndex = 0);
|
|
void captureSceneHistory(VkCommandBuffer cmd,
|
|
VkImage srcColorImage,
|
|
VkImage srcDepthImage,
|
|
VkExtent2D srcExtent,
|
|
bool srcDepthIsMsaa,
|
|
uint32_t frameIndex = 0);
|
|
|
|
// --- Planar reflection pass ---
|
|
// Call sequence: beginReflectionPass → [render scene] → endReflectionPass
|
|
bool beginReflectionPass(VkCommandBuffer cmd);
|
|
void endReflectionPass(VkCommandBuffer cmd);
|
|
|
|
// Get the dominant water height near a position (for reflection plane)
|
|
std::optional<float> getDominantWaterHeight(const glm::vec3& cameraPos) const;
|
|
|
|
// Compute reflected view matrix for a given water height
|
|
static glm::mat4 computeReflectedView(const Camera& camera, float waterHeight);
|
|
// Compute oblique clip projection to clip below-water geometry in reflection
|
|
static glm::mat4 computeObliqueProjection(const glm::mat4& proj, const glm::mat4& view, float waterHeight);
|
|
|
|
// Update the reflection UBO with reflected viewProj matrix
|
|
void updateReflectionUBO(const glm::mat4& reflViewProj);
|
|
|
|
VkRenderPass getReflectionRenderPass() const { return reflectionRenderPass; }
|
|
VkExtent2D getReflectionExtent() const { return {REFLECTION_WIDTH, REFLECTION_HEIGHT}; }
|
|
bool hasReflectionPass() const { return reflectionRenderPass != VK_NULL_HANDLE; }
|
|
bool hasSurfaces() const { return !surfaces.empty(); }
|
|
|
|
void setEnabled(bool enabled) { renderingEnabled = enabled; }
|
|
bool isEnabled() const { return renderingEnabled; }
|
|
|
|
void setRefractionEnabled(bool enabled);
|
|
bool isRefractionEnabled() const { return refractionEnabled; }
|
|
|
|
std::optional<float> getWaterHeightAt(float glX, float glY) const;
|
|
/// Like getWaterHeightAt but only returns water surfaces whose height is
|
|
/// close to the query Z (within maxAbove units above). Avoids false
|
|
/// underwater detection from elevated WMO water far above the camera.
|
|
std::optional<float> getNearestWaterHeightAt(float glX, float glY, float queryZ, float maxAbove = 15.0f) const;
|
|
std::optional<uint16_t> getWaterTypeAt(float glX, float glY) const;
|
|
bool isWmoWaterAt(float glX, float glY) const;
|
|
|
|
int getSurfaceCount() const { return static_cast<int>(surfaces.size()); }
|
|
|
|
private:
|
|
void createWaterMesh(WaterSurface& surface);
|
|
void destroyWaterMesh(WaterSurface& surface);
|
|
|
|
glm::vec4 getLiquidColor(uint16_t liquidType) const;
|
|
float getLiquidAlpha(uint16_t liquidType) const;
|
|
|
|
void updateMaterialUBO(WaterSurface& surface);
|
|
VkDescriptorSet allocateMaterialSet();
|
|
void createSceneHistoryResources(VkExtent2D extent, VkFormat colorFormat, VkFormat depthFormat);
|
|
void destroySceneHistoryResources();
|
|
|
|
// Reflection pass resources
|
|
void createReflectionResources();
|
|
void destroyReflectionResources();
|
|
|
|
VkContext* vkCtx = nullptr;
|
|
|
|
// Pipeline
|
|
VkPipeline waterPipeline = VK_NULL_HANDLE;
|
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
|
VkDescriptorSetLayout materialSetLayout = VK_NULL_HANDLE;
|
|
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
|
VkDescriptorSetLayout sceneSetLayout = VK_NULL_HANDLE;
|
|
VkDescriptorPool sceneDescPool = VK_NULL_HANDLE;
|
|
static constexpr uint32_t MAX_WATER_SETS = 16384;
|
|
|
|
VkSampler sceneColorSampler = VK_NULL_HANDLE;
|
|
VkSampler sceneDepthSampler = VK_NULL_HANDLE;
|
|
// Per-frame scene history to avoid race between frames in flight
|
|
static constexpr uint32_t SCENE_HISTORY_FRAMES = 2;
|
|
struct PerFrameSceneHistory {
|
|
VkImage colorImage = VK_NULL_HANDLE;
|
|
VmaAllocation colorAlloc = VK_NULL_HANDLE;
|
|
VkImageView colorView = VK_NULL_HANDLE;
|
|
VkImage depthImage = VK_NULL_HANDLE;
|
|
VmaAllocation depthAlloc = VK_NULL_HANDLE;
|
|
VkImageView depthView = VK_NULL_HANDLE;
|
|
VkDescriptorSet sceneSet = VK_NULL_HANDLE;
|
|
};
|
|
PerFrameSceneHistory sceneHistory[SCENE_HISTORY_FRAMES];
|
|
VkExtent2D sceneHistoryExtent = {0, 0};
|
|
bool sceneHistoryReady = false;
|
|
mutable uint32_t renderDiagCounter_ = 0;
|
|
|
|
// Planar reflection resources
|
|
static constexpr uint32_t REFLECTION_WIDTH = 512;
|
|
static constexpr uint32_t REFLECTION_HEIGHT = 512;
|
|
VkRenderPass reflectionRenderPass = VK_NULL_HANDLE;
|
|
VkFramebuffer reflectionFramebuffer = VK_NULL_HANDLE;
|
|
VkImage reflectionColorImage = VK_NULL_HANDLE;
|
|
VmaAllocation reflectionColorAlloc = VK_NULL_HANDLE;
|
|
VkImageView reflectionColorView = VK_NULL_HANDLE;
|
|
VkImage reflectionDepthImage = VK_NULL_HANDLE;
|
|
VmaAllocation reflectionDepthAlloc = VK_NULL_HANDLE;
|
|
VkImageView reflectionDepthView = VK_NULL_HANDLE;
|
|
VkSampler reflectionSampler = VK_NULL_HANDLE;
|
|
VkImageLayout reflectionColorLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
// Reflection UBO (mat4 reflViewProj)
|
|
::VkBuffer reflectionUBO = VK_NULL_HANDLE;
|
|
VmaAllocation reflectionUBOAlloc = VK_NULL_HANDLE;
|
|
void* reflectionUBOMapped = nullptr;
|
|
|
|
// Separate 1x water pass (used when MSAA is active)
|
|
VkRenderPass water1xRenderPass = VK_NULL_HANDLE;
|
|
VkPipeline water1xPipeline = VK_NULL_HANDLE;
|
|
std::vector<VkFramebuffer> water1xFramebuffers;
|
|
|
|
std::vector<WaterSurface> surfaces;
|
|
bool renderingEnabled = true;
|
|
bool refractionEnabled = false;
|
|
};
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|