practices: Warn when clears are not compressed
Color clears may not get compressed if meet this criteria:
- A clear color of (1.0f, 1.0f, 1.0f, 1.0f) or (0, 0, 0, 0) is not used;
- the clear happens on a format that cannot be compressed;
- the clear happens on a format that can be compressed, but too many
clear colors have been used.
This commit tracks this criteria for vkCmdClearColorImage,
vkCmdClearAttachments, and vkCmdBeginRender* with CLEAR_OP attachments.
diff --git a/layers/best_practices_utils.cpp b/layers/best_practices_utils.cpp
index 8b792b3..639ce9b 100644
--- a/layers/best_practices_utils.cpp
+++ b/layers/best_practices_utils.cpp
@@ -58,6 +58,13 @@
kVUID_BestPractices_CreateDevice_SpecialUseExtension_GLEmulation,
};
+static constexpr std::array<VkFormat, 12> kCustomClearColorCompressedFormatsNVIDIA = {
+ VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_R16G16B16A16_UNORM,
+ VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SINT,
+ VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+};
+
ReadLockGuard BestPractices::ReadLock() {
if (fine_grained_locking) {
return ReadLockGuard(validation_object_mutex, std::defer_lock);
@@ -1898,14 +1905,32 @@
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.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];
+ 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->activeRenderPassBeginInfo.clearValueCount; ++i) {
+ const auto& attachment = rp->createInfo.pAttachments[i];
+ if (attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) {
+ const auto& clear_color = cmd_state->activeRenderPassBeginInfo.pClearValues[i].color;
+ RecordClearColor(attachment.format, clear_color);
}
}
}
@@ -2162,6 +2187,136 @@
return skip;
}
+static std::array<uint32_t, 4> GetRawClearColor(VkFormat format, const VkClearColorValue& clear_value) {
+ std::array<uint32_t, 4> raw_color{};
+ std::copy_n(clear_value.uint32, raw_color.size(), raw_color.data());
+
+ // Zero out unused components to avoid polluting the cache with garbage
+ if (!FormatHasRed(format)) raw_color[0] = 0;
+ if (!FormatHasGreen(format)) raw_color[1] = 0;
+ if (!FormatHasBlue(format)) raw_color[2] = 0;
+ if (!FormatHasAlpha(format)) raw_color[3] = 0;
+
+ return raw_color;
+}
+
+static bool IsClearColorZeroOrOne(VkFormat format, const std::array<uint32_t, 4> clear_color) {
+ static_assert(sizeof(float) == sizeof(uint32_t), "Mismatching float <-> uint32 sizes");
+ const float one = 1.0f;
+ const float zero = 0.0f;
+ uint32_t raw_one{};
+ uint32_t raw_zero{};
+ memcpy(&raw_one, &one, sizeof(one));
+ memcpy(&raw_zero, &zero, sizeof(zero));
+
+ const bool is_one = (!FormatHasRed(format) || (clear_color[0] == raw_one)) &&
+ (!FormatHasGreen(format) || (clear_color[1] == raw_one)) &&
+ (!FormatHasBlue(format) || (clear_color[2] == raw_one)) &&
+ (!FormatHasAlpha(format) || (clear_color[3] == raw_one));
+ const bool is_zero = (!FormatHasRed(format) || (clear_color[0] == raw_zero)) &&
+ (!FormatHasGreen(format) || (clear_color[1] == raw_zero)) &&
+ (!FormatHasBlue(format) || (clear_color[2] == raw_zero)) &&
+ (!FormatHasAlpha(format) || (clear_color[3] == raw_zero));
+ return is_one || is_zero;
+}
+
+static std::string MakeCompressedFormatListNVIDIA() {
+ std::string format_list;
+ for (VkFormat compressed_format : kCustomClearColorCompressedFormatsNVIDIA) {
+ if (compressed_format == kCustomClearColorCompressedFormatsNVIDIA.back()) {
+ format_list += "or ";
+ }
+ format_list += string_VkFormat(compressed_format);
+ if (compressed_format != kCustomClearColorCompressedFormatsNVIDIA.back()) {
+ format_list += ", ";
+ }
+ }
+ return format_list;
+}
+
+void BestPractices::RecordClearColor(VkFormat format, const VkClearColorValue& clear_value) {
+ assert(VendorCheckEnabled(kBPVendorNVIDIA));
+
+ const std::array<uint32_t, 4> raw_color = GetRawClearColor(format, clear_value);
+ if (IsClearColorZeroOrOne(format, raw_color)) {
+ // These colors are always compressed
+ return;
+ }
+
+ const auto it = std::find(kCustomClearColorCompressedFormatsNVIDIA.begin(), kCustomClearColorCompressedFormatsNVIDIA.end(), format);
+ if (it == kCustomClearColorCompressedFormatsNVIDIA.end()) {
+ // The format cannot be compressed with a custom color
+ return;
+ }
+
+ // Record custom clear color
+ WriteLockGuard guard{clear_colors_lock_};
+ if (clear_colors_.size() < kMaxRecommendedNumberOfClearColorsNVIDIA) {
+ clear_colors_.insert(raw_color);
+ }
+}
+
+bool BestPractices::ValidateClearColor(VkCommandBuffer commandBuffer, VkFormat format, const VkClearColorValue& clear_value) const {
+ assert(VendorCheckEnabled(kBPVendorNVIDIA));
+
+ bool skip = false;
+
+ const std::array<uint32_t, 4> raw_color = GetRawClearColor(format, clear_value);
+ if (IsClearColorZeroOrOne(format, raw_color)) {
+ return skip;
+ }
+
+ const auto it = std::find(kCustomClearColorCompressedFormatsNVIDIA.begin(), kCustomClearColorCompressedFormatsNVIDIA.end(), format);
+ if (it == kCustomClearColorCompressedFormatsNVIDIA.end()) {
+ // The format is not compressible
+ static const std::string format_list = MakeCompressedFormatListNVIDIA();
+
+ skip |= LogPerformanceWarning(commandBuffer, kVUID_BestPractices_ClearColor_NotCompressed,
+ "%s Clearing image with format %s without a 1.0f or 0.0f clear color. "
+ "The clear will not get compressed in the GPU, harming performance. "
+ "This can be fixed using a clear color of VkClearColorValue{0.0f, 0.0f, 0.0f, 0.0f}, or "
+ "VkClearColorValue{1.0f, 1.0f, 1.0f, 1.0f}. Alternatively, use %s.",
+ VendorSpecificTag(kBPVendorNVIDIA), string_VkFormat(format), format_list.c_str());
+ } else {
+ // The format is compressible
+ bool registered = false;
+ {
+ ReadLockGuard guard{clear_colors_lock_};
+ registered = clear_colors_.find(raw_color) != clear_colors_.end();
+
+ if (!registered) {
+ // If it's not in the list, it might be new. Check if there's still space for new entries.
+ registered = clear_colors_.size() < kMaxRecommendedNumberOfClearColorsNVIDIA;
+ }
+ }
+ if (!registered) {
+ std::string clear_color_str;
+
+ if (FormatIsUINT(format)) {
+ clear_color_str = std::to_string(clear_value.uint32[0]) + ", " + std::to_string(clear_value.uint32[1]) + ", " +
+ std::to_string(clear_value.uint32[2]) + ", " + std::to_string(clear_value.uint32[3]);
+ } else if (FormatIsSINT(format)) {
+ clear_color_str = std::to_string(clear_value.int32[0]) + ", " + std::to_string(clear_value.int32[1]) + ", " +
+ std::to_string(clear_value.int32[2]) + ", " + std::to_string(clear_value.int32[3]);
+ } else {
+ clear_color_str = std::to_string(clear_value.float32[0]) + ", " + std::to_string(clear_value.float32[1]) + ", " +
+ std::to_string(clear_value.float32[2]) + ", " + std::to_string(clear_value.float32[3]);
+ }
+
+ skip |= LogPerformanceWarning(
+ commandBuffer, kVUID_BestPractices_ClearColor_NotCompressed,
+ "%s Clearing image with unregistered VkClearColorValue{%s}. "
+ "This clear will not get compressed in the GPU, harming performance. "
+ "The clear color is not registered because too many unique colors have been used. "
+ "Select a discrete set of clear colors and stick to those. "
+ "VkClearColorValue{0, 0, 0, 0} and VkClearColorValue{1.0f, 1.0f, 1.0f, 1.0f} are always registered.",
+ VendorSpecificTag(kBPVendorNVIDIA), clear_color_str.c_str());
+ }
+ }
+
+ return skip;
+}
+
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];
@@ -2307,6 +2462,35 @@
"(%" 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);
+ }
+ }
+ }
+ }
+
+ return skip;
+}
+
+bool BestPractices::ValidateCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) 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);
+ }
+ }
}
return skip;
@@ -2658,6 +2842,18 @@
return skip;
}
+bool BestPractices::PreCallValidateCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) const {
+ bool skip = StateTracker::PreCallValidateCmdBeginRendering(commandBuffer, pRenderingInfo);
+ skip |= ValidateCmdBeginRendering(commandBuffer, pRenderingInfo);
+ return skip;
+}
+
+bool BestPractices::PreCallValidateCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) const {
+ bool skip = StateTracker::PreCallValidateCmdBeginRenderingKHR(commandBuffer, pRenderingInfo);
+ skip |= ValidateCmdBeginRendering(commandBuffer, pRenderingInfo);
+ return skip;
+}
+
void BestPractices::RecordCmdBeginRenderPass(VkCommandBuffer commandBuffer, RenderPassCreateVersion rp_version,
const VkRenderPassBeginInfo* pRenderPassBegin) {
// Reset the renderpass state
@@ -3190,10 +3386,24 @@
if (rp_state->UsesDynamicRendering()) {
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
+ auto pColorAttachments = rp_state->dynamic_rendering_begin_rendering_info.pColorAttachments;
+
for (uint32_t i = 0; i < attachmentCount; i++) {
- if (pClearAttachments[i].aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ auto& clear_attachment = pClearAttachments[i];
+
+ if (clear_attachment.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
RecordResetScopeZcullDirection(*cmd_state);
}
+ if ((clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) &&
+ clear_attachment.colorAttachment != VK_ATTACHMENT_UNUSED &&
+ pColorAttachments) {
+ const auto& attachment = pColorAttachments[clear_attachment.colorAttachment];
+ if (attachment.imageView) {
+ auto image_view_state = Get<IMAGE_VIEW_STATE>(attachment.imageView);
+ const VkFormat format = image_view_state->create_info.format;
+ RecordClearColor(format, clear_attachment.clearValue.color);
+ }
+ }
}
}
@@ -3225,6 +3435,10 @@
} else {
RecordAttachmentAccess(*cmd_state, fb_attachment, aspects);
}
+ if (VendorCheckEnabled(kBPVendorNVIDIA)) {
+ const VkFormat format = rp_state->createInfo.pAttachments[fb_attachment].format;
+ RecordClearColor(format, attachment.clearValue.color);
+ }
}
}
}
@@ -4121,12 +4335,22 @@
const RENDER_PASS_STATE* rp = cb_node->activeRenderPass.get();
if (rp) {
if (rp->use_dynamic_rendering || rp->use_dynamic_rendering_inherited) {
+ const auto pColorAttachments = rp->dynamic_rendering_begin_rendering_info.pColorAttachments;
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
for (uint32_t i = 0; i < attachmentCount; i++) {
- if (pAttachments[i].aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) {
+ const auto& attachment = pAttachments[i];
+ if (attachment.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) {
skip |= ValidateZcullScope(commandBuffer);
}
+ if ((attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) && attachment.colorAttachment != VK_ATTACHMENT_UNUSED) {
+ const auto& color_attachment = pColorAttachments[attachment.colorAttachment];
+ if (color_attachment.imageView) {
+ auto image_view_state = Get<IMAGE_VIEW_STATE>(color_attachment.imageView);
+ const VkFormat format = image_view_state->create_info.format;
+ skip |= ValidateClearColor(commandBuffer, format, attachment.clearValue.color);
+ }
+ }
}
}
@@ -4154,6 +4378,19 @@
}
}
}
+ if (VendorCheckEnabled(kBPVendorNVIDIA) && rp->createInfo.pAttachments) {
+ for (uint32_t attachment_idx = 0; attachment_idx < attachmentCount; ++attachment_idx) {
+ const auto& attachment = pAttachments[attachment_idx];
+
+ if (attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
+ const uint32_t fb_attachment = subpass.pColorAttachments[attachment.colorAttachment].attachment;
+ if (fb_attachment != VK_ATTACHMENT_UNUSED) {
+ const VkFormat format = rp->createInfo.pAttachments[fb_attachment].format;
+ skip |= ValidateClearColor(commandBuffer, format, attachment.clearValue.color);
+ }
+ }
+ }
+ }
}
}
@@ -4300,6 +4537,10 @@
for (uint32_t i = 0; i < rangeCount; i++) {
QueueValidateImage(funcs, "vkCmdClearColorImage()", dst, IMAGE_SUBRESOURCE_USAGE_BP::CLEARED, pRanges[i]);
}
+
+ if (VendorCheckEnabled(kBPVendorNVIDIA)) {
+ RecordClearColor(dst->createInfo.format, *pColor);
+ }
}
void BestPractices::PreCallRecordCmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout,
@@ -4481,6 +4722,9 @@
const VkClearColorValue* pColor, uint32_t rangeCount,
const VkImageSubresourceRange* pRanges) const {
bool skip = false;
+
+ auto dst = Get<bp_state::Image>(image);
+
if (VendorCheckEnabled(kBPVendorAMD)) {
skip |= LogPerformanceWarning(
device, kVUID_BestPractices_ClearAttachment_ClearImage,
@@ -4488,6 +4732,9 @@
"vkCmdClearAttachments instead",
VendorSpecificTag(kBPVendorAMD));
}
+ if (VendorCheckEnabled(kBPVendorNVIDIA)) {
+ skip |= ValidateClearColor(commandBuffer, dst->createInfo.format, *pColor);
+ }
return skip;
}