blob: 63d88246b4ed3502ba3e95fe86aef42df4ff2dbf [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/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;
}