blob: 08c17df523d3ac58a6eb92da909da4ac6ea45be4 [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 "generated/dispatch_functions.h"
#include "best_practices/bp_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/queue_state.h"
#include "state_tracker/device_state.h"
bool bp_state::Instance::ValidateSpecialUseExtensions(const Location& loc, vvl::Extension extension) const {
bool skip = false;
const std::string special_uses = GetSpecialUse(extension);
// We don't report "devtools" or "debugging" because if the user is using the validation layers, they likely want these
// extensions and giving a warning/info about it is just noise.
if (!special_uses.empty()) {
const char* const format =
"Attempting to enable extension %s, but this extension is intended to support %s "
"and it is strongly recommended that it be otherwise avoided.";
const char* vuid = "BestPractices-specialuse-extension";
if (special_uses.find("cadsupport") != std::string::npos) {
skip |= LogWarning(vuid, instance, loc, format, String(extension),
"specialized functionality used by CAD/CAM applications");
}
if (special_uses.find("d3demulation") != std::string::npos) {
skip |= LogWarning(vuid, instance, loc, format, String(extension),
"D3D emulation layers, and applications ported from D3D, by adding functionality specific to D3D");
}
if (special_uses.find("glemulation") != std::string::npos) {
skip |= LogWarning(
vuid, instance, loc, format, String(extension),
"OpenGL and/or OpenGL ES emulation layers, and applications ported from those APIs, by adding functionality "
"specific to those APIs");
}
}
return skip;
}
bool bp_state::Instance::PreCallValidateCreateInstance(const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkInstance* pInstance,
const ErrorObject& error_obj) const {
bool skip = false;
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
if (IsDeviceExtension(extension)) {
skip |= LogWarning("BestPractices-vkCreateInstance-extension-mismatch", instance, error_obj.location,
"Attempting to enable Device Extension %s at CreateInstance time.", String(extension));
}
skip |= ValidateSpecialUseExtensions(error_obj.location, extension);
}
return skip;
}
bool bp_state::Instance::PreCallValidateCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkDevice* pDevice,
const ErrorObject& error_obj) const {
bool skip = false;
// get API version of physical device passed when creating device.
VkPhysicalDeviceProperties physical_device_properties{};
DispatchGetPhysicalDeviceProperties(physicalDevice, &physical_device_properties);
auto device_api_version = physical_device_properties.apiVersion;
// Check api versions and log an info message when instance api Version is greater than version on device.
if (api_version > device_api_version) {
std::string inst_api_name = StringAPIVersion(api_version);
std::string dev_api_name = StringAPIVersion(device_api_version);
LogInfo("BestPractices-vkCreateDevice-API-version-mismatch", instance, error_obj.location,
"API Version of current instance, %s is greater than API Version on device, %s", inst_api_name.c_str(),
dev_api_name.c_str());
}
std::vector<std::string> extension_names;
{
uint32_t property_count = 0;
if (DispatchEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &property_count, nullptr) == VK_SUCCESS) {
std::vector<VkExtensionProperties> property_list(property_count);
if (DispatchEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &property_count, property_list.data()) ==
VK_SUCCESS) {
extension_names.reserve(property_list.size());
for (const VkExtensionProperties& properties : property_list) {
extension_names.emplace_back(properties.extensionName);
}
}
}
}
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
const char* extension_name = pCreateInfo->ppEnabledExtensionNames[i];
vvl::Extension extension = GetExtension(extension_name);
if (IsInstanceExtension(extension)) {
skip |= LogWarning("BestPractices-vkCreateDevice-extension-mismatch", instance, error_obj.location,
"Attempting to enable Instance Extension %s at CreateDevice time.", String(extension));
}
skip |= ValidateSpecialUseExtensions(error_obj.location, extension);
}
if (pCreateInfo->pEnabledFeatures) {
if (const auto bp_pd_state = Get<vvl::PhysicalDevice>(physicalDevice)) {
if (bp_pd_state->WasUncalled(vvl::Func::vkGetPhysicalDeviceFeatures) &&
bp_pd_state->WasUncalled(vvl::Func::vkGetPhysicalDeviceFeatures2) &&
bp_pd_state->WasUncalled(vvl::Func::vkGetPhysicalDeviceFeatures2KHR)) {
skip |=
LogWarning("BestPractices-vkCreateDevice-physical-device-features-not-retrieved", instance, error_obj.location,
"called before getting physical device features from vkGetPhysicalDeviceFeatures or "
"vkGetPhysicalDeviceFeatures2.");
}
}
if ((VendorCheckEnabled(kBPVendorArm) || VendorCheckEnabled(kBPVendorAMD) || VendorCheckEnabled(kBPVendorIMG)) &&
pCreateInfo->pEnabledFeatures->robustBufferAccess) {
skip |= LogPerformanceWarning(
"BestPractices-vkCreateDevice-RobustBufferAccess", instance, error_obj.location,
"%s %s %s: called with enabled robustBufferAccess. Use robustBufferAccess as a debugging tool during "
"development. Enabling it causes loss in performance for accesses to uniform buffers and shader storage "
"buffers. Disable robustBufferAccess in release builds. Only leave it enabled if the application use-case "
"requires the additional level of reliability due to the use of unverified user-supplied draw parameters.",
VendorSpecificTag(kBPVendorArm), VendorSpecificTag(kBPVendorAMD), VendorSpecificTag(kBPVendorIMG));
}
}
const bool enabled_pageable_device_local_memory = IsExtEnabled(extensions.vk_ext_pageable_device_local_memory);
if (VendorCheckEnabled(kBPVendorNVIDIA) && !enabled_pageable_device_local_memory &&
std::find(extension_names.begin(), extension_names.end(), VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME) !=
extension_names.end()) {
skip |=
LogPerformanceWarning("BestPractices-NVIDIA-CreateDevice-PageableDeviceLocalMemory", instance, error_obj.location,
"%s called without pageable device local memory. "
"Use pageableDeviceLocalMemory from VK_EXT_pageable_device_local_memory when it is available.",
VendorSpecificTag(kBPVendorNVIDIA));
}
return skip;
}
// Common function to handle validation for GetPhysicalDeviceQueueFamilyProperties & 2KHR version
bool bp_state::Instance::ValidateCommonGetPhysicalDeviceQueueFamilyProperties(const vvl::PhysicalDevice& bp_pd_state,
uint32_t requested_queue_family_property_count,
const Location& loc) const {
bool skip = false;
if (bp_pd_state.queue_family_known_count != requested_queue_family_property_count) {
skip |= LogWarning("BestPractices-GetPhysicalDeviceQueueFamilyProperties-CountMismatch", bp_pd_state.Handle(), loc,
"is called with non-NULL pQueueFamilyProperties and pQueueFamilyPropertyCount value %" PRIu32
", but the largest previously returned pQueueFamilyPropertyCount for this physicalDevice is %" PRIu32
". It is recommended to instead receive all the properties by calling %s with "
"pQueueFamilyPropertyCount that was "
"previously obtained by calling %s with NULL pQueueFamilyProperties.",
requested_queue_family_property_count, bp_pd_state.queue_family_known_count, loc.StringFunc(),
loc.StringFunc());
}
return skip;
}
bool bp_state::Instance::PreCallValidateGetPhysicalDeviceQueueFamilyProperties(VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties* pQueueFamilyProperties,
const ErrorObject& error_obj) const {
const auto bp_pd_state = Get<vvl::PhysicalDevice>(physicalDevice);
if (pQueueFamilyProperties && bp_pd_state) {
return ValidateCommonGetPhysicalDeviceQueueFamilyProperties(*bp_pd_state, *pQueueFamilyPropertyCount, error_obj.location);
}
return false;
}
bool bp_state::Instance::PreCallValidateGetPhysicalDeviceQueueFamilyProperties2(VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties2* pQueueFamilyProperties,
const ErrorObject& error_obj) const {
const auto bp_pd_state = Get<vvl::PhysicalDevice>(physicalDevice);
if (pQueueFamilyProperties && bp_pd_state) {
return ValidateCommonGetPhysicalDeviceQueueFamilyProperties(*bp_pd_state, *pQueueFamilyPropertyCount, error_obj.location);
}
return false;
}
bool bp_state::Instance::PreCallValidateGetPhysicalDeviceQueueFamilyProperties2KHR(VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties2* pQueueFamilyProperties,
const ErrorObject& error_obj) const {
return PreCallValidateGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties,
error_obj);
}
ReadLockGuard BestPractices::ReadLock() const {
if (global_settings.fine_grained_locking) {
return ReadLockGuard(validation_object_mutex, std::defer_lock);
} else {
return ReadLockGuard(validation_object_mutex);
}
}
WriteLockGuard BestPractices::WriteLock() {
if (global_settings.fine_grained_locking) {
return WriteLockGuard(validation_object_mutex, std::defer_lock);
} else {
return WriteLockGuard(validation_object_mutex);
}
}
void BestPractices::PreCallRecordQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence,
const RecordObject& record_obj) {
auto queue_state = Get<vvl::Queue>(queue);
for (uint32_t submit = 0; submit < submitCount; submit++) {
const auto& submit_info = pSubmits[submit];
for (uint32_t cb_index = 0; cb_index < submit_info.commandBufferCount; cb_index++) {
auto cb = GetWrite<vvl::CommandBuffer>(submit_info.pCommandBuffers[cb_index]);
auto& sub_state = bp_state::SubState(*cb);
sub_state.num_submits++;
}
}
}
namespace {
struct EventValidator {
const BestPractices& bp;
vvl::unordered_map<VkEvent, bool> signaling_state;
EventValidator(const BestPractices& bp_) : bp(bp_) {}
bool ValidateSubmittedCbSignalingState(const bp_state::CommandBufferSubState& cb, const Location& cb_loc) {
bool skip = false;
for (const auto& [event, info] : cb.event_signaling_state) {
if (info.first_state_change_is_signal) {
bool signaled = false;
if (auto* p_signaled = vvl::Find(signaling_state, event)) {
// at first check local tracking map
signaled = *p_signaled;
} else if (auto event_state = bp.Get<vvl::Event>(event)) {
// then check global event state
signaled = event_state->signaled;
}
if (signaled) {
const LogObjectList objlist(cb.VkHandle(), event);
skip |= bp.LogWarning(
"BestPractices-Event-SignalSignaledEvent", objlist, cb_loc,
"%s sets event %s which is already in the signaled state (set by previously submitted command buffers or "
"from the host). If this is not the desired behavior, the event must be reset before it is set again.",
bp.FormatHandle(cb.VkHandle()).c_str(), bp.FormatHandle(event).c_str());
}
}
signaling_state[event] = info.signaled;
}
return skip;
}
};
} // namespace
bool BestPractices::PreCallValidateQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence,
const ErrorObject& error_obj) const {
bool skip = false;
EventValidator event_validator(*this);
for (uint32_t submit = 0; submit < submitCount; submit++) {
const Location submit_loc = error_obj.location.dot(Field::pSubmits, submit);
for (uint32_t cb_index = 0; cb_index < pSubmits[submit].commandBufferCount; cb_index++) {
if (auto cb_state = GetRead<vvl::CommandBuffer>(pSubmits[submit].pCommandBuffers[cb_index])) {
const Location cb_loc = submit_loc.dot(vvl::Field::pCommandBuffers, cb_index);
const auto& sub_state = bp_state::SubState(*cb_state);
skip |= event_validator.ValidateSubmittedCbSignalingState(sub_state, cb_loc);
}
}
}
return skip;
}
bool BestPractices::PreCallValidateQueueSubmit2KHR(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2KHR* pSubmits,
VkFence fence, const ErrorObject& error_obj) const {
return PreCallValidateQueueSubmit2(queue, submitCount, pSubmits, fence, error_obj);
}
bool BestPractices::PreCallValidateQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence,
const ErrorObject& error_obj) const {
bool skip = false;
EventValidator event_validator(*this);
for (uint32_t submit = 0; submit < submitCount; submit++) {
const Location submit_loc = error_obj.location.dot(Field::pSubmits, submit);
for (uint32_t cb_index = 0; cb_index < pSubmits[submit].commandBufferInfoCount; cb_index++) {
if (auto cb_state = GetRead<vvl::CommandBuffer>(pSubmits[submit].pCommandBufferInfos[cb_index].commandBuffer)) {
const Location infos_loc = submit_loc.dot(vvl::Field::pCommandBufferInfos, cb_index);
const Location cb_loc = infos_loc.dot(vvl::Field::commandBuffer);
const auto& sub_state = bp_state::SubState(*cb_state);
skip |= event_validator.ValidateSubmittedCbSignalingState(sub_state, cb_loc);
}
}
}
return skip;
}
bool BestPractices::PreCallValidateQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo,
VkFence fence, const ErrorObject& error_obj) const {
bool skip = false;
for (uint32_t bind_idx = 0; bind_idx < bindInfoCount; bind_idx++) {
const Location bind_info_loc = error_obj.location.dot(Field::pBindInfo, bind_idx);
const VkBindSparseInfo& bind_info = pBindInfo[bind_idx];
// Store sparse binding image_state and after binding is complete make sure that any requiring metadata have it bound
vvl::unordered_set<const bp_state::ImageSubState*> sparse_images;
// Track images getting metadata bound by this call in a set, it'll be recorded into the image_state
// in RecordQueueBindSparse.
vvl::unordered_set<const bp_state::ImageSubState*> sparse_images_with_metadata;
// If we're binding sparse image memory make sure reqs were queried and note if metadata is required and bound
for (uint32_t i = 0; i < bind_info.imageBindCount; ++i) {
const auto& image_bind = bind_info.pImageBinds[i];
auto image_state = Get<vvl::Image>(image_bind.image);
if (!image_state) {
continue; // Param/Object validation should report image_bind.image handles being invalid, so just skip here.
}
const auto& image_sub_state = bp_state::SubState(*image_state);
sparse_images.insert(&image_sub_state);
if (image_state->sparse_residency) {
if (!image_sub_state.get_sparse_reqs_called || image_state->sparse_requirements.empty()) {
// For now just warning if sparse image binding occurs without calling to get reqs first
skip |= LogWarning("BestPractices-vkQueueBindSparse-image-requirements2", image_state->Handle(),
bind_info_loc.dot(Field::pImageBinds, i),
"Binding sparse memory to %s without first calling "
"vkGetImageSparseMemoryRequirements[2KHR]() to retrieve requirements.",
FormatHandle(image_state->Handle()).c_str());
}
}
if (!image_sub_state.memory_requirements_checked[0]) {
// For now just warning if sparse image binding occurs without calling to get reqs first
skip |= LogWarning("BestPractices-vkQueueBindSparse-image-requirements", image_state->Handle(),
bind_info_loc.dot(Field::pImageBinds, i),
"Binding sparse memory to %s without first calling "
"vkGetImageMemoryRequirements() to retrieve requirements.",
FormatHandle(image_state->Handle()).c_str());
}
}
for (uint32_t i = 0; i < bind_info.imageOpaqueBindCount; ++i) {
const auto& image_opaque_bind = bind_info.pImageOpaqueBinds[i];
auto image_state = Get<vvl::Image>(bind_info.pImageOpaqueBinds[i].image);
if (!image_state) {
continue; // Param/Object validation should report image_bind.image handles being invalid, so just skip here.
}
const auto& image_sub_state = bp_state::SubState(*image_state);
sparse_images.insert(&image_sub_state);
if (image_state->sparse_residency) {
if (!image_sub_state.get_sparse_reqs_called || image_state->sparse_requirements.empty()) {
// For now just warning if sparse image binding occurs without calling to get reqs first
skip |= LogWarning("BestPractices-vkQueueBindSparse-image-opaque-requirements2", image_state->Handle(),
bind_info_loc.dot(Field::pImageOpaqueBinds, i),
"Binding opaque sparse memory to %s without first calling "
"vkGetImageSparseMemoryRequirements[2KHR]() to retrieve requirements.",
FormatHandle(image_state->Handle()).c_str());
}
}
if (!image_sub_state.memory_requirements_checked[0]) {
// For now just warning if sparse image binding occurs without calling to get reqs first
skip |= LogWarning("BestPractices-vkQueueBindSparse-image-opaque-requirements", image_state->Handle(),
bind_info_loc.dot(Field::pImageOpaqueBinds, i),
"Binding opaque sparse memory to %s without first calling "
"vkGetImageMemoryRequirements() to retrieve requirements.",
FormatHandle(image_state->Handle()).c_str());
}
for (uint32_t j = 0; j < image_opaque_bind.bindCount; ++j) {
if (image_opaque_bind.pBinds[j].flags & VK_SPARSE_MEMORY_BIND_METADATA_BIT) {
sparse_images_with_metadata.insert(&image_sub_state);
}
}
}
for (const auto& sparse_image_state : sparse_images) {
if (sparse_image_state->sparse_metadata_required && !sparse_image_state->sparse_metadata_bound &&
sparse_images_with_metadata.find(sparse_image_state) == sparse_images_with_metadata.end()) {
// Warn if sparse image binding metadata required for image with sparse binding, but metadata not bound
skip |= LogWarning("BestPractices-vkQueueBindSparse-image-metadata-requirements", sparse_image_state->base.Handle(),
bind_info_loc,
"Binding sparse memory to %s which requires a metadata aspect but no "
"binding with VK_SPARSE_MEMORY_BIND_METADATA_BIT set was made.",
FormatHandle(sparse_image_state->base.Handle()).c_str());
}
}
}
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
auto queue_state = Get<vvl::Queue>(queue);
if (queue_state && queue_state->GetQueueFlags() != (VK_QUEUE_TRANSFER_BIT | VK_QUEUE_SPARSE_BINDING_BIT)) {
skip |= LogPerformanceWarning("BestPractices-NVIDIA-QueueBindSparse-NotAsync", queue, error_obj.location,
"issued on queue %s. All binds should happen on an asynchronous copy "
"queue to hide the OS scheduling and submit costs.",
FormatHandle(queue).c_str());
}
}
return skip;
}
void BestPractices::ManualPostCallRecordQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo,
VkFence fence, const RecordObject& record_obj) {
if (record_obj.result != VK_SUCCESS) {
return;
}
for (uint32_t bind_idx = 0; bind_idx < bindInfoCount; bind_idx++) {
const VkBindSparseInfo& bind_info = pBindInfo[bind_idx];
for (uint32_t i = 0; i < bind_info.imageOpaqueBindCount; ++i) {
const auto& image_opaque_bind = bind_info.pImageOpaqueBinds[i];
auto image_state = Get<vvl::Image>(bind_info.pImageOpaqueBinds[i].image);
if (!image_state) {
continue; // Param/Object validation should report image_bind.image handles being invalid, so just skip here.
}
auto& image_sub_state = bp_state::SubState(*image_state);
for (uint32_t j = 0; j < image_opaque_bind.bindCount; ++j) {
if (image_opaque_bind.pBinds[j].flags & VK_SPARSE_MEMORY_BIND_METADATA_BIT) {
image_sub_state.sparse_metadata_bound = true;
}
}
}
}
}
void BestPractices::ManualPostCallRecordQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits,
VkFence fence, const RecordObject& record_obj) {
// We ignore the VkResult because we want to call the total attempted queue submit calls
// AMD best practice
num_queue_submissions_ += submitCount;
}