| /* 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; |
| } |