blob: a0e8597c240276a1903481b7294efac42cb62a6a [file] [log] [blame]
/* Copyright (c) 2015-2023 The Khronos Group Inc.
* Copyright (c) 2015-2023 Valve Corporation
* Copyright (c) 2015-2023 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/best_practices_error_enums.h"
bool BestPractices::PreCallValidateCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool,
const ErrorObject& error_obj) const {
bool skip = false;
if (pCreateInfo->flags & VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) {
skip |= LogPerformanceWarning(kVUID_BestPractices_CreateCommandPool_CommandBufferReset, device, error_obj.location,
"VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT is set. Consider resetting entire "
"pool instead.");
}
return skip;
}
bool BestPractices::PreCallValidateAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo,
VkCommandBuffer* pCommandBuffers, const ErrorObject& error_obj) const {
bool skip = false;
auto cp_state = Get<COMMAND_POOL_STATE>(pAllocateInfo->commandPool);
if (!cp_state) return false;
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(kVUID_BestPractices_AllocateCommandBuffers_UnusableSecondary, device, error_obj.location,
"Allocating secondary level command buffer from command pool "
"created against queue family #%u (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;
}
void BestPractices::PreCallRecordBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo) {
StateTracker::PreCallRecordBeginCommandBuffer(commandBuffer, pBeginInfo);
auto cb = GetWrite<bp_state::CommandBuffer>(commandBuffer);
if (!cb) return;
cb->num_submits = 0;
cb->is_one_time_submit = (pBeginInfo->flags & VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) != 0;
}
bool BestPractices::PreCallValidateBeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo,
const ErrorObject& error_obj) const {
bool skip = false;
if (pBeginInfo->flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT) {
skip |= LogPerformanceWarning(kVUID_BestPractices_BeginCommandBuffer_SimultaneousUse, device, error_obj.location,
"VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT is set.");
}
if (VendorCheckEnabled(kBPVendorArm)) {
if (!(pBeginInfo->flags & VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)) {
skip |= LogPerformanceWarning(kVUID_BestPractices_BeginCommandBuffer_OneTimeSubmit, device, error_obj.location,
"%s VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT is not set. "
"For best performance on Mali GPUs, consider setting ONE_TIME_SUBMIT by default.",
VendorSpecificTag(kBPVendorArm));
}
}
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
auto cb = GetRead<bp_state::CommandBuffer>(commandBuffer);
if (cb->num_submits == 1 && !cb->is_one_time_submit) {
skip |= LogPerformanceWarning(kVUID_BestPractices_BeginCommandBuffer_OneTimeSubmit, device, error_obj.location,
"%s VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT was not set "
"and the command buffer has only been submitted once. "
"For best performance on NVIDIA GPUs, use ONE_TIME_SUBMIT.",
VendorSpecificTag(kBPVendorNVIDIA));
}
}
return skip;
}
bool BestPractices::PreCallValidateCmdWriteTimestamp(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage,
VkQueryPool queryPool, uint32_t query, const ErrorObject& error_obj) const {
bool skip = false;
skip |= CheckPipelineStageFlags(error_obj.location.dot(Field::pipelineStage), static_cast<VkPipelineStageFlags>(pipelineStage));
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::PreCallValidateCmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 pipelineStage,
VkQueryPool queryPool, uint32_t query, const ErrorObject& error_obj) const {
bool skip = false;
skip |= CheckPipelineStageFlags(error_obj.location.dot(Field::pipelineStage), pipelineStage);
return skip;
}
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<QUERY_POOL_STATE>(queryPool);
for (uint32_t i = firstQuery; i < firstQuery + queryCount; ++i) {
if (query_pool_state.GetQueryState(i, 0u) == QUERYSTATE_RESET) {
const LogObjectList objlist(queryPool);
skip |= LogWarning(kVUID_BestPractices_QueryPool_Unavailable, objlist, error_obj.location,
"QueryPool %s and query %" PRIu32 ": vkCmdBeginQuery() was never called.",
FormatHandle(query_pool_state.pool()).c_str(), i);
break;
}
}
return skip;
}
void BestPractices::PreCallRecordCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
StateTracker::PreCallRecordCmdSetDepthCompareOp(commandBuffer, depthCompareOp);
auto cb = GetWrite<bp_state::CommandBuffer>(commandBuffer);
assert(cb);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
RecordSetDepthTestState(*cb, depthCompareOp, cb->nv.depth_test_enable);
}
}
void BestPractices::PreCallRecordCmdSetDepthCompareOpEXT(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
PreCallRecordCmdSetDepthCompareOp(commandBuffer, depthCompareOp);
}
void BestPractices::PreCallRecordCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
StateTracker::PreCallRecordCmdSetDepthTestEnable(commandBuffer, depthTestEnable);
auto cb = GetWrite<bp_state::CommandBuffer>(commandBuffer);
assert(cb);
if (VendorCheckEnabled(kBPVendorNVIDIA)) {
RecordSetDepthTestState(*cb, cb->nv.depth_compare_op, depthTestEnable != VK_FALSE);
}
}
void BestPractices::PreCallRecordCmdSetDepthTestEnableEXT(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
PreCallRecordCmdSetDepthTestEnable(commandBuffer, depthTestEnable);
}
bool BestPractices::PreCallValidateCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount,
const VkCommandBuffer* pCommandBuffers, const ErrorObject& error_obj) const {
bool skip = false;
const auto primary = GetRead<bp_state::CommandBuffer>(commandBuffer);
for (uint32_t i = 0; i < commandBufferCount; i++) {
const auto secondary_cb = GetRead<bp_state::CommandBuffer>(pCommandBuffers[i]);
if (secondary_cb == nullptr) {
continue;
}
const auto& secondary = secondary_cb->render_pass_state;
for (auto& clear : secondary.earlyClearAttachments) {
if (ClearAttachmentsIsFullClear(*primary, uint32_t(clear.rects.size()), clear.rects.data())) {
skip |= ValidateClearAttachment(*primary, clear.framebufferAttachment, clear.colorAttachment, clear.aspects,
error_obj.location);
}
}
if (!(secondary_cb->beginInfo.flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)) {
if (primary->beginInfo.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(kVUID_BestPractices_DrawState_InvalidCommandBufferSimultaneousUse, objlist, error_obj.location,
"pCommandBuffers[%" PRIu32
"] %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.",
i, FormatHandle(pCommandBuffers[i]).c_str(), FormatHandle(commandBuffer).c_str());
}
}
}
if (VendorCheckEnabled(kBPVendorAMD)) {
if (commandBufferCount > 0) {
skip |= LogPerformanceWarning(kVUID_BestPractices_CmdBuffer_AvoidSecondaryCmdBuffers, device, error_obj.location,
"%s Performance warning: Use of secondary command buffers is not recommended. ",
VendorSpecificTag(kBPVendorAMD));
}
}
return skip;
}
void BestPractices::PreCallRecordCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount,
const VkCommandBuffer* pCommandBuffers) {
ValidationStateTracker::PreCallRecordCmdExecuteCommands(commandBuffer, commandBufferCount, pCommandBuffers);
auto primary = GetWrite<bp_state::CommandBuffer>(commandBuffer);
if (!primary) {
return;
}
for (uint32_t i = 0; i < commandBufferCount; i++) {
auto secondary = GetWrite<bp_state::CommandBuffer>(pCommandBuffers[i]);
if (!secondary) {
continue;
}
for (auto& early_clear : secondary->render_pass_state.earlyClearAttachments) {
if (ClearAttachmentsIsFullClear(*primary, uint32_t(early_clear.rects.size()), early_clear.rects.data())) {
RecordAttachmentClearAttachments(*primary, early_clear.framebufferAttachment, early_clear.colorAttachment,
early_clear.aspects, uint32_t(early_clear.rects.size()), early_clear.rects.data());
} else {
RecordAttachmentAccess(*primary, early_clear.framebufferAttachment, early_clear.aspects);
}
}
for (auto& touch : secondary->render_pass_state.touchesAttachments) {
RecordAttachmentAccess(*primary, touch.framebufferAttachment, touch.aspects);
}
primary->render_pass_state.numDrawCallsDepthEqualCompare += secondary->render_pass_state.numDrawCallsDepthEqualCompare;
primary->render_pass_state.numDrawCallsDepthOnly += secondary->render_pass_state.numDrawCallsDepthOnly;
}
}