Latte: Add support for VK_EXT_attachment_feedback_loop

Requires both VK_EXT_attachment_feedback_loop and VK_EXT_attachment_feedback_loop_dynamic_state to be provided by the driver. Otherwise it will fall back to the old code. Main benefit is for improving performance as it makes barriers cheaper and doesn't force us to unnecessarily interrupt renderpasses
This commit is contained in:
Exzap
2026-06-23 14:02:18 +02:00
parent e142fadfcd
commit 62ac48f0e9
12 changed files with 225 additions and 76 deletions
@@ -126,7 +126,10 @@ void CachedFBOVk::InitDynamicRenderingData()
continue; continue;
} }
else else
{
m_vkColorAttachments[i].imageLayout = static_cast<LatteTextureVk*>(buffer.texture->baseTexture)->GetDefaultLayout();
m_vkColorAttachments[i].imageView = cbView->m_textureImageView; m_vkColorAttachments[i].imageView = cbView->m_textureImageView;
}
} }
m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments; m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments;
@@ -167,6 +170,9 @@ void CachedFBOVk::InitDynamicRenderingData()
// setup depth and stencil attachment // setup depth and stencil attachment
if (depthStencilView) if (depthStencilView)
{ {
auto depthTexVk = static_cast<LatteTextureVk*>(depthBuffer.texture->baseTexture);
m_vkDepthAttachment.imageLayout = depthTexVk->GetDefaultLayout();
m_vkStencilAttachment.imageLayout = depthTexVk->GetDefaultLayout();
m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView; m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView;
m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
@@ -189,41 +195,46 @@ void CachedFBOVk::InitDynamicRenderingData()
m_vkRenderingInfo.layerCount = 1; m_vkRenderingInfo.layerCount = 1;
} }
static uint32 s_selfDependencyCheckIndex = 1;
uint32 s_currentCollisionCheckIndex = 1; CachedFBOVk::RendertargetSelfDependencyMask CachedFBOVk::CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const
bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const
{ {
s_currentCollisionCheckIndex++; s_selfDependencyCheckIndex++;
const uint32 curColIndex = s_currentCollisionCheckIndex; const uint32 curColIndex = s_selfDependencyCheckIndex;
for (auto& itr : m_referencedTextures) for (auto& colorAttachment : colorBuffer)
{ {
LatteTextureVk* vkTex = (LatteTextureVk*)itr; if (colorAttachment.texture)
vkTex->m_collisionCheckIndex = curColIndex;
}
if (vsDS)
{
for (auto& itr : vsDS->list_fboCandidates)
{ {
if (itr->m_collisionCheckIndex == curColIndex) LatteTextureVk* vkTex = static_cast<LatteTextureVk*>(colorAttachment.texture->baseTexture);
return true; vkTex->m_selfDependencyCheckIndex = curColIndex;
vkTex->m_selfDependencyCheckAspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
} }
} }
if (gsDS) if (depthBuffer.texture)
{ {
for (auto& itr : gsDS->list_fboCandidates) LatteTextureVk* vkTex = static_cast<LatteTextureVk*>(depthBuffer.texture->baseTexture);
{ vkTex->m_selfDependencyCheckIndex = curColIndex;
if (itr->m_collisionCheckIndex == curColIndex) vkTex->m_selfDependencyCheckAspectMask = depthBuffer.hasStencil ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT): VK_IMAGE_ASPECT_DEPTH_BIT;
return true;
}
} }
if (psDS)
auto getSelfDependencyMask = [curColIndex](VkDescriptorSetInfo* ds) -> VkImageAspectFlags
{ {
for (auto& itr : psDS->list_fboCandidates) VkImageAspectFlags aspectMask = 0;
if (!ds)
return aspectMask;
for (auto& itr : ds->list_fboCandidates)
{ {
if (itr->m_collisionCheckIndex == curColIndex) if (itr->m_selfDependencyCheckIndex == curColIndex)
return true; aspectMask |= itr->m_selfDependencyCheckAspectMask;
} }
} return aspectMask;
return false; };
}
RendertargetSelfDependencyMask selfDepInfo{};
VkImageAspectFlags vertexAspectFlags = getSelfDependencyMask(vsDS);
VkImageAspectFlags geometryAspectFlags = getSelfDependencyMask(gsDS);
VkImageAspectFlags pixelAspectFlags = getSelfDependencyMask(psDS);
selfDepInfo.aspectMaskFlags = vertexAspectFlags | geometryAspectFlags | pixelAspectFlags;
selfDepInfo.hasNonPixelSelfDependency = (vertexAspectFlags | geometryAspectFlags) != 0;
return selfDepInfo;
}
@@ -51,8 +51,29 @@ public:
[[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;} [[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;}
struct RendertargetSelfDependencyMask
{
VkImageAspectFlags aspectMaskFlags{}; // aspect flags which are simultaneously sampled and written
bool hasNonPixelSelfDependency{false};
VkImageAspectFlags GetAspectMask() const
{
return aspectMaskFlags;
}
bool HasSelfDependency() const
{
return GetAspectMask() != 0;
}
bool HasVertexOrGeometrySelfDependency() const
{
return hasNonPixelSelfDependency; // vertex or geometry shader samples texture which is written to
}
};
// checks if any of the sampled textures are output by the FBO // checks if any of the sampled textures are output by the FBO
bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; RendertargetSelfDependencyMask CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const;
private: private:
@@ -67,6 +67,12 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
} }
if (m_vkr->UseAttachmentFeedbackLoop() && (imageInfo.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0)
{
imageInfo.usage |= VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT;
m_defaultLayout = VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT;
}
if (dim == Latte::E_DIM::DIM_2D) if (dim == Latte::E_DIM::DIM_2D)
imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.imageType = VK_IMAGE_TYPE_2D;
else if (dim == Latte::E_DIM::DIM_1D) else if (dim == Latte::E_DIM::DIM_1D)
@@ -20,6 +20,7 @@ public:
VkFormat GetFormat() const { return vkObjTex->m_format; } VkFormat GetFormat() const { return vkObjTex->m_format; }
VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; }
VkImageLayout GetDefaultLayout() const { return m_defaultLayout; }
VkImageLayout GetImageLayout(VkImageSubresource& subresource) VkImageLayout GetImageLayout(VkImageSubresource& subresource)
{ {
@@ -83,12 +84,14 @@ public:
uint64 m_vkFlushIndex_read{}; uint64 m_vkFlushIndex_read{};
uint64 m_vkFlushIndex_write{}; uint64 m_vkFlushIndex_write{};
uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall uint32 m_selfDependencyCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall
VkImageAspectFlags m_selfDependencyCheckAspectMask{};
private: private:
class VulkanRenderer* m_vkr; class VulkanRenderer* m_vkr;
VKRObjectTexture* vkObjTex{}; VKRObjectTexture* vkObjTex{};
VkImageLayout m_defaultLayout{ VK_IMAGE_LAYOUT_GENERAL }; // the targetted long term layout of the texture. Can be either VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT for potential rendertargets if supported
std::vector<VkImageLayout> m_layouts; std::vector<VkImageLayout> m_layouts;
uint32 m_layoutsMips; uint32 m_layoutsMips;
uint32 m_layoutsDepth; uint32 m_layoutsDepth;
@@ -122,15 +122,15 @@ void LatteTextureReadbackInfoVk::StartTransfer()
const auto renderer = VulkanRenderer::GetInstance(); const auto renderer = VulkanRenderer::GetInstance();
renderer->draw_endRenderPass(); renderer->draw_endRenderPass();
renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
renderer->barrier_sequentializeTransfer(); renderer->barrier_sequentializeTransfer();
vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, &region); vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buffer, 1, &region);
renderer->barrier_sequentializeTransfer(); renderer->barrier_sequentializeTransfer();
renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, baseTexture->GetDefaultLayout()); // make sure transfer is finished before image is modified
renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read
m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId(); m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId();
@@ -189,6 +189,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR);
VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR);
VKFUNC_DEVICE(vkCmdEndRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR);
// ext_attachment_feedback_loop_dynamic_state
VKFUNC_DEVICE(vkCmdSetAttachmentFeedbackLoopEnableEXT);
// khr_present_wait // khr_present_wait
VKFUNC_DEVICE(vkWaitForPresentKHR); VKFUNC_DEVICE(vkWaitForPresentKHR);
@@ -815,6 +815,10 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle
dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS); dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
pipelineInfo->usesDepthBias = true; pipelineInfo->usesDepthBias = true;
} }
if (VulkanRenderer::GetInstance()->UseAttachmentFeedbackLoop())
{
dynamicStates.emplace_back(VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT);
}
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = dynamicStates.size(); dynamicState.dynamicStateCount = dynamicStates.size();
@@ -1135,4 +1139,4 @@ void PipelineCompiler::CompileThreadPool_Stop()
void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v) void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v)
{ {
s_pipelineCompileRequests.push(v); s_pipelineCompileRequests.push(v);
} }
@@ -50,7 +50,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME,
VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME,
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME,
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME,
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME
}; };
const std::vector<const char*> kRequiredDeviceExtensions = const std::vector<const char*> kRequiredDeviceExtensions =
@@ -271,6 +273,22 @@ void VulkanRenderer::GetDeviceFeatures()
prevStruct = &pprf; prevStruct = &pprf;
} }
VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{};
if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state)
{
attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT;
attachmentFeedbackLoopDynamicStateFeature.pNext = prevStruct;
prevStruct = &attachmentFeedbackLoopDynamicStateFeature;
}
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{};
if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
{
attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT;
attachmentFeedbackLoopLayoutFeature.pNext = prevStruct;
prevStruct = &attachmentFeedbackLoopLayoutFeature;
}
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
physicalDeviceFeatures2.pNext = prevStruct; physicalDeviceFeatures2.pNext = prevStruct;
@@ -330,6 +348,12 @@ void VulkanRenderer::GetDeviceFeatures()
if ( pprf.pipelineRobustness != VK_TRUE ) if ( pprf.pipelineRobustness != VK_TRUE )
m_featureControl.deviceExtensions.pipeline_robustness = false; m_featureControl.deviceExtensions.pipeline_robustness = false;
} }
if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
m_featureControl.deviceExtensions.attachment_feedback_loop_layout = attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout == VK_TRUE;
if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state && m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state = attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState == VK_TRUE;
if (!UseAttachmentFeedbackLoop())
cemuLog_log(LogType::Force, "VK_EXT_attachment_feedback_loop_layout(_dynamic_state) not supported");
// get limits // get limits
m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4);
m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4);
@@ -698,6 +722,21 @@ VulkanRenderer::VulkanRenderer()
deviceExtensionFeatures = &pipelineRobustnessFeature; deviceExtensionFeatures = &pipelineRobustnessFeature;
pipelineRobustnessFeature.pipelineRobustness = VK_TRUE; pipelineRobustnessFeature.pipelineRobustness = VK_TRUE;
} }
// enable attachment feedback loop layout + dynamic state if both are supported
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{};
VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{};
if (UseAttachmentFeedbackLoop())
{
attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT;
attachmentFeedbackLoopLayoutFeature.pNext = deviceExtensionFeatures;
deviceExtensionFeatures = &attachmentFeedbackLoopLayoutFeature;
attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout = VK_TRUE;
attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT;
attachmentFeedbackLoopDynamicStateFeature.pNext = deviceExtensionFeatures;
deviceExtensionFeatures = &attachmentFeedbackLoopDynamicStateFeature;
attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState = VK_TRUE;
}
std::vector<const char*> used_extensions; std::vector<const char*> used_extensions;
VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions);
@@ -1122,7 +1161,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
range.mipLevel = 0; range.mipLevel = 0;
range.baseArrayLayer = texViewVk->firstSlice; range.baseArrayLayer = texViewVk->firstSlice;
range.layerCount = 1; range.layerCount = 1;
barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, range, VK_IMAGE_LAYOUT_GENERAL); barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, range, baseImageTex->GetDefaultLayout());
} }
format = VK_FORMAT_R8G8B8A8_UNORM; format = VK_FORMAT_R8G8B8A8_UNORM;
@@ -1178,6 +1217,10 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
} }
vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, &region); vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, &region);
if (dumpImage == baseImage)
{
barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, region.imageSubresource, baseImageTex->GetDefaultLayout());
}
SubmitCommandBuffer(); SubmitCommandBuffer();
WaitCommandBufferFinished(GetCurrentCommandBufferId()); WaitCommandBufferFinished(GetCurrentCommandBufferId());
@@ -1282,6 +1325,11 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe
} }
if (m_featureControl.deviceExtensions.pipeline_robustness) if (m_featureControl.deviceExtensions.pipeline_robustness)
used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
if (UseAttachmentFeedbackLoop())
{
used_extensions.emplace_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME);
used_extensions.emplace_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
}
VkDeviceCreateInfo createInfo{}; VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
@@ -1379,6 +1427,8 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device,
info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
info.deviceExtensions.pipeline_robustness = isExtensionAvailable(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); info.deviceExtensions.pipeline_robustness = isExtensionAvailable(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
info.deviceExtensions.attachment_feedback_loop_layout = isExtensionAvailable(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME);
info.deviceExtensions.attachment_feedback_loop_dynamic_state = isExtensionAvailable(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
// dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022
info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME);
@@ -3237,7 +3287,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
performanceMonitor.vk.numDescriptorSets.increment(); performanceMonitor.vk.numDescriptorSets.increment();
VkDescriptorImageInfo imageInfo = {}; VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; imageInfo.imageLayout = static_cast<LatteTextureVk*>(texViewVk->baseTexture)->GetDefaultLayout();
imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView;
imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
@@ -3395,7 +3445,7 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI
else else
{ {
cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D);
ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, vkTexture->GetDefaultLayout());
} }
} }
@@ -3406,7 +3456,7 @@ void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s
{ {
cemu_assert_unimplemented(); cemu_assert_unimplemented();
} }
ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, vkTexture->GetDefaultLayout());
} }
void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue)
@@ -3447,7 +3497,7 @@ void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s
vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range); vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range);
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, vkTexture->GetDefaultLayout());
} }
void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize)
@@ -3550,7 +3600,7 @@ void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width,
vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion);
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, vkTexture->GetDefaultLayout());
} }
LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels,
@@ -3628,13 +3678,11 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip,
sint32 mipWidth = std::max(dst->width >> dstMip, 1); sint32 mipWidth = std::max(dst->width >> dstMip, 1);
sint32 mipHeight = std::max(dst->height >> dstMip, 1); sint32 mipHeight = std::max(dst->height >> dstMip, 1);
if (mipWidth < 4 || mipHeight < 4) if (mipWidth < 4 || mipHeight < 4)
{ {
cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4"); cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4");
return; return;
} }
} }
// make sure all write operations to the src image have finished // make sure all write operations to the src image have finished
@@ -3645,7 +3693,8 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip,
vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, &region); vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
// make sure the transfer is finished before the image is read or written // make sure the transfer is finished before the image is read or written
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcVk, region.srcSubresource, srcVk->GetDefaultLayout());
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, dstVk->GetDefaultLayout());
} }
LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView) LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView)
@@ -4064,6 +4113,10 @@ void VKRObjectSampler::DestroyCache()
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
{ {
VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance();
bool useAttachmentFeedbackLoop = vkRenderer->UseAttachmentFeedbackLoop();
VkImageLayout attachmentLayout = useAttachmentFeedbackLoop ? VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT : VK_IMAGE_LAYOUT_GENERAL;
// generate helper hash for pipeline state // generate helper hash for pipeline state
uint64 stateHash = 0; uint64 stateHash = 0;
for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i) for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i)
@@ -4097,7 +4150,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format; m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format;
color_attachments_references[i].attachment = (uint32)attachments_descriptions.size(); color_attachments_references[i].attachment = (uint32)attachments_descriptions.size();
color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL; color_attachments_references[i].layout = attachmentLayout;
VkAttachmentDescription entry{}; VkAttachmentDescription entry{};
entry.format = attachmentInfo.colorAttachment[i].format; entry.format = attachmentInfo.colorAttachment[i].format;
@@ -4106,8 +4159,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; entry.initialLayout = attachmentLayout;
entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; entry.finalLayout = attachmentLayout;
attachments_descriptions.emplace_back(entry); attachments_descriptions.emplace_back(entry);
numColorAttachments = i + 1; numColorAttachments = i + 1;
@@ -4124,7 +4177,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
{ {
hasDepthStencilAttachment = true; hasDepthStencilAttachment = true;
depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size(); depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size();
depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL; depth_stencil_attachments_references.layout = attachmentLayout;
m_depthAttachmentFormat = attachmentInfo.depthAttachment.format; m_depthAttachmentFormat = attachmentInfo.depthAttachment.format;
VkAttachmentDescription entry{}; VkAttachmentDescription entry{};
@@ -4142,8 +4195,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
} }
entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; entry.initialLayout = attachmentLayout;
entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; entry.finalLayout = attachmentLayout;
attachments_descriptions.emplace_back(entry); attachments_descriptions.emplace_back(entry);
} }
@@ -4164,12 +4217,29 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
renderPassInfo.subpassCount = 1; renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass; renderPassInfo.pSubpasses = &subpass;
renderPassInfo.pDependencies = nullptr; VkSubpassDependency feedbackLoopDependency{};
renderPassInfo.dependencyCount = 0; if (useAttachmentFeedbackLoop)
{
feedbackLoopDependency.srcSubpass = 0;
feedbackLoopDependency.dstSubpass = 0;
feedbackLoopDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
feedbackLoopDependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
feedbackLoopDependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
feedbackLoopDependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
feedbackLoopDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT | VK_DEPENDENCY_FEEDBACK_LOOP_BIT_EXT;
renderPassInfo.pDependencies = &feedbackLoopDependency;
renderPassInfo.dependencyCount = 1;
}
else
{
renderPassInfo.pDependencies = nullptr;
renderPassInfo.dependencyCount = 0;
}
// before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above) // before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above)
// Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred // Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred
// as of Cemu 2.7 we are now using VK_EXT_attachment_feedback_loop_layout with a matching renderpass dependency if supported. Otherwise we are falling back to the above
if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) if (vkCreateRenderPass(vkRenderer->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS)
{ {
cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass"); cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass");
throw std::runtime_error("failed to create render pass!"); throw std::runtime_error("failed to create render pass!");
@@ -380,7 +380,7 @@ private:
VkDescriptorSetInfo* activePixelDS{ nullptr }; VkDescriptorSetInfo* activePixelDS{ nullptr };
VkDescriptorSetInfo* activeGeometryDS{ nullptr }; VkDescriptorSetInfo* activeGeometryDS{ nullptr };
bool descriptorSetsChanged{ false }; bool descriptorSetsChanged{ false };
bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget VkImageAspectFlags feedbackLoopImageAspect{0xFFFFFFFF};
// viewport and scissor box // viewport and scissor box
VkViewport currentViewport{}; VkViewport currentViewport{};
VkRect2D currentScissorRect{}; VkRect2D currentScissorRect{};
@@ -414,6 +414,7 @@ private:
activeIndexType = Renderer::INDEX_TYPE::NONE; activeIndexType = Renderer::INDEX_TYPE::NONE;
activeIndexBufferIndex = std::numeric_limits<uint32>::max(); activeIndexBufferIndex = std::numeric_limits<uint32>::max();
activeIndexBufferOffset = std::numeric_limits<uint32>::max(); activeIndexBufferOffset = std::numeric_limits<uint32>::max();
feedbackLoopImageAspect = 0xFFFFFFFF;
} }
// invalidation / flushing // invalidation / flushing
@@ -465,6 +466,8 @@ private:
bool present_wait = false; // VK_KHR_present_wait bool present_wait = false; // VK_KHR_present_wait
bool depth_clip_enable = false; // VK_EXT_depth_clip_enable bool depth_clip_enable = false; // VK_EXT_depth_clip_enable
bool pipeline_robustness = false; // VK_EXT_pipeline_robustness bool pipeline_robustness = false; // VK_EXT_pipeline_robustness
bool attachment_feedback_loop_layout = false; // VK_EXT_attachment_feedback_loop_layout
bool attachment_feedback_loop_dynamic_state = false; // VK_EXT_attachment_feedback_loop_dynamic_state (this is forced to false if VK_EXT_attachment_feedback_loop_layout is not supported)
}deviceExtensions; }deviceExtensions;
struct struct
@@ -553,7 +556,7 @@ private:
void draw_handleSpecialState5(); void draw_handleSpecialState5();
// draw synchronization helper // draw synchronization helper
void sync_inputTexturesChanged(); void sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass = false);
void sync_RenderPassLoadTextures(CachedFBOVk* fboVk); void sync_RenderPassLoadTextures(CachedFBOVk* fboVk);
void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); void sync_RenderPassStoreTextures(CachedFBOVk* fboVk);
@@ -952,6 +955,7 @@ public:
bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; } bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; }
bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; } bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; }
bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; } bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; }
bool UseAttachmentFeedbackLoop() const { return m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state; }
private: private:
@@ -711,14 +711,13 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
continue; continue;
} }
info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
VkSamplerCreateInfo samplerInfo{}; VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
LatteTexture* baseTexture = textureView->baseTexture; LatteTextureVk* baseTexture = static_cast<LatteTextureVk*>(textureView->baseTexture);
info.imageLayout = baseTexture->GetDefaultLayout();
// get texture register word 0 // get texture register word 0
uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4];
@@ -731,7 +730,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
textureView->AddDescriptorSetReference(dsInfo); textureView->AddDescriptorSetReference(dsInfo);
if (!baseTexture->IsCompressedFormat()) if (!baseTexture->IsCompressedFormat())
vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture); vectorAppendUnique(dsInfo->list_fboCandidates, baseTexture);
uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit];
if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE)
@@ -1001,7 +1000,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
return dsInfo; return dsInfo;
} }
void VulkanRenderer::sync_inputTexturesChanged() void VulkanRenderer::sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass)
{ {
bool writeFlushRequired = false; bool writeFlushRequired = false;
@@ -1050,14 +1049,27 @@ void VulkanRenderer::sync_inputTexturesChanged()
srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
// dst if (withinFeedbackLoopRenderPass)
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; {
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; // this renderpass has a pixel self dependency. Feedback loop extension allows barriers during renderpass, but they must be BY_REGION
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
}
else
{
// dst
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
}
VkDependencyFlags dependencyFlags = withinFeedbackLoopRenderPass ? VK_DEPENDENCY_BY_REGION_BIT : 0;
vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, dependencyFlags, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
performanceMonitor.vk.numDrawBarriersPerFrame.increment(); performanceMonitor.vk.numDrawBarriersPerFrame.increment();
@@ -1180,22 +1192,32 @@ bool s_syncOnNextDraw = false;
void VulkanRenderer::draw_setRenderPass() void VulkanRenderer::draw_setRenderPass()
{ {
CachedFBOVk* fboVk = m_state.activeFBO; CachedFBOVk* fboVk = m_state.activeFBO;
// note - pixel self dependency can be handled via feedback_loop extension
// vertex/geometry self dependency needs renderpass split
CachedFBOVk::RendertargetSelfDependencyMask renderSelfDependencyInfo{};
// update self-dependency flag // update self-dependency state
if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk) if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk)
{ {
m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
} }
auto vkObjRenderPass = fboVk->GetRenderPassObj(); auto vkObjRenderPass = fboVk->GetRenderPassObj();
auto vkObjFramebuffer = fboVk->GetFramebufferObj(); auto vkObjFramebuffer = fboVk->GetFramebufferObj();
bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier); bool feedbackLoopHandlesSelfDependency = UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.HasSelfDependency() && !renderSelfDependencyInfo.HasVertexOrGeometrySelfDependency();
bool selfDependencyNeedsPassSplit = renderSelfDependencyInfo.HasSelfDependency() && !feedbackLoopHandlesSelfDependency;
bool overridePassReuse = selfDependencyNeedsPassSplit && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier);
if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk) if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk)
{ {
if (m_state.descriptorSetsChanged) if (m_state.descriptorSetsChanged)
sync_inputTexturesChanged(); sync_inputTexturesChanged(feedbackLoopHandlesSelfDependency);
if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect)
{
m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask();
vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask());
}
return; return;
} }
draw_endRenderPass(); draw_endRenderPass();
@@ -1203,7 +1225,7 @@ void VulkanRenderer::draw_setRenderPass()
sync_inputTexturesChanged(); sync_inputTexturesChanged();
// assume that FBO changed, update self-dependency state // assume that FBO changed, update self-dependency state
m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
sync_RenderPassLoadTextures(fboVk); sync_RenderPassLoadTextures(fboVk);
@@ -1228,6 +1250,11 @@ void VulkanRenderer::draw_setRenderPass()
} }
m_state.activeRenderpassFBO = fboVk; m_state.activeRenderpassFBO = fboVk;
if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect)
{
m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask();
vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask());
}
vkObjRenderPass->flagForCurrentCommandBuffer(); vkObjRenderPass->flagForCurrentCommandBuffer();
vkObjFramebuffer->flagForCurrentCommandBuffer(); vkObjFramebuffer->flagForCurrentCommandBuffer();
@@ -557,7 +557,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC
descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0]; descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0];
descriptorImageInfo.imageView = vkObjImageView->m_textureImageView; descriptorImageInfo.imageView = vkObjImageView->m_textureImageView;
descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; descriptorImageInfo.imageLayout = state.sourceTexture->GetDefaultLayout();
VkWriteDescriptorSet write_descriptor{}; VkWriteDescriptorSet write_descriptor{};
write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
@@ -673,8 +673,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3
cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image); cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image);
barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for any modifying operations on source image to complete
barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for any operations on destination image to complete
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
@@ -692,8 +692,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3
vkCmdEndRenderPass(m_state.currentCommandBuffer); vkCmdEndRenderPass(m_state.currentCommandBuffer);
barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the source image
barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the destination image
// restore viewport and scissor box // restore viewport and scissor box
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);