blob: e0aa76b3550e0dd119109e701a431bf8027258ef [file]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 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/bp_state.h"
#include "state_tracker/queue_state.h"
#include "generated/dispatch_functions.h"
bool BestPractices::PreCallValidateCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkImage* pImage,
const ErrorObject& error_obj) const {
bool skip = false;
if ((pCreateInfo->queueFamilyIndexCount > 1) && (pCreateInfo->sharingMode == VK_SHARING_MODE_EXCLUSIVE)) {
skip |= LogWarning("BestPractices-vkCreateImage-sharing-mode-exclusive", device,
error_obj.location.dot(Field::pCreateInfo).dot(Field::sharingMode),
"is VK_SHARING_MODE_EXCLUSIVE while specifying multiple queues "
"(queueFamilyIndexCount of %" PRIu32 ").",
pCreateInfo->queueFamilyIndexCount);
}
if ((pCreateInfo->flags & VK_IMAGE_CREATE_EXTENDED_USAGE_BIT) && !(pCreateInfo->flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT)) {
skip |= LogWarning("BestPractices-vkCreateImage-CreateFlags", device,
error_obj.location.dot(Field::pCreateInfo).dot(Field::flags),
"has VK_IMAGE_CREATE_EXTENDED_USAGE_BIT set, but not "
"VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, therefore image views created from this image will have to use the "
"same format and VK_IMAGE_CREATE_EXTENDED_USAGE_BIT will not have any effect.");
}
if (VendorCheckEnabled(kBPVendorArm) || VendorCheckEnabled(kBPVendorIMG)) {
if (pCreateInfo->samples > VK_SAMPLE_COUNT_1_BIT && !(pCreateInfo->usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT)) {
skip |= LogPerformanceWarning(
"BestPractices-vkCreateImage-non-transient-ms-image", device, error_obj.location,
"%s %s Trying to create a multisampled image, but pCreateInfo->usage did not have "
"VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT set. Multisampled images may be resolved on-chip, "
"and do not need to be backed by physical storage. "
"TRANSIENT_ATTACHMENT allows tiled GPUs to not back the multisampled image with physical memory.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG));
}
}
if (VendorCheckEnabled(kBPVendorArm) && pCreateInfo->samples > kMaxEfficientSamplesArm) {
skip |= LogPerformanceWarning(
"BestPractices-Arm-vkCreateImage-too-large-sample-count", device, error_obj.location,
"%s Trying to create an image with %" PRIu32
" samples. "
"The hardware revision may not have full throughput for framebuffers with more than %" PRIu32 " samples.",
VendorSpecificTag(kBPVendorArm), SampleCountSize(pCreateInfo->samples), SampleCountSize(kMaxEfficientSamplesArm));
}
if (VendorCheckEnabled(kBPVendorIMG) && pCreateInfo->samples > kMaxEfficientSamplesImg) {
skip |= LogPerformanceWarning(
"BestPractices-IMG-vkCreateImage-too-large-sample-count", device, error_obj.location,
"%s Trying to create an image with %" PRIu32
" samples. "
"The device may not have full support for true multisampling for images with more than %" PRIu32
" samples. "
"XT devices support up to 8 samples, XE up to 4 samples.",
VendorSpecificTag(kBPVendorIMG), SampleCountSize(pCreateInfo->samples), SampleCountSize(kMaxEfficientSamplesImg));
}
if (VendorCheckEnabled(kBPVendorIMG) && (pCreateInfo->format == VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG ||
pCreateInfo->format == VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG)) {
skip |= LogPerformanceWarning("BestPractices-IMG-Texture-Format-PVRTC-Outdated", device, error_obj.location,
"%s Trying to create an image with a PVRTC format. Both PVRTC1 and PVRTC2 "
"are slower than standard image formats on PowerVR GPUs, prefer ETC, BC, ASTC, etc.",
VendorSpecificTag(kBPVendorIMG));
}
if (VendorCheckEnabled(kBPVendorAMD)) {
if ((pCreateInfo->usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
(pCreateInfo->sharingMode == VK_SHARING_MODE_CONCURRENT)) {
skip |= LogPerformanceWarning("BestPractices-AMD-vkImage-AvoidConcurrentRenderTargets", device, error_obj.location,
"%s Trying to create an image as a render target with VK_SHARING_MODE_CONCURRENT. "
"Using SHARING_MODE_CONCURRENT "
"is not recommended with color and depth targets",
VendorSpecificTag(kBPVendorAMD));
}
if ((pCreateInfo->usage &
(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
(pCreateInfo->flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT)) {
skip |=
LogPerformanceWarning("BestPractices-AMD-vkImage-DontUseMutableRenderTargets", device, error_obj.location,
"%s Trying to create an image as a render target with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT. "
"Using MUTABLE_FORMAT is not recommended with color, depth, and storage targets",
VendorSpecificTag(kBPVendorAMD));
}
if ((pCreateInfo->usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) &&
(pCreateInfo->usage & VK_IMAGE_USAGE_STORAGE_BIT)) {
skip |= LogPerformanceWarning("BestPractices-AMD-vkImage-DontUseStorageRenderTargets", device, error_obj.location,
"%s Trying to create an image as a render target with VK_IMAGE_USAGE_STORAGE_BIT. Using "
"VK_IMAGE_USAGE_STORAGE_BIT is not recommended with color and depth targets",
VendorSpecificTag(kBPVendorAMD));
}
}
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
if (pCreateInfo->tiling == VK_IMAGE_TILING_LINEAR) {
skip |= LogPerformanceWarning("BestPractices-NVIDIA-CreateImage-TilingLinear", device, error_obj.location,
"%s Trying to create an image with tiling VK_IMAGE_TILING_LINEAR. "
"Use VK_IMAGE_TILING_OPTIMAL instead.",
VendorSpecificTag(kBPVendorNVIDIA));
}
if (pCreateInfo->format == VK_FORMAT_D32_SFLOAT || pCreateInfo->format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
skip |=
LogPerformanceWarning("BestPractices-NVIDIA-CreateImage-Depth32Format", device, error_obj.location,
"%s Trying to create an image with a 32-bit depth format. Use VK_FORMAT_D24_UNORM_S8_UINT or "
"VK_FORMAT_D16_UNORM instead, unless the extra precision is needed.",
VendorSpecificTag(kBPVendorNVIDIA));
}
}
return skip;
}
void BestPractices::QueueValidateImageView(QueueCallbacks& funcs, const Location& loc, const vvl::ImageView& image_view,
IMAGE_SUBRESOURCE_USAGE_BP usage) {
auto image_state = std::static_pointer_cast<vvl::Image>(image_view.image_state);
if (image_view.image_state) {
QueueValidateImage(funcs, loc, *image_view.image_state, usage, image_view.normalized_subresource_range);
}
}
void BestPractices::QueueValidateImage(QueueCallbacks& funcs, const Location& loc, vvl::Image& image_state,
IMAGE_SUBRESOURCE_USAGE_BP usage, const VkImageSubresourceRange& subresource_range) {
// If we're viewing a 3D slice, ignore base array layer.
// The entire 3D subresource is accessed as one atomic unit.
const uint32_t base_array_layer = image_state.GetImageType() == VK_IMAGE_TYPE_3D ? 0 : subresource_range.baseArrayLayer;
const uint32_t max_layers = image_state.GetArrayLayers() - base_array_layer;
const uint32_t array_layers = std::min(subresource_range.layerCount, max_layers);
const uint32_t max_levels = image_state.GetMipLevels() - subresource_range.baseMipLevel;
const uint32_t mip_levels = std::min(image_state.GetMipLevels(), max_levels);
for (uint32_t layer = 0; layer < array_layers; layer++) {
for (uint32_t level = 0; level < mip_levels; level++) {
QueueValidateImage(funcs, loc, image_state, usage, layer + base_array_layer, level + subresource_range.baseMipLevel);
}
}
}
void BestPractices::QueueValidateImage(QueueCallbacks& funcs, const Location& loc, vvl::Image& image_state,
IMAGE_SUBRESOURCE_USAGE_BP usage, const VkImageSubresourceLayers& subresource_layers) {
const uint32_t max_layers = image_state.GetArrayLayers() - subresource_layers.baseArrayLayer;
const uint32_t array_layers = std::min(subresource_layers.layerCount, max_layers);
for (uint32_t layer = 0; layer < array_layers; layer++) {
QueueValidateImage(funcs, loc, image_state, usage, layer + subresource_layers.baseArrayLayer, subresource_layers.mipLevel);
}
}
void BestPractices::QueueValidateImage(QueueCallbacks& funcs, const Location& loc, vvl::Image& image_state,
IMAGE_SUBRESOURCE_USAGE_BP usage, uint32_t array_layer, uint32_t mip_level) {
funcs.emplace_back(
[this, loc, &image_state, usage, array_layer, mip_level](const vvl::Queue& qs, const vvl::CommandBuffer& cbs) -> bool {
ValidateImageInQueue(qs, cbs, loc, image_state, usage, array_layer, mip_level);
return false;
});
}
void BestPractices::ValidateImageInQueueArmImg(const Location& loc, vvl::Image& image_state, IMAGE_SUBRESOURCE_USAGE_BP last_usage,
IMAGE_SUBRESOURCE_USAGE_BP usage, uint32_t array_layer, uint32_t mip_level) {
// Swapchain images are implicitly read so clear after store is expected.
if (usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_CLEARED && last_usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_STORED &&
!image_state.IsSwapchainImage()) {
LogPerformanceWarning("BestPractices-RenderPass-redundant-store", device, loc,
"%s %s Subresource (arrayLayer: %" PRIu32 ", mipLevel: %" PRIu32
") of image was cleared as part of LOAD_OP_CLEAR, but last time "
"image was used, it was written to with STORE_OP_STORE. "
"Storing to the image is probably redundant in this case, and wastes bandwidth on tile-based "
"architectures.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG), array_layer, mip_level);
} else if (usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_CLEARED && last_usage == IMAGE_SUBRESOURCE_USAGE_BP::CLEARED) {
LogPerformanceWarning(
"BestPractices-RenderPass-redundant-clear", device, loc,
"%s %s Subresource (arrayLayer: %" PRIu32 ", mipLevel: %" PRIu32
") of image was cleared as part of LOAD_OP_CLEAR, but last time "
"image was used, it was written to with vkCmdClear*Image(). "
"Clearing the image with vkCmdClear*Image() is probably redundant in this case, and wastes bandwidth on "
"tile-based architectures.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG), array_layer, mip_level);
} else if (usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_READ_TO_TILE &&
(last_usage == IMAGE_SUBRESOURCE_USAGE_BP::BLIT_WRITE || last_usage == IMAGE_SUBRESOURCE_USAGE_BP::CLEARED ||
last_usage == IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE || last_usage == IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_WRITE)) {
const char* last_cmd = nullptr;
const char* vuid = nullptr;
const char* suggestion = nullptr;
switch (last_usage) {
case IMAGE_SUBRESOURCE_USAGE_BP::BLIT_WRITE:
vuid = "BestPractices-RenderPass-blitimage-loadopload";
last_cmd = "vkCmdBlitImage";
suggestion =
"The blit is probably redundant in this case, and wastes bandwidth on tile-based architectures. "
"Rather than blitting, just render the source image in a fragment shader in this render pass, "
"which avoids the memory roundtrip.";
break;
case IMAGE_SUBRESOURCE_USAGE_BP::CLEARED:
vuid = "BestPractices-RenderPass-inefficient-clear";
last_cmd = "vkCmdClear*Image";
suggestion =
"Clearing the image with vkCmdClear*Image() is probably redundant in this case, and wastes bandwidth on "
"tile-based architectures. "
"Use LOAD_OP_CLEAR instead to clear the image for free.";
break;
case IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE:
vuid = "BestPractices-RenderPass-copyimage-loadopload";
last_cmd = "vkCmdCopy*Image";
suggestion =
"The copy is probably redundant in this case, and wastes bandwidth on tile-based architectures. "
"Rather than copying, just render the source image in a fragment shader in this render pass, "
"which avoids the memory roundtrip.";
break;
case IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_WRITE:
vuid = "BestPractices-RenderPass-resolveimage-loadopload";
last_cmd = "vkCmdResolveImage";
suggestion =
"The resolve is probably redundant in this case, and wastes a lot of bandwidth on tile-based architectures. "
"Rather than resolving, and then loading, try to keep rendering in the same render pass, "
"which avoids the memory roundtrip.";
break;
default:
break;
}
LogPerformanceWarning(vuid, device, loc,
"%s %s Subresource (arrayLayer: %" PRIu32 ", mipLevel: %" PRIu32
") of image was loaded to tile as part of LOAD_OP_LOAD, but last "
"time image was used, it was written to with %s. %s",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorIMG), array_layer, mip_level, last_cmd,
suggestion);
}
}
void BestPractices::ValidateImageInQueue(const vvl::Queue& qs, const vvl::CommandBuffer& cbs, const Location& loc,
vvl::Image& image_state, IMAGE_SUBRESOURCE_USAGE_BP usage, uint32_t array_layer,
uint32_t mip_level) {
auto queue_family = qs.queue_family_index;
auto& sub_state = bp_state::SubState(image_state);
auto last_usage = sub_state.UpdateUsage(array_layer, mip_level, usage, queue_family);
// Concurrent sharing usage of image with exclusive sharing mode
bool qfot_required = true;
if (enabled_features.maintenance9) {
if (image_state.GetTiling() == VK_IMAGE_TILING_LINEAR) {
qfot_required = false;
} else if ((image_state.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT |
VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT |
VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR)) == 0) {
// The list of image usages not allowed comes from
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSharingMode.html
VkQueueFamilyOwnershipTransferPropertiesKHR qfot_props = vku::InitStructHelper();
uint32_t qf_count = last_usage.queue_family_index + 1;
std::vector<VkQueueFamilyProperties2> qf_props(qf_count);
qf_props.back().pNext = &qfot_props;
DispatchGetPhysicalDeviceQueueFamilyProperties2(cbs.dev_data.physical_device, &qf_count, qf_props.data());
qfot_required = (qfot_props.optimalImageTransferToQueueFamilies & (1 << last_usage.queue_family_index)) == 0;
}
}
if (image_state.GetSharingMode() == VK_SHARING_MODE_EXCLUSIVE && last_usage.queue_family_index != queue_family &&
qfot_required) {
// if UNDEFINED then first use/acquisition of subresource
if (last_usage.type != IMAGE_SUBRESOURCE_USAGE_BP::UNDEFINED) {
// If usage might read from the subresource, as contents are undefined
// so write only is fine
if (usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_READ_TO_TILE || usage == IMAGE_SUBRESOURCE_USAGE_BP::BLIT_READ ||
usage == IMAGE_SUBRESOURCE_USAGE_BP::COPY_READ || usage == IMAGE_SUBRESOURCE_USAGE_BP::DESCRIPTOR_ACCESS ||
usage == IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_READ) {
LogWarning("BestPractices-ConcurrentUsageOfExclusiveImage", image_state.Handle(), loc,
"Subresource (arrayLayer: %" PRIu32 ", mipLevel: %" PRIu32
") of image is used on queue family index %" PRIu32 " after being used on queue family index %" PRIu32
", but has VK_SHARING_MODE_EXCLUSIVE, and has not been acquired and released with a ownership transfer "
"operation",
array_layer, mip_level, queue_family, last_usage.queue_family_index);
}
}
}
// When image was discarded with StoreOpDontCare but is now being read with LoadOpLoad
if (last_usage.type == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_DISCARDED &&
usage == IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_READ_TO_TILE) {
LogWarning("BestPractices-StoreOpDontCareThenLoadOpLoad", device, loc,
"Trying to load an attachment with LOAD_OP_LOAD that was previously stored with STORE_OP_DONT_CARE. This may "
"result in undefined behaviour.");
}
if (VendorCheckEnabled(kBPVendorArm) || VendorCheckEnabled(kBPVendorIMG)) {
ValidateImageInQueueArmImg(loc, image_state, last_usage.type, usage, array_layer, mip_level);
}
}
void BestPractices::Created(vvl::Image& image_state) {
image_state.SetSubState(container_type, std::make_unique<bp_state::ImageSubState>(image_state));
}