diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index d4d9a05b..7216fa4e 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -208,7 +208,7 @@ 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 destroyModelGPU(M2ModelGPU& gpuModel, bool defer = false); void destroyInstanceBones(CharacterInstance& inst, bool defer = false); // Keyframe interpolation helpers diff --git a/include/rendering/post_process_pipeline.hpp b/include/rendering/post_process_pipeline.hpp index 765dfff4..74c4c9b7 100644 --- a/include/rendering/post_process_pipeline.hpp +++ b/include/rendering/post_process_pipeline.hpp @@ -182,7 +182,9 @@ private: VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; VkDescriptorSetLayout descSetLayout = VK_NULL_HANDLE; VkDescriptorPool descPool = VK_NULL_HANDLE; - VkDescriptorSet descSet = VK_NULL_HANDLE; + // Per-frame descriptor sets to avoid race with in-flight command buffers + static constexpr uint32_t DESC_SET_COUNT = 2; // matches MAX_FRAMES_IN_FLIGHT + VkDescriptorSet descSet[DESC_SET_COUNT] = {}; }; FXAAState fxaa_; bool initFXAAResources(); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 90957951..753b2d73 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -472,11 +472,31 @@ void CharacterRenderer::createFallbackTextures(VkDevice device) { } } -void CharacterRenderer::destroyModelGPU(M2ModelGPU& gpuModel) { +void CharacterRenderer::destroyModelGPU(M2ModelGPU& gpuModel, bool defer) { if (!vkCtx_) return; VmaAllocator alloc = vkCtx_->getAllocator(); - if (gpuModel.vertexBuffer) { vmaDestroyBuffer(alloc, gpuModel.vertexBuffer, gpuModel.vertexAlloc); gpuModel.vertexBuffer = VK_NULL_HANDLE; } - if (gpuModel.indexBuffer) { vmaDestroyBuffer(alloc, gpuModel.indexBuffer, gpuModel.indexAlloc); gpuModel.indexBuffer = VK_NULL_HANDLE; } + + // Snapshot raw handles and null the model fields immediately + ::VkBuffer vb = gpuModel.vertexBuffer; + VmaAllocation vbAlloc = gpuModel.vertexAlloc; + ::VkBuffer ib = gpuModel.indexBuffer; + VmaAllocation ibAlloc = gpuModel.indexAlloc; + gpuModel.vertexBuffer = VK_NULL_HANDLE; + gpuModel.vertexAlloc = VK_NULL_HANDLE; + gpuModel.indexBuffer = VK_NULL_HANDLE; + gpuModel.indexAlloc = VK_NULL_HANDLE; + + if (!defer) { + // Safe after vkDeviceWaitIdle (shutdown / clear paths) + if (vb) vmaDestroyBuffer(alloc, vb, vbAlloc); + if (ib) vmaDestroyBuffer(alloc, ib, ibAlloc); + } else if (vb || ib) { + // Streaming path: in-flight command buffers may still reference these + vkCtx_->deferAfterFrameFence([alloc, vb, vbAlloc, ib, ibAlloc]() { + if (vb) vmaDestroyBuffer(alloc, vb, vbAlloc); + if (ib) vmaDestroyBuffer(alloc, ib, ibAlloc); + }); + } } void CharacterRenderer::destroyInstanceBones(CharacterInstance& inst, bool defer) { @@ -1412,7 +1432,7 @@ bool CharacterRenderer::loadModel(const pipeline::M2Model& model, uint32_t id) { if (models.find(id) != models.end()) { core::Logger::getInstance().warning("Model ID ", id, " already loaded, replacing"); - destroyModelGPU(models[id]); + destroyModelGPU(models[id], /*defer=*/true); } M2ModelGPU gpuModel; diff --git a/src/rendering/post_process_pipeline.cpp b/src/rendering/post_process_pipeline.cpp index c4522b9d..ad627c7c 100644 --- a/src/rendering/post_process_pipeline.cpp +++ b/src/rendering/post_process_pipeline.cpp @@ -198,7 +198,9 @@ bool PostProcessPipeline::executePostProcessing(VkCommandBuffer cmd, uint32_t im // FSR3+FXAA combined: re-point FXAA's descriptor to the FSR3 temporal output // so renderFXAAPass() applies spatial AA on the temporally-stabilized frame. // This must happen outside the render pass (descriptor updates are CPU-side). - if (fxaa_.enabled && fxaa_.descSet && fxaa_.sceneSampler) { + // Use per-frame descriptor set to avoid race with in-flight command buffers. + uint32_t fxaaFrameIdx = vkCtx_->getCurrentFrame(); + if (fxaa_.enabled && fxaa_.descSet[fxaaFrameIdx] && fxaa_.sceneSampler) { VkImageView fsr3OutputView = VK_NULL_HANDLE; if (fsr2_.useAmdBackend) { if (fsr2_.amdFsr3FramegenRuntimeActive && fsr2_.framegenOutput.image) @@ -215,7 +217,7 @@ bool PostProcessPipeline::executePostProcessing(VkCommandBuffer cmd, uint32_t im imgInfo.sampler = fxaa_.sceneSampler; VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = fxaa_.descSet; + write.dstSet = fxaa_.descSet[fxaaFrameIdx]; write.dstBinding = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -257,23 +259,23 @@ bool PostProcessPipeline::executePostProcessing(VkCommandBuffer cmd, uint32_t im // of RCAS sharpening. FXAA descriptor is temporarily pointed to the FSR3 // history buffer (which is already in SHADER_READ_ONLY_OPTIMAL). This gives // FSR3 temporal stability + FXAA spatial edge smoothing ("ultra quality native"). - if (fxaa_.enabled && fxaa_.pipeline && fxaa_.descSet) { + if (fxaa_.enabled && fxaa_.pipeline && fxaa_.descSet[fxaaFrameIdx]) { renderFXAAPass(); } else { // Draw RCAS sharpening from accumulated history buffer renderFSR2Sharpen(); } - // Restore FXAA descriptor to its normal scene color source so standalone - // FXAA frames are not affected by the FSR3 history pointer set above. - if (fxaa_.enabled && fxaa_.descSet && fxaa_.sceneSampler && fxaa_.sceneColor.imageView) { + // Restore this frame's FXAA descriptor to its normal scene color source + // so standalone FXAA frames are not affected by the FSR3 history pointer. + if (fxaa_.enabled && fxaa_.descSet[fxaaFrameIdx] && fxaa_.sceneSampler && fxaa_.sceneColor.imageView) { VkDescriptorImageInfo restoreInfo{}; restoreInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; restoreInfo.imageView = fxaa_.sceneColor.imageView; restoreInfo.sampler = fxaa_.sceneSampler; VkWriteDescriptorSet restoreWrite{}; restoreWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - restoreWrite.dstSet = fxaa_.descSet; + restoreWrite.dstSet = fxaa_.descSet[fxaaFrameIdx]; restoreWrite.dstBinding = 0; restoreWrite.descriptorCount = 1; restoreWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; @@ -1754,36 +1756,41 @@ bool PostProcessPipeline::initFXAAResources() { layoutInfo.pBindings = &binding; vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &fxaa_.descSetLayout); + constexpr uint32_t setCount = FXAAState::DESC_SET_COUNT; VkDescriptorPoolSize poolSize{}; poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - poolSize.descriptorCount = 1; + poolSize.descriptorCount = setCount; VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - poolInfo.maxSets = 1; + poolInfo.maxSets = setCount; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; vkCreateDescriptorPool(device, &poolInfo, nullptr, &fxaa_.descPool); + VkDescriptorSetLayout layouts[setCount]; + for (uint32_t i = 0; i < setCount; i++) layouts[i] = fxaa_.descSetLayout; VkDescriptorSetAllocateInfo dsAllocInfo{}; dsAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; dsAllocInfo.descriptorPool = fxaa_.descPool; - dsAllocInfo.descriptorSetCount = 1; - dsAllocInfo.pSetLayouts = &fxaa_.descSetLayout; - vkAllocateDescriptorSets(device, &dsAllocInfo, &fxaa_.descSet); + dsAllocInfo.descriptorSetCount = setCount; + dsAllocInfo.pSetLayouts = layouts; + vkAllocateDescriptorSets(device, &dsAllocInfo, fxaa_.descSet); - // Bind the resolved 1x sceneColor + // Bind the resolved 1x sceneColor to all per-frame sets VkDescriptorImageInfo imgInfo{}; imgInfo.sampler = fxaa_.sceneSampler; imgInfo.imageView = fxaa_.sceneColor.imageView; imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - VkWriteDescriptorSet write{}; - write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = fxaa_.descSet; - write.dstBinding = 0; - write.descriptorCount = 1; - write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - write.pImageInfo = &imgInfo; - vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); + for (uint32_t i = 0; i < setCount; i++) { + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = fxaa_.descSet[i]; + write.dstBinding = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &imgInfo; + vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); + } // Pipeline layout — push constant holds vec4(rcpFrame.xy, sharpness, pad) VkPushConstantRange pc{}; @@ -1843,7 +1850,7 @@ void PostProcessPipeline::destroyFXAAResources() { if (fxaa_.pipeline) { vkDestroyPipeline(device, fxaa_.pipeline, nullptr); fxaa_.pipeline = VK_NULL_HANDLE; } if (fxaa_.pipelineLayout) { vkDestroyPipelineLayout(device, fxaa_.pipelineLayout, nullptr); fxaa_.pipelineLayout = VK_NULL_HANDLE; } - if (fxaa_.descPool) { vkDestroyDescriptorPool(device, fxaa_.descPool, nullptr); fxaa_.descPool = VK_NULL_HANDLE; fxaa_.descSet = VK_NULL_HANDLE; } + if (fxaa_.descPool) { vkDestroyDescriptorPool(device, fxaa_.descPool, nullptr); fxaa_.descPool = VK_NULL_HANDLE; for (auto& s : fxaa_.descSet) s = VK_NULL_HANDLE; } if (fxaa_.descSetLayout) { vkDestroyDescriptorSetLayout(device, fxaa_.descSetLayout, nullptr); fxaa_.descSetLayout = VK_NULL_HANDLE; } if (fxaa_.sceneFramebuffer) { vkDestroyFramebuffer(device, fxaa_.sceneFramebuffer, nullptr); fxaa_.sceneFramebuffer = VK_NULL_HANDLE; } fxaa_.sceneSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache @@ -1857,9 +1864,10 @@ void PostProcessPipeline::renderFXAAPass() { if (!fxaa_.pipeline || currentCmd_ == VK_NULL_HANDLE) return; VkExtent2D ext = vkCtx_->getSwapchainExtent(); + uint32_t fi = vkCtx_->getCurrentFrame(); vkCmdBindPipeline(currentCmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, fxaa_.pipeline); vkCmdBindDescriptorSets(currentCmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, - fxaa_.pipelineLayout, 0, 1, &fxaa_.descSet, 0, nullptr); + fxaa_.pipelineLayout, 0, 1, &fxaa_.descSet[fi], 0, nullptr); // Pass rcpFrame + sharpness + effect flag (vec4, 16 bytes). // When FSR2/FSR3 is active alongside FXAA, forward FSR2's sharpness so the diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index 6b5be908..c60b2307 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -1920,27 +1920,25 @@ void WaterRenderer::endReflectionPass(VkCommandBuffer cmd) { vkCmdEndRenderPass(cmd); reflectionColorLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - // Update all per-frame scene descriptor sets with the freshly rendered reflection texture - if (reflectionColorView && reflectionSampler) { - VkDescriptorImageInfo reflInfo{}; - reflInfo.sampler = reflectionSampler; - reflInfo.imageView = reflectionColorView; - reflInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + // Update only the current frame's scene descriptor set with the reflection texture. + // Updating all frames would race with in-flight command buffers that have the + // other frame's descriptor set bound. + if (reflectionColorView && reflectionSampler && vkCtx) { + uint32_t fi = vkCtx->getCurrentFrame() % SCENE_HISTORY_FRAMES; + if (sceneHistory[fi].sceneSet) { + VkDescriptorImageInfo reflInfo{}; + reflInfo.sampler = reflectionSampler; + reflInfo.imageView = reflectionColorView; + reflInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - std::vector writes; - for (uint32_t f = 0; f < SCENE_HISTORY_FRAMES; f++) { - if (!sceneHistory[f].sceneSet) continue; VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = sceneHistory[f].sceneSet; + write.dstSet = sceneHistory[fi].sceneSet; write.dstBinding = 2; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &reflInfo; - writes.push_back(write); - } - if (!writes.empty()) { - vkUpdateDescriptorSets(vkCtx->getDevice(), static_cast(writes.size()), writes.data(), 0, nullptr); + vkUpdateDescriptorSets(vkCtx->getDevice(), 1, &write, 0, nullptr); } } }