| /* 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/device_state.h" |
| |
| void BestPractices::Created(vvl::CommandBuffer& cb_state) { |
| cb_state.SetSubState(container_type, std::make_unique<bp_state::CommandBufferSubState>(cb_state, *this)); |
| } |
| |
| bool BestPractices::PreCallValidateAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, |
| VkCommandBuffer* pCommandBuffers, const ErrorObject& error_obj) const { |
| bool skip = false; |
| |
| auto cp_state = Get<vvl::CommandPool>(pAllocateInfo->commandPool); |
| ASSERT_AND_RETURN_SKIP(cp_state); |
| |
| const VkQueueFlags queue_flags = physical_device_state->queue_family_properties[cp_state->queueFamilyIndex].queueFlags; |
| const VkQueueFlags sec_cmd_buf_queue_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT; |
| |
| if (pAllocateInfo->level == VK_COMMAND_BUFFER_LEVEL_SECONDARY && (queue_flags & sec_cmd_buf_queue_flags) == 0) { |
| skip |= LogWarning("BestPractices-vkAllocateCommandBuffers-unusable-secondary", device, error_obj.location, |
| "Allocating secondary level command buffer from command pool " |
| "created against queue family %" PRIu32 |
| " (queue flags: %s), but vkCmdExecuteCommands() is only " |
| "supported on queue families supporting VK_QUEUE_GRAPHICS_BIT, VK_QUEUE_COMPUTE_BIT, or " |
| "VK_QUEUE_TRANSFER_BIT. The allocated command buffer will not be submittable.", |
| cp_state->queueFamilyIndex, string_VkQueueFlags(queue_flags).c_str()); |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::PreCallValidateBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo, |
| const ErrorObject& error_obj) const { |
| bool skip = false; |
| |
| const bool is_one_time_submit = (pBeginInfo->flags & VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) != 0; |
| if (VendorCheckEnabled(kBPVendorArm)) { |
| if (!is_one_time_submit) { |
| skip |= |
| LogPerformanceWarning("BestPractices-Arm-vkBeginCommandBuffer-one-time-submit", device, |
| error_obj.location.dot(Field::pBeginInfo).dot(Field::flags), |
| "(%s) doesn't have VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT set. %s For best performance " |
| "on Mali GPUs, consider setting ONE_TIME_SUBMIT by default.", |
| string_VkCommandBufferUsageFlags(pBeginInfo->flags).c_str(), VendorSpecificTag(kBPVendorArm)); |
| } |
| } |
| if (VendorCheckEnabled(kBPVendorNVIDIA)) { |
| auto cb_state = GetRead<vvl::CommandBuffer>(commandBuffer); |
| const auto& sub_state = bp_state::SubState(*cb_state); |
| if (sub_state.num_submits == 1 && !is_one_time_submit) { |
| skip |= LogPerformanceWarning("BestPractices-NVIDIA-vkBeginCommandBuffer-one-time-submit", device, |
| error_obj.location.dot(Field::pBeginInfo).dot(Field::flags), |
| "(%s) doesn't have VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT set " |
| "and the command buffer has only been submitted once. %s " |
| "For best performance on NVIDIA GPUs, use ONE_TIME_SUBMIT.", |
| string_VkCommandBufferUsageFlags(pBeginInfo->flags).c_str(), |
| VendorSpecificTag(kBPVendorNVIDIA)); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::PreCallValidateCmdWriteTimestamp2KHR(VkCommandBuffer commandBuffer, VkPipelineStageFlags2KHR pipelineStage, |
| VkQueryPool queryPool, uint32_t query, |
| const ErrorObject& error_obj) const { |
| return PreCallValidateCmdWriteTimestamp2(commandBuffer, pipelineStage, queryPool, query, error_obj); |
| } |
| |
| bool BestPractices::PreCallValidateGetQueryPoolResults(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, |
| uint32_t queryCount, size_t dataSize, void* pData, VkDeviceSize stride, |
| VkQueryResultFlags flags, const ErrorObject& error_obj) const { |
| bool skip = false; |
| |
| const auto query_pool_state = Get<vvl::QueryPool>(queryPool); |
| ASSERT_AND_RETURN_SKIP(query_pool_state); |
| |
| for (uint32_t i = firstQuery; i < firstQuery + queryCount; ++i) { |
| // Some query type can't have a begin call on it (see VUID-vkCmdBeginQuery-queryType-02804) |
| const bool can_have_begin = |
| !IsValueIn(query_pool_state->create_info.queryType, |
| {VK_QUERY_TYPE_TIMESTAMP, VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR, |
| VK_QUERY_TYPE_ACCELERATION_STRUCTURE_SERIALIZATION_SIZE_KHR, VK_QUERY_TYPE_ACCELERATION_STRUCTURE_SIZE_KHR, |
| VK_QUERY_TYPE_ACCELERATION_STRUCTURE_SERIALIZATION_BOTTOM_LEVEL_POINTERS_KHR, |
| VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_NV}); |
| if (can_have_begin && query_pool_state->GetQueryState(i, 0u) == QUERYSTATE_RESET) { |
| const LogObjectList objlist(queryPool); |
| skip |= LogWarning("BestPractices-QueryPool-Unavailable", objlist, error_obj.location, |
| "QueryPool %s and query %" PRIu32 ": vkCmdBeginQuery() was never called.", |
| FormatHandle(query_pool_state->Handle()).c_str(), i); |
| break; |
| } |
| } |
| |
| return skip; |
| } |
| |
| namespace { |
| struct EventValidator { |
| const Logger& log; |
| vvl::unordered_map<VkEvent, bool> signaling_state; |
| |
| explicit EventValidator(const Logger& log_) : log(log_) {} |
| |
| bool ValidateSecondaryCbSignalingState(const bp_state::CommandBufferSubState& primary_cb, |
| const bp_state::CommandBufferSubState& secondary_cb, const Location& secondary_cb_loc) { |
| bool skip = false; |
| for (const auto& [event, signaling_info] : secondary_cb.event_signaling_state) { |
| if (signaling_info.first_state_change_is_signal) { |
| bool signaled = false; |
| if (auto* p_signaled = vvl::Find(signaling_state, event)) { |
| // check local tracking map |
| signaled = *p_signaled; |
| } else if (auto* primary_signal_info = vvl::Find(primary_cb.event_signaling_state, event)) { |
| // check parent command buffer |
| signaled = primary_signal_info->signaled; |
| } |
| if (signaled) { |
| // the most recent state update was signal (signaled == true) and the secondary |
| // command buffer starts with a signal too (first_state_change_is_signal). |
| const LogObjectList objlist(primary_cb.VkHandle(), secondary_cb.VkHandle(), event); |
| skip |= log.LogWarning( |
| "BestPractices-Event-SignalSignaledEvent", objlist, secondary_cb_loc, |
| "%s sets event %s which was already set (in the primary command buffer %s or in the executed secondary " |
| "command buffers). If this is not the desired behavior, the event must be reset before it is set again.", |
| log.FormatHandle(secondary_cb.VkHandle()).c_str(), log.FormatHandle(event).c_str(), |
| log.FormatHandle(primary_cb.VkHandle()).c_str()); |
| } |
| } |
| signaling_state[event] = signaling_info.signaled; |
| } |
| return skip; |
| } |
| }; |
| } // namespace |
| |
| bool BestPractices::PreCallValidateCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, |
| const VkCommandBuffer* pCommandBuffers, const ErrorObject& error_obj) const { |
| bool skip = false; |
| EventValidator event_validator(*this); |
| const auto primary = GetRead<vvl::CommandBuffer>(commandBuffer); |
| const auto& primary_sub_state = bp_state::SubState(*primary); |
| for (uint32_t i = 0; i < commandBufferCount; i++) { |
| const auto secondary_cb = GetRead<vvl::CommandBuffer>(pCommandBuffers[i]); |
| if (secondary_cb == nullptr) { |
| continue; |
| } |
| const auto& secondary_sub_state = bp_state::SubState(*secondary_cb); |
| const Location& cb_loc = error_obj.location.dot(Field::pCommandBuffers, i); |
| const auto& secondary = secondary_sub_state.render_pass_state; |
| for (auto& clear : secondary.earlyClearAttachments) { |
| if (ClearAttachmentsIsFullClear(primary_sub_state, uint32_t(clear.rects.size()), clear.rects.data())) { |
| skip |= ValidateClearAttachment(primary_sub_state, clear.framebufferAttachment, clear.colorAttachment, |
| clear.aspects, cb_loc); |
| } |
| } |
| |
| if (!(secondary_cb->begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) { |
| if (primary->begin_info_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT) { |
| // Warn that non-simultaneous secondary cmd buffer renders primary non-simultaneous |
| const LogObjectList objlist(commandBuffer, pCommandBuffers[i]); |
| skip |= LogWarning("BestPractices-vkCmdExecuteCommands-CommandBufferSimultaneousUse", objlist, cb_loc, |
| "(%s) does not have " |
| "VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT set and will cause primary " |
| "(%s) to be treated as if it does not have " |
| "VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT set, even though it does.", |
| FormatHandle(pCommandBuffers[i]).c_str(), FormatHandle(commandBuffer).c_str()); |
| } |
| } |
| skip |= event_validator.ValidateSecondaryCbSignalingState(primary_sub_state, secondary_sub_state, cb_loc); |
| } |
| |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| if (commandBufferCount > 0) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-VkCommandBuffer-AvoidSecondaryCmdBuffers", commandBuffer, |
| error_obj.location, "%s Use of secondary command buffers is not recommended.", |
| VendorSpecificTag(kBPVendorAMD)); |
| } |
| } |
| return skip; |
| } |