mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-10 06:43:51 +00:00
Implement GPU-driven Hierarchical-Z occlusion culling for M2 doodads using a depth pyramid built from the previous frame's depth buffer. The cull shader projects bounding spheres via prevViewProj (temporal reprojection) and samples the HiZ pyramid to reject hidden objects before the main render pass. Key implementation details: - Separate early compute submission (beginSingleTimeCommands + fence wait) eliminates 2-frame visibility staleness - Conservative safeguards prevent false culls: screen-edge guard, full VP row-vector AABB projection (Cauchy-Schwarz), 50% sphere inflation, depth bias, mip+1, min screen size threshold, camera motion dampening (auto-disable on fast rotations), and per-instance previouslyVisible flag tracking - Graceful fallback to frustum-only culling if HiZ init fails Fix dark WMO interiors by gating shadow map sampling on isInterior==0 in the WMO fragment shader. Interior groups (flag 0x2000) now rely solely on pre-baked MOCV vertex-color lighting + MOHD ambient color. Disable interiorDarken globally (was incorrectly darkening outdoor M2s when camera was inside a WMO). Use isInsideInteriorWMO() instead of isInsideWMO() for correct indoor detection. New files: - hiz_system.hpp/cpp: pyramid image management, compute pipeline, descriptors, mip-chain build dispatch, resize handling - hiz_build.comp.glsl: MAX-depth 2x2 reduction compute shader - m2_cull_hiz.comp.glsl: frustum + HiZ occlusion cull compute shader - test_indoor_shadows.cpp: 14 unit tests for shadow/interior contracts Modified: - CullUniformsGPU expanded 128->272 bytes (HiZ params, viewProj, prevViewProj) - Depth buffer images gain VK_IMAGE_USAGE_SAMPLED_BIT for HiZ reads - wmo.frag.glsl: interior branch before unlit, shadow skip for 0x2000 - Render graph: hiz_build + compute_cull disabled (run in early compute) - .gitignore: ignore compiled .spv binaries - MEGA_BONE_MAX_INSTANCES: 2048 -> 4096 Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
150 lines
5 KiB
C++
150 lines
5 KiB
C++
#pragma once
|
|
|
|
#include <vulkan/vulkan.h>
|
|
#include <vk_mem_alloc.h>
|
|
#include <glm/glm.hpp>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
class VkContext;
|
|
|
|
/**
|
|
* Hierarchical-Z (HiZ) depth pyramid for GPU occlusion culling (Phase 6.3 Option B).
|
|
*
|
|
* Builds a min-depth mip chain from the previous frame's depth buffer each frame.
|
|
* The M2 cull compute shader samples this pyramid to reject objects hidden behind
|
|
* geometry, complementing the existing frustum culling.
|
|
*
|
|
* Lifecycle:
|
|
* initialize() — create pyramid image, sampler, compute pipeline, descriptors
|
|
* buildPyramid() — dispatch compute to reduce depth → mip chain (once per frame)
|
|
* shutdown() — destroy all Vulkan resources
|
|
*
|
|
* The pyramid is double-buffered (per frame-in-flight) so builds and reads
|
|
* never race across concurrent GPU submissions.
|
|
*/
|
|
class HiZSystem {
|
|
public:
|
|
HiZSystem() = default;
|
|
~HiZSystem();
|
|
|
|
HiZSystem(const HiZSystem&) = delete;
|
|
HiZSystem& operator=(const HiZSystem&) = delete;
|
|
|
|
/**
|
|
* Create all Vulkan resources.
|
|
* @param ctx Vulkan context (device, allocator, etc.)
|
|
* @param width Full-resolution depth buffer width
|
|
* @param height Full-resolution depth buffer height
|
|
* @return true on success
|
|
*/
|
|
[[nodiscard]] bool initialize(VkContext* ctx, uint32_t width, uint32_t height);
|
|
|
|
/**
|
|
* Release all Vulkan resources.
|
|
*/
|
|
void shutdown();
|
|
|
|
/**
|
|
* Rebuild the pyramid after a swapchain resize.
|
|
* Safe to call repeatedly — destroys old resources first.
|
|
*/
|
|
[[nodiscard]] bool resize(uint32_t width, uint32_t height);
|
|
|
|
/**
|
|
* Dispatch compute shader to build the HiZ pyramid from the current depth buffer.
|
|
* Must be called AFTER the main scene pass has finished writing to the depth buffer.
|
|
*
|
|
* @param cmd Active command buffer (in recording state)
|
|
* @param frameIndex Current frame-in-flight index (0 or 1)
|
|
* @param depthImage Source depth image (VK_FORMAT_D32_SFLOAT)
|
|
*/
|
|
void buildPyramid(VkCommandBuffer cmd, uint32_t frameIndex, VkImage depthImage);
|
|
|
|
/**
|
|
* @return Descriptor set layout for the HiZ pyramid sampler (set 1 for m2_cull_hiz).
|
|
*/
|
|
VkDescriptorSetLayout getDescriptorSetLayout() const { return hizSetLayout_; }
|
|
|
|
/**
|
|
* @return Descriptor set for the given frame (sampler2D of the HiZ pyramid).
|
|
* Bind as set 1 in the M2 HiZ cull pipeline.
|
|
*/
|
|
VkDescriptorSet getDescriptorSet(uint32_t frameIndex) const { return hizDescSet_[frameIndex]; }
|
|
|
|
/**
|
|
* @return true if HiZ system is initialized and ready.
|
|
*/
|
|
bool isReady() const { return ready_; }
|
|
|
|
/**
|
|
* @return Number of mip levels in the pyramid.
|
|
*/
|
|
uint32_t getMipLevels() const { return mipLevels_; }
|
|
|
|
/**
|
|
* @return Pyramid base resolution (mip 0).
|
|
*/
|
|
uint32_t getPyramidWidth() const { return pyramidWidth_; }
|
|
uint32_t getPyramidHeight() const { return pyramidHeight_; }
|
|
|
|
private:
|
|
bool createPyramidImage();
|
|
void destroyPyramidImage();
|
|
bool createComputePipeline();
|
|
void destroyComputePipeline();
|
|
bool createDescriptors();
|
|
void destroyDescriptors();
|
|
|
|
VkContext* ctx_ = nullptr;
|
|
bool ready_ = false;
|
|
|
|
// Pyramid dimensions (mip 0 = half of full-res depth)
|
|
uint32_t fullWidth_ = 0;
|
|
uint32_t fullHeight_ = 0;
|
|
uint32_t pyramidWidth_ = 0;
|
|
uint32_t pyramidHeight_ = 0;
|
|
uint32_t mipLevels_ = 0;
|
|
|
|
static constexpr uint32_t MAX_FRAMES = 2;
|
|
|
|
// Per-frame HiZ pyramid images (R32_SFLOAT, full mip chain)
|
|
VkImage pyramidImage_[MAX_FRAMES] = {};
|
|
VmaAllocation pyramidAlloc_[MAX_FRAMES] = {};
|
|
VkImageView pyramidViewAll_[MAX_FRAMES] = {}; // View of all mip levels (for sampling)
|
|
std::vector<VkImageView> pyramidMipViews_[MAX_FRAMES]; // Per-mip views (for storage image writes)
|
|
|
|
// Depth input — image view for sampling the depth buffer as a texture
|
|
VkImageView depthSamplerView_[MAX_FRAMES] = {};
|
|
|
|
// Sampler for depth reads (nearest, clamp-to-edge)
|
|
VkSampler depthSampler_ = VK_NULL_HANDLE;
|
|
|
|
// Compute pipeline for building the pyramid
|
|
VkPipeline buildPipeline_ = VK_NULL_HANDLE;
|
|
VkPipelineLayout buildPipelineLayout_ = VK_NULL_HANDLE;
|
|
|
|
// Descriptor set layout for build pipeline (set 0: src sampler + dst storage image)
|
|
VkDescriptorSetLayout buildSetLayout_ = VK_NULL_HANDLE;
|
|
VkDescriptorPool buildDescPool_ = VK_NULL_HANDLE;
|
|
// Per-frame, per-mip descriptor sets for pyramid build
|
|
std::vector<VkDescriptorSet> buildDescSets_[MAX_FRAMES];
|
|
|
|
// HiZ sampling descriptor: exposed to M2 cull shader (set 1: combined image sampler)
|
|
VkDescriptorSetLayout hizSetLayout_ = VK_NULL_HANDLE;
|
|
VkDescriptorPool hizDescPool_ = VK_NULL_HANDLE;
|
|
VkDescriptorSet hizDescSet_[MAX_FRAMES] = {};
|
|
|
|
// Push constant for build shader
|
|
struct HiZBuildPushConstants {
|
|
int32_t dstWidth;
|
|
int32_t dstHeight;
|
|
int32_t mipLevel;
|
|
};
|
|
};
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|