blob: 8569bc752496ba7601e7121f96d5c0b79cb2eb82 [file] [log] [blame]
/* Copyright (c) 2015-2023 The Khronos Group Inc.
* Copyright (c) 2015-2023 Valve Corporation
* Copyright (c) 2015-2023 LunarG, Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
* Modifications Copyright (C) 2022 RasterGrid Kft.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "best_practices/best_practices_validation.h"
#include "best_practices/best_practices_error_enums.h"
static inline bool RenderPassUsesAttachmentAsResolve(const safe_VkRenderPassCreateInfo2& createInfo, uint32_t attachment) {
for (uint32_t subpass = 0; subpass < createInfo.subpassCount; subpass++) {
const auto& subpass_info = createInfo.pSubpasses[subpass];
if (subpass_info.pResolveAttachments) {
for (uint32_t i = 0; i < subpass_info.colorAttachmentCount; i++) {
if (subpass_info.pResolveAttachments[i].attachment == attachment) return true;
}
}
}
return false;
}
static inline bool RenderPassUsesAttachmentOnTile(const safe_VkRenderPassCreateInfo2& createInfo, uint32_t attachment) {
for (uint32_t subpass = 0; subpass < createInfo.subpassCount; subpass++) {
const auto& subpass_info = createInfo.pSubpasses[subpass];
// If an attachment is ever used as a color attachment,
// resolve attachment or depth stencil attachment,
// it needs to exist on tile at some point.
for (uint32_t i = 0; i < subpass_info.colorAttachmentCount; i++) {
if (subpass_info.pColorAttachments[i].attachment == attachment) return true;
}
if (subpass_info.pResolveAttachments) {
for (uint32_t i = 0; i < subpass_info.colorAttachmentCount; i++) {
if (subpass_info.pResolveAttachments[i].attachment == attachment) return true;
}
}
if (subpass_info.pDepthStencilAttachment && subpass_info.pDepthStencilAttachment->attachment == attachment) return true;
}
return false;
}
static inline bool RenderPassUsesAttachmentAsImageOnly(const safe_VkRenderPassCreateInfo2& createInfo, uint32_t attachment) {
if (RenderPassUsesAttachmentOnTile(createInfo, attachment)) {
return false;
}
for (uint32_t subpass = 0; subpass < createInfo.subpassCount; subpass++) {
const auto& subpassInfo = createInfo.pSubpasses[subpass];
for (uint32_t i = 0; i < subpassInfo.inputAttachmentCount; i++) {
if (subpassInfo.pInputAttachments[i].attachment == attachment) {
return true;
}
}
}
return false;
}
bool BestPractices::PreCallValidateCreateRenderPass(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass,
const ErrorObject& error_obj) const {
bool skip = false;
const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo);
for (uint32_t i = 0; i < pCreateInfo->attachmentCount; ++i) {
VkFormat format = pCreateInfo->pAttachments[i].format;
if (pCreateInfo->pAttachments[i].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
if ((vkuFormatIsColor(format) || vkuFormatHasDepth(format)) &&
pCreateInfo->pAttachments[i].loadOp == VK_ATTACHMENT_LOAD_OP_LOAD) {
skip |= LogWarning(kVUID_BestPractices_RenderPass_Attatchment, device, error_obj.location,
"Render pass has an attachment with loadOp == VK_ATTACHMENT_LOAD_OP_LOAD and "
"initialLayout == VK_IMAGE_LAYOUT_UNDEFINED. This is probably not what you "
"intended. Consider using VK_ATTACHMENT_LOAD_OP_DONT_CARE instead if the "
"image truely is undefined at the start of the render pass.");
}
if (vkuFormatHasStencil(format) && pCreateInfo->pAttachments[i].stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD) {
skip |= LogWarning(kVUID_BestPractices_RenderPass_Attatchment, device, error_obj.location,
"Render pass has an attachment with stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD "
"and initialLayout == VK_IMAGE_LAYOUT_UNDEFINED. This is probably not what you "
"intended. Consider using VK_ATTACHMENT_LOAD_OP_DONT_CARE instead if the "
"image truely is undefined at the start of the render pass.");
}
}
const auto& attachment = pCreateInfo->pAttachments[i];
if (attachment.samples > VK_SAMPLE_COUNT_1_BIT) {
bool access_requires_memory =
attachment.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD || attachment.storeOp == VK_ATTACHMENT_STORE_OP_STORE;
if (vkuFormatHasStencil(format)) {
access_requires_memory |= attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ||
attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_STORE;
}
if (access_requires_memory) {
skip |= LogPerformanceWarning(
kVUID_BestPractices_CreateRenderPass_ImageRequiresMemory, device, error_obj.location,
"Attachment %u in the VkRenderPass is a multisampled image with %u samples, but it uses loadOp/storeOp "
"which requires accessing data from memory. Multisampled images should always be loadOp = CLEAR or DONT_CARE, "
"storeOp = DONT_CARE. This allows the implementation to use lazily allocated memory effectively.",
i, static_cast<uint32_t>(attachment.samples));
}
}
}
if (IsExtEnabled(device_extensions.vk_ext_multisampled_render_to_single_sampled)) {
for (uint32_t i = 0; i < pCreateInfo->subpassCount; ++i) {
if (pCreateInfo->pSubpasses[i].pResolveAttachments) {
for (uint32_t j = 0; j < pCreateInfo->pSubpasses[i].colorAttachmentCount; ++j) {
const auto attachment = pCreateInfo->pSubpasses[i].pResolveAttachments[j].attachment;
if (attachment != VK_ATTACHMENT_UNUSED) {
const auto format = pCreateInfo->pAttachments[attachment].format;
VkSubpassResolvePerformanceQueryEXT performance_query = vku::InitStructHelper();
VkFormatProperties2 format_properties2 = vku::InitStructHelper(&performance_query);
DispatchGetPhysicalDeviceFormatProperties2(physical_device, format, &format_properties2);
if (performance_query.optimal == VK_FALSE) {
skip |= LogPerformanceWarning(
kVUID_BestPractices_SubpassResolve_NonOptimalFormat, device, error_obj.location,
"Attachment %" PRIu32
" in the VkRenderPass has the format %s and is used as a resolve attachment, "
"but VkSubpassResolvePerformanceQueryEXT::optimal is VK_FALSE.",
i, string_VkFormat(format));
}
}
}
}
}
}
for (uint32_t dependency = 0; dependency < pCreateInfo->dependencyCount; dependency++) {
const Location dependency_loc = create_info_loc.dot(Field::pDependencies, dependency);
skip |=
CheckPipelineStageFlags(dependency_loc.dot(Field::srcStageMask), pCreateInfo->pDependencies[dependency].srcStageMask);
skip |=
CheckPipelineStageFlags(dependency_loc.dot(Field::dstStageMask), pCreateInfo->pDependencies[dependency].dstStageMask);
}
return skip;
}
bool BestPractices::ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
const Location& loc) const {
bool skip = false;
if (!pRenderPassBegin) {
return skip;
}
if (pRenderPassBegin->renderArea.extent.width == 0 || pRenderPassBegin->renderArea.extent.height == 0) {
skip |= LogWarning(kVUID_BestPractices_BeginRenderPass_ZeroSizeRenderArea, device, loc,
"This render pass has a zero-size render area. It cannot write to any attachments, "
"and can only be used for side effects such as layout transitions.");
}
auto rp_state = Get<RENDER_PASS_STATE>(pRenderPassBegin->renderPass);
if (rp_state) {
if (rp_state->createInfo.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) {
const VkRenderPassAttachmentBeginInfo* rpabi = vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext);
if (rpabi) {
skip = ValidateAttachments(rp_state->createInfo.ptr(), rpabi->attachmentCount, rpabi->pAttachments, loc);
}
}
// Check if any attachments have LOAD operation on them
for (uint32_t att = 0; att < rp_state->createInfo.attachmentCount; att++) {
const auto& attachment = rp_state->createInfo.pAttachments[att];
bool attachment_has_readback = false;
if (!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD) {
attachment_has_readback = true;
}
if (vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD) {
attachment_has_readback = true;
}
bool attachment_needs_readback = false;
// Check if the attachment is actually used in any subpass on-tile
if (attachment_has_readback && RenderPassUsesAttachmentOnTile(rp_state->createInfo, att)) {
attachment_needs_readback = true;
}
// Using LOAD_OP_LOAD is expensive on tiled GPUs, so flag it as a potential improvement
if (attachment_needs_readback && (VendorCheckEnabled(kBPVendorArm) || VendorCheckEnabled(kBPVendorIMG))) {
skip |=
LogPerformanceWarning(kVUID_BestPractices_BeginRenderPass_AttachmentNeedsReadback, device, loc,
"%s %s: Attachment #%u in render pass has begun with VK_ATTACHMENT_LOAD_OP_LOAD.\n"
"Submitting this renderpass will cause the driver to inject a readback of the attachment "
"which will copy in total %u pixels (renderArea = "
"{ %" PRId32 ", %" PRId32 ", %" PRIu32 ", %" PRIu32 " }) to the tile buffer.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG), att,
pRenderPassBegin->renderArea.extent.width * pRenderPassBegin->renderArea.extent.height,
pRenderPassBegin->renderArea.offset.x, pRenderPassBegin->renderArea.offset.y,
pRenderPassBegin->renderArea.extent.width, pRenderPassBegin->renderArea.extent.height);
}
}
// Check if renderpass has at least one VK_ATTACHMENT_LOAD_OP_CLEAR
bool clearing = false;
for (uint32_t att = 0; att < rp_state->createInfo.attachmentCount; att++) {
const auto& attachment = rp_state->createInfo.pAttachments[att];
if (attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
clearing = true;
break;
}
}
// Check if there are ClearValues passed to BeginRenderPass even though no attachments will be cleared
if (!clearing && pRenderPassBegin->clearValueCount > 0) {
// Flag as warning because nothing will happen per spec, and pClearValues will be ignored
skip |= LogWarning(
kVUID_BestPractices_ClearValueWithoutLoadOpClear, device, loc,
"This render pass does not have VkRenderPassCreateInfo.pAttachments->loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR "
"but VkRenderPassBeginInfo.clearValueCount > 0. VkRenderPassBeginInfo.pClearValues will be ignored and no "
"attachments will be cleared.");
}
// Check if there are more clearValues than attachments
if (pRenderPassBegin->clearValueCount > rp_state->createInfo.attachmentCount) {
// Flag as warning because the overflowing clearValues will be ignored and could even be undefined on certain platforms.
// This could signal a bug and there seems to be no reason for this to happen on purpose.
skip |=
LogWarning(kVUID_BestPractices_ClearValueCountHigherThanAttachmentCount, device, loc,
"This render pass has VkRenderPassBeginInfo.clearValueCount > VkRenderPassCreateInfo.attachmentCount "
"(%" PRIu32 " > %" PRIu32
") and as such the clearValues that do not have a corresponding attachment will be ignored.",
pRenderPassBegin->clearValueCount, rp_state->createInfo.attachmentCount);
}
if (VendorCheckEnabled(kBPVendorNVIDIA) && rp_state->createInfo.pAttachments) {
for (uint32_t i = 0; i < pRenderPassBegin->clearValueCount; ++i) {
const auto& attachment = rp_state->createInfo.pAttachments[i];
if (attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
const auto& clear_color = pRenderPassBegin->pClearValues[i].color;
skip |= ValidateClearColor(commandBuffer, attachment.format, clear_color, loc);
}
}
}
}
return skip;
}
bool BestPractices::ValidateCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
const Location& loc) const {
bool skip = false;
auto cmd_state = Get<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
for (uint32_t i = 0; i < pRenderingInfo->colorAttachmentCount; ++i) {
const auto& color_attachment = pRenderingInfo->pColorAttachments[i];
if (color_attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
const VkFormat format = Get<IMAGE_VIEW_STATE>(color_attachment.imageView)->create_info.format;
skip |= ValidateClearColor(commandBuffer, format, color_attachment.clearValue.color, loc);
}
}
}
return skip;
}
void BestPractices::PreCallRecordCmdEndRenderPass(VkCommandBuffer commandBuffer) {
RecordCmdEndRenderingCommon(commandBuffer);
ValidationStateTracker::PreCallRecordCmdEndRenderPass(commandBuffer);
auto cb_node = GetWrite<bp_state::CommandBuffer>(commandBuffer);
if (cb_node) {
AddDeferredQueueOperations(*cb_node);
}
}
void BestPractices::PreCallRecordCmdEndRenderPass2(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassInfo) {
RecordCmdEndRenderingCommon(commandBuffer);
ValidationStateTracker::PreCallRecordCmdEndRenderPass2(commandBuffer, pSubpassInfo);
auto cb_node = GetWrite<bp_state::CommandBuffer>(commandBuffer);
if (cb_node) {
AddDeferredQueueOperations(*cb_node);
}
}
void BestPractices::PreCallRecordCmdEndRenderPass2KHR(VkCommandBuffer commandBuffer, const VkSubpassEndInfoKHR* pSubpassInfo) {
PreCallRecordCmdEndRenderPass2(commandBuffer, pSubpassInfo);
}
void BestPractices::PreCallRecordCmdEndRendering(VkCommandBuffer commandBuffer) {
RecordCmdEndRenderingCommon(commandBuffer);
ValidationStateTracker::PreCallRecordCmdEndRendering(commandBuffer);
}
void BestPractices::PreCallRecordCmdEndRenderingKHR(VkCommandBuffer commandBuffer) { PreCallRecordCmdEndRendering(commandBuffer); }
void BestPractices::PreCallRecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
VkSubpassContents contents) {
ValidationStateTracker::PreCallRecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin, contents);
RecordCmdBeginRenderingCommon(commandBuffer);
RecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin);
}
void BestPractices::PreCallRecordCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo) {
ValidationStateTracker::PreCallRecordCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo);
RecordCmdBeginRenderingCommon(commandBuffer);
RecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin);
}
void BestPractices::PreCallRecordCmdBeginRenderPass2KHR(VkCommandBuffer commandBuffer,
const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo) {
PreCallRecordCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo);
}
void BestPractices::PreCallRecordCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
ValidationStateTracker::PreCallRecordCmdBeginRendering(commandBuffer, pRenderingInfo);
RecordCmdBeginRenderingCommon(commandBuffer);
}
void BestPractices::PreCallRecordCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
PreCallRecordCmdBeginRendering(commandBuffer, pRenderingInfo);
}
void BestPractices::PostCallRecordCmdNextSubpass(VkCommandBuffer commandBuffer, VkSubpassContents contents,
const RecordObject& record_obj) {
ValidationStateTracker::PostCallRecordCmdNextSubpass(commandBuffer, contents, record_obj);
auto cmd_state = GetWrite<bp_state::CommandBuffer>(commandBuffer);
auto rp = cmd_state->activeRenderPass.get();
assert(rp);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
IMAGE_VIEW_STATE* depth_image_view = nullptr;
const auto depth_attachment = rp->createInfo.pSubpasses[cmd_state->GetActiveSubpass()].pDepthStencilAttachment;
if (depth_attachment) {
const uint32_t attachment_index = depth_attachment->attachment;
if (attachment_index != VK_ATTACHMENT_UNUSED) {
depth_image_view = (*cmd_state->active_attachments)[attachment_index];
}
}
if (depth_image_view && (depth_image_view->create_info.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0U) {
const VkImage depth_image = depth_image_view->image_state->image();
const VkImageSubresourceRange& subresource_range = depth_image_view->create_info.subresourceRange;
RecordBindZcullScope(*cmd_state, depth_image, subresource_range);
} else {
RecordUnbindZcullScope(*cmd_state);
}
}
}
void BestPractices::RecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin) {
if (!pRenderPassBegin) {
return;
}
auto cb = GetWrite<bp_state::CommandBuffer>(commandBuffer);
auto rp_state = Get<RENDER_PASS_STATE>(pRenderPassBegin->renderPass);
if (rp_state) {
// Check load ops
for (uint32_t att = 0; att < rp_state->createInfo.attachmentCount; att++) {
const auto& attachment = rp_state->createInfo.pAttachments[att];
if (!RenderPassUsesAttachmentAsImageOnly(rp_state->createInfo, att) &&
!RenderPassUsesAttachmentOnTile(rp_state->createInfo, att)) {
continue;
}
// If renderpass doesn't load attachment, no need to validate image in queue
if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_NONE_EXT) ||
(vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_NONE_EXT)) {
continue;
}
IMAGE_SUBRESOURCE_USAGE_BP usage = IMAGE_SUBRESOURCE_USAGE_BP::UNDEFINED;
if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD) ||
(vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD)) {
usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_READ_TO_TILE;
} else if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) ||
(vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR)) {
usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_CLEARED;
} else if (RenderPassUsesAttachmentAsImageOnly(rp_state->createInfo, att)) {
usage = IMAGE_SUBRESOURCE_USAGE_BP::DESCRIPTOR_ACCESS;
}
auto framebuffer = Get<FRAMEBUFFER_STATE>(pRenderPassBegin->framebuffer);
std::shared_ptr<IMAGE_VIEW_STATE> image_view = nullptr;
if (framebuffer->createInfo.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) {
const VkRenderPassAttachmentBeginInfo* rpabi =
vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext);
if (rpabi) {
image_view = Get<IMAGE_VIEW_STATE>(rpabi->pAttachments[att]);
}
} else {
image_view = Get<IMAGE_VIEW_STATE>(framebuffer->createInfo.pAttachments[att]);
}
QueueValidateImageView(cb->queue_submit_functions, Func::vkCmdBeginRenderPass, image_view.get(), usage);
}
// Check store ops
for (uint32_t att = 0; att < rp_state->createInfo.attachmentCount; att++) {
const auto& attachment = rp_state->createInfo.pAttachments[att];
if (!RenderPassUsesAttachmentOnTile(rp_state->createInfo, att)) {
continue;
}
// If renderpass doesn't store attachment, no need to validate image in queue
if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.storeOp == VK_ATTACHMENT_STORE_OP_NONE) ||
(vkuFormatHasStencil(attachment.format) && attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_NONE)) {
continue;
}
IMAGE_SUBRESOURCE_USAGE_BP usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_DISCARDED;
if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.storeOp == VK_ATTACHMENT_STORE_OP_STORE) ||
(vkuFormatHasStencil(attachment.format) && attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_STORE)) {
usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_STORED;
}
auto framebuffer = Get<FRAMEBUFFER_STATE>(pRenderPassBegin->framebuffer);
std::shared_ptr<IMAGE_VIEW_STATE> image_view;
if (framebuffer->createInfo.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) {
const VkRenderPassAttachmentBeginInfo* rpabi =
vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(pRenderPassBegin->pNext);
if (rpabi) {
image_view = Get<IMAGE_VIEW_STATE>(rpabi->pAttachments[att]);
}
} else {
image_view = Get<IMAGE_VIEW_STATE>(framebuffer->createInfo.pAttachments[att]);
}
QueueValidateImageView(cb->queue_submit_functions_after_render_pass, Func::vkCmdEndRenderPass, image_view.get(), usage);
}
}
}
void BestPractices::RecordCmdBeginRenderingCommon(VkCommandBuffer commandBuffer) {
auto cmd_state = GetWrite<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
auto rp = cmd_state->activeRenderPass.get();
assert(rp);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
std::shared_ptr<IMAGE_VIEW_STATE> depth_image_view_shared_ptr;
IMAGE_VIEW_STATE* depth_image_view = nullptr;
std::optional<VkAttachmentLoadOp> load_op;
if (rp->use_dynamic_rendering || rp->use_dynamic_rendering_inherited) {
const auto depth_attachment = rp->dynamic_rendering_begin_rendering_info.pDepthAttachment;
if (depth_attachment) {
load_op.emplace(depth_attachment->loadOp);
depth_image_view_shared_ptr = Get<IMAGE_VIEW_STATE>(depth_attachment->imageView);
depth_image_view = depth_image_view_shared_ptr.get();
}
for (uint32_t i = 0; i < rp->dynamic_rendering_begin_rendering_info.colorAttachmentCount; ++i) {
const auto& color_attachment = rp->dynamic_rendering_begin_rendering_info.pColorAttachments[i];
if (color_attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
const VkFormat format = Get<IMAGE_VIEW_STATE>(color_attachment.imageView)->create_info.format;
RecordClearColor(format, color_attachment.clearValue.color);
}
}
} else {
if (rp->createInfo.pAttachments) {
if (rp->createInfo.subpassCount > 0) {
const auto depth_attachment = rp->createInfo.pSubpasses[0].pDepthStencilAttachment;
if (depth_attachment) {
const uint32_t attachment_index = depth_attachment->attachment;
if (attachment_index != VK_ATTACHMENT_UNUSED) {
load_op.emplace(rp->createInfo.pAttachments[attachment_index].loadOp);
depth_image_view = (*cmd_state->active_attachments)[attachment_index];
}
}
}
for (uint32_t i = 0; i < cmd_state->active_render_pass_begin_info.clearValueCount; ++i) {
const auto& attachment = rp->createInfo.pAttachments[i];
if (attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
const auto& clear_color = cmd_state->active_render_pass_begin_info.pClearValues[i].color;
RecordClearColor(attachment.format, clear_color);
}
}
}
}
if (depth_image_view && (depth_image_view->create_info.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0U) {
const VkImage depth_image = depth_image_view->image_state->image();
const VkImageSubresourceRange& subresource_range = depth_image_view->create_info.subresourceRange;
RecordBindZcullScope(*cmd_state, depth_image, subresource_range);
} else {
RecordUnbindZcullScope(*cmd_state);
}
if (load_op) {
if (*load_op == VK_ATTACHMENT_LOAD_OP_CLEAR || *load_op == VK_ATTACHMENT_LOAD_OP_DONT_CARE) {
RecordResetScopeZcullDirection(*cmd_state);
}
}
}
}
void BestPractices::RecordCmdEndRenderingCommon(VkCommandBuffer commandBuffer) {
auto cmd_state = GetWrite<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
auto rp = cmd_state->activeRenderPass.get();
assert(rp);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
std::optional<VkAttachmentStoreOp> store_op;
if (rp->use_dynamic_rendering || rp->use_dynamic_rendering_inherited) {
const auto depth_attachment = rp->dynamic_rendering_begin_rendering_info.pDepthAttachment;
if (depth_attachment) {
store_op.emplace(depth_attachment->storeOp);
}
} else {
if (rp->createInfo.subpassCount > 0) {
const uint32_t last_subpass = rp->createInfo.subpassCount - 1;
const auto depth_attachment = rp->createInfo.pSubpasses[last_subpass].pDepthStencilAttachment;
if (depth_attachment) {
const uint32_t attachment = depth_attachment->attachment;
if (attachment != VK_ATTACHMENT_UNUSED) {
store_op.emplace(rp->createInfo.pAttachments[attachment].storeOp);
}
}
}
}
if (store_op) {
if (*store_op == VK_ATTACHMENT_STORE_OP_DONT_CARE || *store_op == VK_ATTACHMENT_STORE_OP_NONE) {
RecordResetScopeZcullDirection(*cmd_state);
}
}
RecordUnbindZcullScope(*cmd_state);
}
}
bool BestPractices::PreCallValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
VkSubpassContents contents, const ErrorObject& error_obj) const {
bool skip = StateTracker::PreCallValidateCmdBeginRenderPass(commandBuffer, pRenderPassBegin, contents, error_obj);
skip |= ValidateCmdBeginRenderPass(commandBuffer, pRenderPassBegin, error_obj.location);
return skip;
}
bool BestPractices::PreCallValidateCmdBeginRenderPass2KHR(VkCommandBuffer commandBuffer,
const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo,
const ErrorObject& error_obj) const {
return PreCallValidateCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, error_obj);
}
bool BestPractices::PreCallValidateCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo,
const ErrorObject& error_obj) const {
bool skip = StateTracker::PreCallValidateCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, error_obj);
skip |= ValidateCmdBeginRenderPass(commandBuffer, pRenderPassBegin, error_obj.location);
return skip;
}
bool BestPractices::PreCallValidateCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
const ErrorObject& error_obj) const {
bool skip = StateTracker::PreCallValidateCmdBeginRendering(commandBuffer, pRenderingInfo, error_obj);
skip |= ValidateCmdBeginRendering(commandBuffer, pRenderingInfo, error_obj.location);
return skip;
}
bool BestPractices::PreCallValidateCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
const ErrorObject& error_obj) const {
return PreCallValidateCmdBeginRendering(commandBuffer, pRenderingInfo, error_obj);
}
void BestPractices::PostRecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin) {
// Reset the renderpass state
auto cb = GetWrite<bp_state::CommandBuffer>(commandBuffer);
// TODO - move this logic to the Render Pass state as cb->has_draw_cmd should stay true for lifetime of command buffer
cb->has_draw_cmd = false;
assert(cb);
auto& render_pass_state = cb->render_pass_state;
render_pass_state.touchesAttachments.clear();
render_pass_state.earlyClearAttachments.clear();
render_pass_state.numDrawCallsDepthOnly = 0;
render_pass_state.numDrawCallsDepthEqualCompare = 0;
render_pass_state.colorAttachment = false;
render_pass_state.depthAttachment = false;
render_pass_state.drawTouchAttachments = true;
// Don't reset state related to pipeline state.
// Reset NV state
cb->nv = {};
auto rp_state = Get<RENDER_PASS_STATE>(pRenderPassBegin->renderPass);
// track depth / color attachment usage within the renderpass
for (size_t i = 0; i < rp_state->createInfo.subpassCount; i++) {
// record if depth/color attachments are in use for this renderpass
if (rp_state->createInfo.pSubpasses[i].pDepthStencilAttachment != nullptr) render_pass_state.depthAttachment = true;
if (rp_state->createInfo.pSubpasses[i].colorAttachmentCount > 0) render_pass_state.colorAttachment = true;
}
}
void BestPractices::PostCallRecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
VkSubpassContents contents, const RecordObject& record_obj) {
StateTracker::PostCallRecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin, contents, record_obj);
PostRecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin);
}
void BestPractices::PostCallRecordCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo, const RecordObject& record_obj) {
StateTracker::PostCallRecordCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, record_obj);
PostRecordCmdBeginRenderPass(commandBuffer, pRenderPassBegin);
}
void BestPractices::PostCallRecordCmdBeginRenderPass2KHR(VkCommandBuffer commandBuffer,
const VkRenderPassBeginInfo* pRenderPassBegin,
const VkSubpassBeginInfo* pSubpassBeginInfo,
const RecordObject& record_obj) {
PostCallRecordCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, record_obj);
}
bool BestPractices::PreCallValidateCmdEndRenderPass2(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
const ErrorObject& error_obj) const {
bool skip = false;
skip |= StateTracker::PreCallValidateCmdEndRenderPass2(commandBuffer, pSubpassEndInfo, error_obj);
skip |= ValidateCmdEndRenderPass(commandBuffer, error_obj.location);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
const auto cmd_state = GetRead<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
skip |= ValidateZcullScope(*cmd_state, error_obj.location);
}
return skip;
}
bool BestPractices::PreCallValidateCmdEndRenderPass2KHR(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
const ErrorObject& error_obj) const {
return PreCallValidateCmdEndRenderPass2(commandBuffer, pSubpassEndInfo, error_obj);
}
bool BestPractices::PreCallValidateCmdEndRenderPass(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
bool skip = false;
skip |= StateTracker::PreCallValidateCmdEndRenderPass(commandBuffer, error_obj);
skip |= ValidateCmdEndRenderPass(commandBuffer, error_obj.location);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
const auto cmd_state = GetRead<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
skip |= ValidateZcullScope(*cmd_state, error_obj.location);
}
return skip;
}
bool BestPractices::PreCallValidateCmdEndRendering(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
bool skip = false;
skip |= StateTracker::PreCallValidateCmdEndRendering(commandBuffer, error_obj);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
const auto cmd_state = GetRead<bp_state::CommandBuffer>(commandBuffer);
assert(cmd_state);
skip |= ValidateZcullScope(*cmd_state, error_obj.location);
}
return skip;
}
bool BestPractices::PreCallValidateCmdEndRenderingKHR(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
return PreCallValidateCmdEndRendering(commandBuffer, error_obj);
}
bool BestPractices::ValidateCmdEndRenderPass(VkCommandBuffer commandBuffer, const Location& loc) const {
bool skip = false;
const auto cmd = GetRead<bp_state::CommandBuffer>(commandBuffer);
if (cmd == nullptr) return skip;
auto& render_pass_state = cmd->render_pass_state;
// Does the number of draw calls classified as depth only surpass the vendor limit for a specified vendor
const bool depth_only_arm = render_pass_state.numDrawCallsDepthEqualCompare >= kDepthPrePassNumDrawCallsArm &&
render_pass_state.numDrawCallsDepthOnly >= kDepthPrePassNumDrawCallsArm;
const bool depth_only_img = render_pass_state.numDrawCallsDepthEqualCompare >= kDepthPrePassNumDrawCallsIMG &&
render_pass_state.numDrawCallsDepthOnly >= kDepthPrePassNumDrawCallsIMG;
// Only send the warning when the vendor is enabled and a depth prepass is detected
bool uses_depth =
(render_pass_state.depthAttachment || render_pass_state.colorAttachment) &&
((depth_only_arm && VendorCheckEnabled(kBPVendorArm)) || (depth_only_img && VendorCheckEnabled(kBPVendorIMG)));
if (uses_depth) {
skip |= LogPerformanceWarning(
kVUID_BestPractices_EndRenderPass_DepthPrePassUsage, device, loc,
"%s %s: Depth pre-passes may be in use. In general, this is not recommended in tile-based deferred "
"renderering architectures; such as those in Arm Mali or PowerVR GPUs. Since they can remove geometry "
"hidden by other opaque geometry. Mali has Forward Pixel Killing (FPK), PowerVR has Hiden Surface "
"Remover (HSR) in which case, using depth pre-passes for hidden surface removal may worsen performance.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG));
}
RENDER_PASS_STATE* rp = cmd->activeRenderPass.get();
if ((VendorCheckEnabled(kBPVendorArm) || VendorCheckEnabled(kBPVendorIMG)) && rp) {
// If we use an attachment on-tile, we should access it in some way. Otherwise,
// it is redundant to have it be part of the render pass.
// Only consider it redundant if it will actually consume bandwidth, i.e.
// LOAD_OP_LOAD is used or STORE_OP_STORE. CLEAR -> DONT_CARE is benign,
// as is using pure input attachments.
// CLEAR -> STORE might be considered a "useful" thing to do, but
// the optimal thing to do is to defer the clear until you're actually
// going to render to the image.
uint32_t num_attachments = rp->createInfo.attachmentCount;
for (uint32_t i = 0; i < num_attachments; i++) {
if (!RenderPassUsesAttachmentOnTile(rp->createInfo, i) || RenderPassUsesAttachmentAsResolve(rp->createInfo, i)) {
continue;
}
auto& attachment = rp->createInfo.pAttachments[i];
VkImageAspectFlags bandwidth_aspects = 0;
if (!vkuFormatIsStencilOnly(attachment.format) &&
(attachment.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD || attachment.storeOp == VK_ATTACHMENT_STORE_OP_STORE)) {
if (vkuFormatHasDepth(attachment.format)) {
bandwidth_aspects |= VK_IMAGE_ASPECT_DEPTH_BIT;
} else {
bandwidth_aspects |= VK_IMAGE_ASPECT_COLOR_BIT;
}
}
if (vkuFormatHasStencil(attachment.format) && (attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD ||
attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_STORE)) {
bandwidth_aspects |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
if (!bandwidth_aspects) {
continue;
}
auto itr = std::find_if(render_pass_state.touchesAttachments.begin(), render_pass_state.touchesAttachments.end(),
[i](const bp_state::AttachmentInfo& info) { return info.framebufferAttachment == i; });
uint32_t untouched_aspects = bandwidth_aspects;
if (itr != render_pass_state.touchesAttachments.end()) {
untouched_aspects &= ~itr->aspects;
}
if (untouched_aspects) {
skip |= LogPerformanceWarning(
kVUID_BestPractices_EndRenderPass_RedundantAttachmentOnTile, device, loc,
"%s %s: Render pass was ended, but attachment #%u (format: %u, untouched aspects 0x%x) "
"was never accessed by a pipeline or clear command. "
"On tile-based architectures, LOAD_OP_LOAD and STORE_OP_STORE consume bandwidth and should not be part of the "
"render pass if the attachments are not intended to be accessed.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG), i, attachment.format, untouched_aspects);
}
}
}
return skip;
}