mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 16:03:52 +00:00
Fix shadow flashing: per-frame shadow depth images and framebuffers
Single shadow depth image shared across MAX_FRAMES=2 in-flight GPU frames caused a race: frame N's main pass reads shadow map while frame N+1's shadow pass clears and writes it, producing visible flashing standing still and while moving. Fix: give each in-flight frame its own VkImage, VmaAllocation, VkImageView, and VkFramebuffer for the shadow depth attachment. renderShadowPass() now indexes all shadow resources by getCurrentFrame(), and layout transitions track per-frame state in shadowDepthLayout_[frame]. Cleanup loops over MAX_FRAMES=2. Descriptor sets already written per-frame; updated shadow image view binding to use the matching per-frame view.
This commit is contained in:
parent
d5de031c23
commit
edd7e5e591
2 changed files with 53 additions and 39 deletions
|
|
@ -241,13 +241,17 @@ private:
|
||||||
std::unique_ptr<game::ZoneManager> zoneManager;
|
std::unique_ptr<game::ZoneManager> zoneManager;
|
||||||
// Shadow mapping (Vulkan)
|
// Shadow mapping (Vulkan)
|
||||||
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
static constexpr uint32_t SHADOW_MAP_SIZE = 4096;
|
||||||
VkImage shadowDepthImage = VK_NULL_HANDLE;
|
// Per-frame shadow resources: each in-flight frame has its own depth image and
|
||||||
VmaAllocation shadowDepthAlloc = VK_NULL_HANDLE;
|
// framebuffer so that frame N's shadow read and frame N+1's shadow write don't
|
||||||
VkImageView shadowDepthView = VK_NULL_HANDLE;
|
// race on the same image across concurrent GPU submissions.
|
||||||
|
// Array size must match MAX_FRAMES (= 2, defined in the private section below).
|
||||||
|
VkImage shadowDepthImage[2] = {};
|
||||||
|
VmaAllocation shadowDepthAlloc[2] = {};
|
||||||
|
VkImageView shadowDepthView[2] = {};
|
||||||
VkSampler shadowSampler = VK_NULL_HANDLE;
|
VkSampler shadowSampler = VK_NULL_HANDLE;
|
||||||
VkRenderPass shadowRenderPass = VK_NULL_HANDLE;
|
VkRenderPass shadowRenderPass = VK_NULL_HANDLE;
|
||||||
VkFramebuffer shadowFramebuffer = VK_NULL_HANDLE;
|
VkFramebuffer shadowFramebuffer[2] = {};
|
||||||
VkImageLayout shadowDepthLayout_ = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout shadowDepthLayout_[2] = {};
|
||||||
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
|
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
|
||||||
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
||||||
bool shadowCenterInitialized = false;
|
bool shadowCenterInitialized = false;
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,9 @@ Renderer::~Renderer() = default;
|
||||||
bool Renderer::createPerFrameResources() {
|
bool Renderer::createPerFrameResources() {
|
||||||
VkDevice device = vkCtx->getDevice();
|
VkDevice device = vkCtx->getDevice();
|
||||||
|
|
||||||
// --- Create shadow depth image ---
|
// --- Create per-frame shadow depth images (one per in-flight frame) ---
|
||||||
|
// Each frame slot has its own depth image so that frame N's shadow read and
|
||||||
|
// frame N+1's shadow write cannot race on the same image.
|
||||||
VkImageCreateInfo imgCI{};
|
VkImageCreateInfo imgCI{};
|
||||||
imgCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
imgCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||||
imgCI.imageType = VK_IMAGE_TYPE_2D;
|
imgCI.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
|
@ -301,26 +303,30 @@ bool Renderer::createPerFrameResources() {
|
||||||
imgCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
imgCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||||
VmaAllocationCreateInfo imgAllocCI{};
|
VmaAllocationCreateInfo imgAllocCI{};
|
||||||
imgAllocCI.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
imgAllocCI.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
||||||
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
||||||
if (vmaCreateImage(vkCtx->getAllocator(), &imgCI, &imgAllocCI,
|
if (vmaCreateImage(vkCtx->getAllocator(), &imgCI, &imgAllocCI,
|
||||||
&shadowDepthImage, &shadowDepthAlloc, nullptr) != VK_SUCCESS) {
|
&shadowDepthImage[i], &shadowDepthAlloc[i], nullptr) != VK_SUCCESS) {
|
||||||
LOG_ERROR("Failed to create shadow depth image");
|
LOG_ERROR("Failed to create shadow depth image [", i, "]");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
shadowDepthLayout_ = VK_IMAGE_LAYOUT_UNDEFINED;
|
shadowDepthLayout_[i] = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Create shadow depth image view ---
|
// --- Create per-frame shadow depth image views ---
|
||||||
VkImageViewCreateInfo viewCI{};
|
VkImageViewCreateInfo viewCI{};
|
||||||
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
viewCI.image = shadowDepthImage;
|
|
||||||
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
viewCI.format = VK_FORMAT_D32_SFLOAT;
|
viewCI.format = VK_FORMAT_D32_SFLOAT;
|
||||||
viewCI.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
viewCI.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
||||||
if (vkCreateImageView(device, &viewCI, nullptr, &shadowDepthView) != VK_SUCCESS) {
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
||||||
LOG_ERROR("Failed to create shadow depth image view");
|
viewCI.image = shadowDepthImage[i];
|
||||||
|
if (vkCreateImageView(device, &viewCI, nullptr, &shadowDepthView[i]) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("Failed to create shadow depth image view [", i, "]");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Create shadow sampler ---
|
// --- Create shadow sampler (shared — read-only, no per-frame needed) ---
|
||||||
VkSamplerCreateInfo sampCI{};
|
VkSamplerCreateInfo sampCI{};
|
||||||
sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||||
sampCI.magFilter = VK_FILTER_LINEAR;
|
sampCI.magFilter = VK_FILTER_LINEAR;
|
||||||
|
|
@ -377,19 +383,21 @@ bool Renderer::createPerFrameResources() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Create shadow framebuffer ---
|
// --- Create per-frame shadow framebuffers ---
|
||||||
VkFramebufferCreateInfo fbCI{};
|
VkFramebufferCreateInfo fbCI{};
|
||||||
fbCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
fbCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
fbCI.renderPass = shadowRenderPass;
|
fbCI.renderPass = shadowRenderPass;
|
||||||
fbCI.attachmentCount = 1;
|
fbCI.attachmentCount = 1;
|
||||||
fbCI.pAttachments = &shadowDepthView;
|
|
||||||
fbCI.width = SHADOW_MAP_SIZE;
|
fbCI.width = SHADOW_MAP_SIZE;
|
||||||
fbCI.height = SHADOW_MAP_SIZE;
|
fbCI.height = SHADOW_MAP_SIZE;
|
||||||
fbCI.layers = 1;
|
fbCI.layers = 1;
|
||||||
if (vkCreateFramebuffer(device, &fbCI, nullptr, &shadowFramebuffer) != VK_SUCCESS) {
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
||||||
LOG_ERROR("Failed to create shadow framebuffer");
|
fbCI.pAttachments = &shadowDepthView[i];
|
||||||
|
if (vkCreateFramebuffer(device, &fbCI, nullptr, &shadowFramebuffer[i]) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("Failed to create shadow framebuffer [", i, "]");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Create descriptor set layout for set 0 (per-frame UBO + shadow sampler) ---
|
// --- Create descriptor set layout for set 0 (per-frame UBO + shadow sampler) ---
|
||||||
VkDescriptorSetLayoutBinding bindings[2]{};
|
VkDescriptorSetLayoutBinding bindings[2]{};
|
||||||
|
|
@ -470,7 +478,7 @@ bool Renderer::createPerFrameResources() {
|
||||||
|
|
||||||
VkDescriptorImageInfo shadowImgInfo{};
|
VkDescriptorImageInfo shadowImgInfo{};
|
||||||
shadowImgInfo.sampler = shadowSampler;
|
shadowImgInfo.sampler = shadowSampler;
|
||||||
shadowImgInfo.imageView = shadowDepthView;
|
shadowImgInfo.imageView = shadowDepthView[i];
|
||||||
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
VkWriteDescriptorSet writes[2]{};
|
VkWriteDescriptorSet writes[2]{};
|
||||||
|
|
@ -527,7 +535,7 @@ bool Renderer::createPerFrameResources() {
|
||||||
|
|
||||||
VkDescriptorImageInfo shadowImgInfo{};
|
VkDescriptorImageInfo shadowImgInfo{};
|
||||||
shadowImgInfo.sampler = shadowSampler;
|
shadowImgInfo.sampler = shadowSampler;
|
||||||
shadowImgInfo.imageView = shadowDepthView;
|
shadowImgInfo.imageView = shadowDepthView[0]; // reflection uses frame 0 shadow view
|
||||||
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
VkWriteDescriptorSet writes[2]{};
|
VkWriteDescriptorSet writes[2]{};
|
||||||
|
|
@ -576,13 +584,15 @@ void Renderer::destroyPerFrameResources() {
|
||||||
perFrameSetLayout = VK_NULL_HANDLE;
|
perFrameSetLayout = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy shadow resources
|
// Destroy per-frame shadow resources
|
||||||
if (shadowFramebuffer) { vkDestroyFramebuffer(device, shadowFramebuffer, nullptr); shadowFramebuffer = VK_NULL_HANDLE; }
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
||||||
|
if (shadowFramebuffer[i]) { vkDestroyFramebuffer(device, shadowFramebuffer[i], nullptr); shadowFramebuffer[i] = VK_NULL_HANDLE; }
|
||||||
|
if (shadowDepthView[i]) { vkDestroyImageView(device, shadowDepthView[i], nullptr); shadowDepthView[i] = VK_NULL_HANDLE; }
|
||||||
|
if (shadowDepthImage[i]) { vmaDestroyImage(vkCtx->getAllocator(), shadowDepthImage[i], shadowDepthAlloc[i]); shadowDepthImage[i] = VK_NULL_HANDLE; shadowDepthAlloc[i] = VK_NULL_HANDLE; }
|
||||||
|
shadowDepthLayout_[i] = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
if (shadowRenderPass) { vkDestroyRenderPass(device, shadowRenderPass, nullptr); shadowRenderPass = VK_NULL_HANDLE; }
|
if (shadowRenderPass) { vkDestroyRenderPass(device, shadowRenderPass, nullptr); shadowRenderPass = VK_NULL_HANDLE; }
|
||||||
if (shadowDepthView) { vkDestroyImageView(device, shadowDepthView, nullptr); shadowDepthView = VK_NULL_HANDLE; }
|
|
||||||
if (shadowDepthImage) { vmaDestroyImage(vkCtx->getAllocator(), shadowDepthImage, shadowDepthAlloc); shadowDepthImage = VK_NULL_HANDLE; shadowDepthAlloc = VK_NULL_HANDLE; }
|
|
||||||
if (shadowSampler) { vkDestroySampler(device, shadowSampler, nullptr); shadowSampler = VK_NULL_HANDLE; }
|
if (shadowSampler) { vkDestroySampler(device, shadowSampler, nullptr); shadowSampler = VK_NULL_HANDLE; }
|
||||||
shadowDepthLayout_ = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::updatePerFrameUBO() {
|
void Renderer::updatePerFrameUBO() {
|
||||||
|
|
@ -1088,7 +1098,7 @@ void Renderer::beginFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow pre-pass (before main render pass)
|
// Shadow pre-pass (before main render pass)
|
||||||
if (shadowsEnabled && shadowDepthImage != VK_NULL_HANDLE) {
|
if (shadowsEnabled && shadowDepthImage[0] != VK_NULL_HANDLE) {
|
||||||
renderShadowPass();
|
renderShadowPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5669,7 +5679,7 @@ void Renderer::renderReflectionPass() {
|
||||||
void Renderer::renderShadowPass() {
|
void Renderer::renderShadowPass() {
|
||||||
static const bool skipShadows = (std::getenv("WOWEE_SKIP_SHADOWS") != nullptr);
|
static const bool skipShadows = (std::getenv("WOWEE_SKIP_SHADOWS") != nullptr);
|
||||||
if (skipShadows) return;
|
if (skipShadows) return;
|
||||||
if (!shadowsEnabled || shadowDepthImage == VK_NULL_HANDLE) return;
|
if (!shadowsEnabled || shadowDepthImage[0] == VK_NULL_HANDLE) return;
|
||||||
if (currentCmd == VK_NULL_HANDLE) return;
|
if (currentCmd == VK_NULL_HANDLE) return;
|
||||||
|
|
||||||
// Shadows render every frame — throttling causes visible flicker on player/NPCs
|
// Shadows render every frame — throttling causes visible flicker on player/NPCs
|
||||||
|
|
@ -5686,21 +5696,21 @@ void Renderer::renderShadowPass() {
|
||||||
ubo->shadowParams.y = 0.8f;
|
ubo->shadowParams.y = 0.8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Barrier 1: transition shadow map into writable depth layout.
|
// Barrier 1: transition this frame's shadow map into writable depth layout.
|
||||||
VkImageMemoryBarrier b1{};
|
VkImageMemoryBarrier b1{};
|
||||||
b1.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
b1.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
b1.oldLayout = shadowDepthLayout_;
|
b1.oldLayout = shadowDepthLayout_[frame];
|
||||||
b1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
b1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
b1.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
b1.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
b1.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
b1.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
b1.srcAccessMask = (shadowDepthLayout_ == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
b1.srcAccessMask = (shadowDepthLayout_[frame] == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||||
? VK_ACCESS_SHADER_READ_BIT
|
? VK_ACCESS_SHADER_READ_BIT
|
||||||
: 0;
|
: 0;
|
||||||
b1.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
b1.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
b1.image = shadowDepthImage;
|
b1.image = shadowDepthImage[frame];
|
||||||
b1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
b1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
||||||
VkPipelineStageFlags srcStage = (shadowDepthLayout_ == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
VkPipelineStageFlags srcStage = (shadowDepthLayout_[frame] == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||||
? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
||||||
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||||
vkCmdPipelineBarrier(currentCmd,
|
vkCmdPipelineBarrier(currentCmd,
|
||||||
|
|
@ -5711,7 +5721,7 @@ void Renderer::renderShadowPass() {
|
||||||
VkRenderPassBeginInfo rpInfo{};
|
VkRenderPassBeginInfo rpInfo{};
|
||||||
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||||
rpInfo.renderPass = shadowRenderPass;
|
rpInfo.renderPass = shadowRenderPass;
|
||||||
rpInfo.framebuffer = shadowFramebuffer;
|
rpInfo.framebuffer = shadowFramebuffer[frame];
|
||||||
rpInfo.renderArea = {{0, 0}, {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE}};
|
rpInfo.renderArea = {{0, 0}, {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE}};
|
||||||
VkClearValue clear{};
|
VkClearValue clear{};
|
||||||
clear.depthStencil = {1.0f, 0};
|
clear.depthStencil = {1.0f, 0};
|
||||||
|
|
@ -5750,12 +5760,12 @@ void Renderer::renderShadowPass() {
|
||||||
b2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
b2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
b2.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
b2.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
b2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
b2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
b2.image = shadowDepthImage;
|
b2.image = shadowDepthImage[frame];
|
||||||
b2.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
b2.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
||||||
vkCmdPipelineBarrier(currentCmd,
|
vkCmdPipelineBarrier(currentCmd,
|
||||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
0, 0, nullptr, 0, nullptr, 1, &b2);
|
0, 0, nullptr, 0, nullptr, 1, &b2);
|
||||||
shadowDepthLayout_ = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
shadowDepthLayout_[frame] = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rendering
|
} // namespace rendering
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue