| /* 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 "generated/spirv_grammar_helper.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "chassis/chassis_modification_state.h" |
| #include "utils/math_utils.h" |
| |
| static inline bool FormatHasFullThroughputBlendingArm(VkFormat format) { |
| switch (format) { |
| case VK_FORMAT_B10G11R11_UFLOAT_PACK32: |
| case VK_FORMAT_R16_SFLOAT: |
| case VK_FORMAT_R16G16_SFLOAT: |
| case VK_FORMAT_R16G16B16_SFLOAT: |
| case VK_FORMAT_R16G16B16A16_SFLOAT: |
| case VK_FORMAT_R32_SFLOAT: |
| case VK_FORMAT_R32G32_SFLOAT: |
| case VK_FORMAT_R32G32B32_SFLOAT: |
| case VK_FORMAT_R32G32B32A32_SFLOAT: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| bool BestPractices::ValidateMultisampledBlendingArm(const vvl::Pipeline& pipeline, const Location& create_info_loc) const { |
| bool skip = false; |
| |
| const auto* color_blend_state = pipeline.ColorBlendState(); |
| const auto* ms_state = pipeline.MultisampleState(); |
| if (!color_blend_state || !ms_state || ms_state->rasterizationSamples == VK_SAMPLE_COUNT_1_BIT || |
| ms_state->sampleShadingEnable) { |
| return skip; |
| } |
| |
| auto rp_state = Get<vvl::RenderPass>(pipeline.GraphicsCreateInfo().renderPass); |
| if (!rp_state) { |
| return skip; |
| } |
| |
| const auto& subpass = rp_state->create_info.pSubpasses[pipeline.Subpass()]; |
| |
| // According to spec, pColorBlendState must be ignored if subpass does not have color attachments. |
| uint32_t num_color_attachments = std::min(subpass.colorAttachmentCount, color_blend_state->attachmentCount); |
| |
| for (uint32_t j = 0; j < num_color_attachments; j++) { |
| const auto& blend_att = color_blend_state->pAttachments[j]; |
| uint32_t att = subpass.pColorAttachments[j].attachment; |
| |
| if (att != VK_ATTACHMENT_UNUSED && blend_att.blendEnable && blend_att.colorWriteMask) { |
| if (!FormatHasFullThroughputBlendingArm(rp_state->create_info.pAttachments[att].format)) { |
| skip |= LogPerformanceWarning("BestPractices-Arm-vkCreatePipelines-multisampled-blending", device, create_info_loc, |
| "%s Pipeline is multisampled and " |
| "color attachment %" PRIu32 |
| " makes use " |
| "of a format which cannot be blended at full throughput when using MSAA.", |
| VendorSpecificTag(kBPVendorArm), j); |
| } |
| } |
| } |
| |
| return skip; |
| } |
| |
| void BestPractices::ManualPostCallRecordCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, |
| uint32_t createInfoCount, |
| const VkComputePipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateComputePipelines& chassis_state) { |
| if (record_obj.result != VK_SUCCESS) { |
| return; |
| } |
| |
| // AMD best practice |
| pipeline_cache_ = pipelineCache; |
| } |
| |
| bool BestPractices::ValidateCreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& create_info, const vvl::Pipeline& pipeline, |
| const Location create_info_loc) const { |
| bool skip = false; |
| |
| const auto* vertex_input = pipeline.InputState(); |
| if (!(pipeline.active_shaders & VK_SHADER_STAGE_MESH_BIT_EXT) && vertex_input) { |
| uint32_t count = 0; |
| for (uint32_t j = 0; j < vertex_input->vertexBindingDescriptionCount; j++) { |
| if (vertex_input->pVertexBindingDescriptions[j].inputRate == VK_VERTEX_INPUT_RATE_INSTANCE) { |
| count++; |
| } |
| } |
| if (count > kMaxInstancedVertexBuffers) { |
| skip |= LogPerformanceWarning("BestPractices-vkCreateGraphicsPipelines-too-many-instanced-vertex-buffers", device, |
| create_info_loc, |
| "The pipeline is using %" PRIu32 " instanced vertex buffers (current limit: %" PRIu32 |
| "), but this can be inefficient on the " |
| "GPU. If using instanced vertex attributes prefer interleaving them in a single buffer.", |
| count, kMaxInstancedVertexBuffers); |
| } |
| } |
| |
| const auto* raster_state = pipeline.RasterizationState(); |
| |
| if (raster_state && raster_state->depthBiasEnable && raster_state->depthBiasConstantFactor == 0.0f && |
| raster_state->depthBiasSlopeFactor == 0.0f && VendorCheckEnabled(kBPVendorArm)) { |
| skip |= |
| LogPerformanceWarning("BestPractices-Arm-vkCreatePipelines-depthbias-zero", device, create_info_loc, |
| "%s This vkCreateGraphicsPipelines call is created with depthBiasEnable set to true " |
| "and both depthBiasConstantFactor and depthBiasSlopeFactor are set to 0. This can cause reduced " |
| "efficiency during rasterization. Consider disabling depthBias or increasing either " |
| "depthBiasConstantFactor or depthBiasSlopeFactor.", |
| VendorSpecificTag(kBPVendorArm)); |
| } |
| |
| const auto* graphics_lib_info = vku::FindStructInPNextChain<VkGraphicsPipelineLibraryCreateInfoEXT>(create_info.pNext); |
| if (create_info.renderPass == VK_NULL_HANDLE && |
| !vku::FindStructInPNextChain<VkPipelineRenderingCreateInfo>(create_info.pNext) && |
| (!graphics_lib_info || |
| (graphics_lib_info->flags & (VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT | |
| VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT)) != 0)) { |
| skip |= LogWarning( |
| "BestPractices-Pipeline-NoRendering", device, create_info_loc, |
| "renderPass is VK_NULL_HANDLE and pNext chain does not contain an instance of VkPipelineRenderingCreateInfo."); |
| } |
| |
| if (VendorCheckEnabled(kBPVendorArm)) { |
| skip |= ValidateMultisampledBlendingArm(pipeline, create_info_loc); |
| } |
| |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| const auto* ia_state = pipeline.InputAssemblyState(); |
| if (ia_state && ia_state->primitiveRestartEnable) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-CreatePipelines-AvoidPrimitiveRestart", device, |
| create_info_loc.dot(Field::pInputAssemblyState).dot(Field::primitiveRestartEnable), |
| "%s Use of primitive restart is not recommended", VendorSpecificTag(kBPVendorAMD)); |
| } |
| |
| // TODO: this might be too aggressive of a check |
| if (create_info.pDynamicState && create_info.pDynamicState->dynamicStateCount > kDynamicStatesWarningLimitAMD) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-CreatePipelines-MinimizeNumDynamicStates", device, create_info_loc, |
| "%s Dynamic States usage incurs a performance cost. Ensure that they are truly needed", |
| VendorSpecificTag(kBPVendorAMD)); |
| } |
| } |
| |
| for (uint32_t i = 0; i < pipeline.stage_states.size(); i++) { |
| auto& stage_state = pipeline.stage_states[i]; |
| const VkShaderStageFlagBits stage = stage_state.GetStage(); |
| // Only validate the shader state once when added, not again when linked |
| if ((stage & pipeline.linking_shaders) == 0) { |
| skip |= ValidateShaderStage(stage_state, &pipeline, create_info_loc.dot(Field::pStages, i)); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::PreCallValidateCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, |
| const VkGraphicsPipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const ErrorObject& error_obj, PipelineStates& pipeline_states, |
| chassis::CreateGraphicsPipelines& chassis_state) const { |
| bool skip = false; |
| |
| if ((createInfoCount > 1) && (!pipelineCache)) { |
| skip |= |
| LogPerformanceWarning("BestPractices-vkCreateGraphicsPipelines-multiple-pipelines-no-cache", device, error_obj.location, |
| "creating multiple pipelines (createInfoCount is %" PRIu32 |
| ") but is not using a " |
| "pipeline cache, which may help with performance", |
| createInfoCount); |
| } |
| |
| for (uint32_t i = 0; i < createInfoCount; i++) { |
| const auto* pipeline = pipeline_states[i].get(); |
| ASSERT_AND_CONTINUE(pipeline); |
| skip |= ValidateCreateGraphicsPipeline(pCreateInfos[i], *pipeline, error_obj.location.dot(Field::pCreateInfos, i)); |
| } |
| |
| if (VendorCheckEnabled(kBPVendorAMD) || VendorCheckEnabled(kBPVendorNVIDIA)) { |
| auto prev_pipeline = pipeline_cache_.load(); |
| if (pipelineCache && prev_pipeline && pipelineCache != prev_pipeline) { |
| skip |= LogPerformanceWarning("BestPractices-vkCreatePipelines-multiple-pipelines-caches", device, error_obj.location, |
| "%s %s A second pipeline cache is in use. " |
| "Consider using only one pipeline cache to improve cache hit rate.", |
| VendorSpecificTag(kBPVendorAMD), VendorSpecificTag(kBPVendorNVIDIA)); |
| } |
| } |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| const uint32_t pso_count = num_pso_.load(); |
| if (pso_count > kMaxRecommendedNumberOfPSOAMD) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-CreatePipelines-TooManyPipelines", device, error_obj.location, |
| "%s Too many pipelines created (%" PRIu32 " but max recommended is %" PRIu32 |
| "), consider consolidation", |
| VendorSpecificTag(kBPVendorAMD), pso_count, kMaxRecommendedNumberOfPSOAMD); |
| } |
| } |
| |
| return skip; |
| } |
| |
| void BestPractices::ManualPostCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, |
| const VkGraphicsPipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj, PipelineStates& pipeline_states, |
| chassis::CreateGraphicsPipelines& chassis_state) { |
| if (record_obj.result != VK_SUCCESS) { |
| return; |
| } |
| // AMD best practice |
| pipeline_cache_ = pipelineCache; |
| } |
| |
| bool BestPractices::PreCallValidateCreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, |
| const VkComputePipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const ErrorObject& error_obj, PipelineStates& pipeline_states, |
| chassis::CreateComputePipelines& chassis_state) const { |
| bool skip = false; |
| |
| if ((createInfoCount > 1) && (!pipelineCache)) { |
| skip |= |
| LogPerformanceWarning("BestPractices-vkCreateComputePipelines-multiple-pipelines-no-cache", device, error_obj.location, |
| "creating multiple pipelines (createInfoCount is %" PRIu32 |
| ") but is not using a pipeline cache, which may help with performance", |
| createInfoCount); |
| } |
| |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| auto prev_pipeline = pipeline_cache_.load(); |
| if (pipelineCache && prev_pipeline && pipelineCache != prev_pipeline) { |
| skip |= LogPerformanceWarning("BestPractices-vkCreateComputePipelines-multiple-cache", device, error_obj.location, |
| "%s A second pipeline cache is in use. Consider using only one pipeline cache to " |
| "improve cache hit rate", |
| VendorSpecificTag(kBPVendorAMD)); |
| } |
| } |
| |
| for (uint32_t i = 0; i < createInfoCount; i++) { |
| const vvl::Pipeline* pipeline = pipeline_states[i].get(); |
| ASSERT_AND_CONTINUE(pipeline); |
| |
| const Location create_info_loc = error_obj.location.dot(Field::pCreateInfos, i); |
| const Location stage_info = create_info_loc.dot(Field::stage); |
| const auto& stage_state = pipeline->stage_states[0]; |
| skip |= ValidateShaderStage(stage_state, pipeline, stage_info); |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::ValidateComputeShaderArm(const spirv::Module& module_state, const spirv::EntryPoint& entrypoint, |
| const Location& loc) const { |
| bool skip = false; |
| |
| spirv::LocalSize local_size = module_state.FindLocalSize(entrypoint); |
| if (local_size.x == 0) { |
| return false; |
| } |
| |
| const uint64_t thread_count = local_size.x * local_size.y * local_size.z; |
| |
| // Generate a priori warnings about work group sizes. |
| if (thread_count > kMaxEfficientWorkGroupThreadCountArm) { |
| skip |= LogPerformanceWarning( |
| "BestPractices-Arm-vkCreateComputePipelines-compute-work-group-size", device, loc, |
| "%s compute shader with work group dimensions (%s) (%" PRIu64 |
| " threads total), has more threads than advised in a single work group. It is advised to use work " |
| "groups with less than %" PRIu32 " threads, especially when using barrier() or shared memory.", |
| VendorSpecificTag(kBPVendorArm), local_size.ToString().c_str(), thread_count, kMaxEfficientWorkGroupThreadCountArm); |
| } |
| |
| if (thread_count == 1 || ((local_size.x > 1) && (local_size.x & (kThreadGroupDispatchCountAlignmentArm - 1))) || |
| ((local_size.y > 1) && (local_size.y & (kThreadGroupDispatchCountAlignmentArm - 1))) || |
| ((local_size.z > 1) && (local_size.z & (kThreadGroupDispatchCountAlignmentArm - 1)))) { |
| skip |= LogPerformanceWarning("BestPractices-Arm-vkCreateComputePipelines-compute-thread-group-alignment", device, loc, |
| "%s compute shader with work group dimensions (%s) is not aligned to %" PRIu32 |
| " threads. On Arm Mali architectures, not aligning work group sizes to %" PRIu32 |
| " may leave threads idle on the shader core.", |
| VendorSpecificTag(kBPVendorArm), local_size.ToString().c_str(), |
| kThreadGroupDispatchCountAlignmentArm, kThreadGroupDispatchCountAlignmentArm); |
| } |
| |
| uint32_t dimensions = 0; |
| if (local_size.x > 1) dimensions++; |
| if (local_size.y > 1) dimensions++; |
| if (local_size.z > 1) dimensions++; |
| |
| if (dimensions == 1) { |
| // If we're accessing images, we almost certainly want to have a 2D workgroup for cache reasons. |
| // There are some false positives here. We could simply have a shader that does this within a 1D grid, |
| // or we may have a linearly tiled image, but these cases are quite unlikely in practice. |
| for (const auto& variable : entrypoint.resource_interface_variables) { |
| if (variable.IsImage() && variable.info.image_dim != spv::Dim1D && variable.info.image_dim != spv::DimBuffer) { |
| LogPerformanceWarning("BestPractices-Arm-vkCreateComputePipelines-compute-spatial-locality", device, loc, |
| "%s compute shader has work group dimensions (%s), which " |
| "suggests a 1D dispatch, but the shader is accessing 2D or 3D images. The shader may be " |
| "exhibiting poor spatial locality with respect to one or more shader resources.", |
| VendorSpecificTag(kBPVendorArm), local_size.ToString().c_str()); |
| break; // only need to report once |
| } |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::ValidateComputeShaderAmd(const spirv::Module& module_state, const spirv::EntryPoint& entrypoint, |
| const Location& loc) const { |
| bool skip = false; |
| |
| spirv::LocalSize local_size = module_state.FindLocalSize(entrypoint); |
| if (local_size.x == 0) { |
| return false; |
| } |
| |
| const uint64_t thread_count = local_size.x * local_size.y * local_size.z; |
| |
| if (!IsIntegerMultipleOf(thread_count, 64)) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-LocalWorkgroup-Multiple64", device, loc, |
| "%s compute shader with work group dimensions (%s), workgroup size (%" PRIu64 |
| ") is not a multiple of 64. Make the workgroup size a multiple of 64 to obtain best " |
| "performance across all AMD GPU generations.", |
| VendorSpecificTag(kBPVendorAMD), local_size.ToString().c_str(), thread_count); |
| } |
| |
| return skip; |
| } |
| |
| void BestPractices::PreCallRecordCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, |
| const VkGraphicsPipelineCreateInfo* pCreateInfos, |
| const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines, |
| const RecordObject& record_obj) { |
| // AMD best practice |
| num_pso_ += createInfoCount; |
| } |
| |
| bool BestPractices::PreCallValidateCreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, |
| const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout, |
| const ErrorObject& error_obj) const { |
| bool skip = false; |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| uint32_t descriptor_size = enabled_features.robustBufferAccess ? 4 : 2; |
| // Descriptor sets cost 1 DWORD each. |
| // Dynamic buffers cost 2 DWORDs each when robust buffer access is OFF. |
| // Dynamic buffers cost 4 DWORDs each when robust buffer access is ON. |
| // Push constants cost 1 DWORD per 4 bytes in the Push constant range. |
| uint32_t pipeline_size = pCreateInfo->setLayoutCount; // in DWORDS |
| for (uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++) { |
| auto descriptor_set_layout_state = Get<vvl::DescriptorSetLayout>(pCreateInfo->pSetLayouts[i]); |
| if (!descriptor_set_layout_state) continue; |
| pipeline_size += descriptor_set_layout_state->GetDynamicDescriptorCount() * descriptor_size; |
| } |
| |
| for (uint32_t i = 0; i < pCreateInfo->pushConstantRangeCount; i++) { |
| pipeline_size += pCreateInfo->pPushConstantRanges[i].size / 4; |
| } |
| |
| if (pipeline_size > kPipelineLayoutSizeWarningLimitAMD) { |
| skip |= LogPerformanceWarning("BestPractices-AMD-CreatePipelinesLayout-KeepLayoutSmall", device, error_obj.location, |
| "%s pipeline layout size is too large. Prefer smaller pipeline layouts. " |
| "Descriptor sets cost 1 DWORD each. " |
| "Dynamic buffers cost 2 DWORDs each when robust buffer access is OFF. " |
| "Dynamic buffers cost 4 DWORDs each when robust buffer access is ON. " |
| "Push constants cost 1 DWORD per 4 bytes in the Push constant range. ", |
| VendorSpecificTag(kBPVendorAMD)); |
| } |
| } |
| |
| if (VendorCheckEnabled(kBPVendorNVIDIA)) { |
| bool has_separate_sampler = false; |
| size_t fast_space_usage = 0; |
| |
| for (uint32_t i = 0; i < pCreateInfo->setLayoutCount; ++i) { |
| auto descriptor_set_layout_state = Get<vvl::DescriptorSetLayout>(pCreateInfo->pSetLayouts[i]); |
| if (!descriptor_set_layout_state) continue; |
| for (const auto& binding : descriptor_set_layout_state->GetBindings()) { |
| if (binding.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER) { |
| has_separate_sampler = true; |
| } |
| |
| if ((descriptor_set_layout_state->GetCreateFlags() & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) == |
| 0U) { |
| size_t descriptor_type_size = 0; |
| |
| switch (binding.descriptorType) { |
| case VK_DESCRIPTOR_TYPE_SAMPLER: |
| case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: |
| case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: |
| case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: |
| case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: |
| case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: |
| case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: |
| descriptor_type_size = 4; |
| break; |
| case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: |
| case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: |
| case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: |
| case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV: |
| case VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV: |
| descriptor_type_size = 8; |
| break; |
| case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: |
| case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: |
| case VK_DESCRIPTOR_TYPE_MUTABLE_EXT: |
| descriptor_type_size = 16; |
| break; |
| case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK: |
| descriptor_type_size = 1; |
| break; |
| default: |
| // Unknown type. |
| break; |
| } |
| |
| size_t descriptor_size = descriptor_type_size * binding.descriptorCount; |
| fast_space_usage += descriptor_size; |
| } |
| } |
| } |
| |
| if (has_separate_sampler) { |
| skip |= LogPerformanceWarning( |
| "BestPractices-NVIDIA-CreatePipelineLayout-SeparateSampler", device, error_obj.location, |
| "%s Consider using combined image samplers instead of separate samplers for marginally better performance.", |
| VendorSpecificTag(kBPVendorNVIDIA)); |
| } |
| |
| if (fast_space_usage > kPipelineLayoutFastDescriptorSpaceNVIDIA) { |
| skip |= LogPerformanceWarning( |
| "BestPractices-NVIDIA-CreatePipelineLayout-LargePipelineLayout", device, error_obj.location, |
| "%s Pipeline layout size is too large, prefer using pipeline-specific descriptor set layouts. " |
| "Aim for consuming less than %" PRIu32 |
| " bytes to allow fast reads for all non-bindless descriptors. " |
| "Samplers, textures, texel buffers, and combined image samplers consume 4 bytes each. " |
| "Uniform buffers and acceleration structures consume 8 bytes. " |
| "Storage buffers consume 16 bytes. " |
| "Push constants do not consume space.", |
| VendorSpecificTag(kBPVendorNVIDIA), kPipelineLayoutFastDescriptorSpaceNVIDIA); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool BestPractices::PreCallValidateCmdBindPipeline(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, |
| VkPipeline pipeline, const ErrorObject& error_obj) const { |
| bool skip = false; |
| |
| if (pipelineBindPoint == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| if (VendorCheckEnabled(kBPVendorAMD) || VendorCheckEnabled(kBPVendorNVIDIA)) { |
| if (IsPipelineUsedInFrame(pipeline)) { |
| skip |= LogPerformanceWarning( |
| "BestPractices-Pipeline-SortAndBind", commandBuffer, error_obj.location, |
| "%s %s Pipeline %s was bound twice in the frame. " |
| "Keep pipeline state changes to a minimum, for example, by sorting draw calls by pipeline.", |
| VendorSpecificTag(kBPVendorAMD), VendorSpecificTag(kBPVendorNVIDIA), FormatHandle(pipeline).c_str()); |
| } |
| } |
| if (VendorCheckEnabled(kBPVendorNVIDIA)) { |
| auto cb_state = Get<vvl::CommandBuffer>(commandBuffer); |
| auto& sub_state = bp_state::SubState(*cb_state); |
| const auto& tgm = sub_state.nv.tess_geometry_mesh; |
| if (tgm.num_switches >= kNumBindPipelineTessGeometryMeshSwitchesThresholdNVIDIA && !tgm.threshold_signaled) { |
| LogPerformanceWarning("BestPractices-NVIDIA-BindPipeline-SwitchTessGeometryMesh", commandBuffer, error_obj.location, |
| "%s Avoid switching between pipelines with and without tessellation, geometry, task, " |
| "and/or mesh shaders. Group draw calls using these shader stages together.", |
| VendorSpecificTag(kBPVendorNVIDIA)); |
| // Do not set 'skip' so the number of switches gets properly counted after the message. |
| } |
| } |
| } |
| |
| return skip; |
| } |
| |
| // Currently only compute uses this, but this is designed to match the way CoreChecks funnels all the SPIR-V related checks |
| bool BestPractices::ValidateShaderStage(const ShaderStageState& stage_state, const vvl::Pipeline* pipeline, |
| const Location& loc) const { |
| bool skip = false; |
| |
| if ((pipeline && pipeline->uses_shader_module_id) || !stage_state.HasSpirv()) { |
| return skip; // these edge cases should be validated already |
| } |
| |
| const spirv::Module& module_state = *stage_state.spirv_state.get(); |
| if (!module_state.valid_spirv) { |
| return skip; // checked elsewhere |
| } |
| |
| const spirv::EntryPoint& entrypoint = *stage_state.entrypoint; |
| |
| if (entrypoint.stage == VK_SHADER_STAGE_COMPUTE_BIT) { |
| if (VendorCheckEnabled(kBPVendorArm)) { |
| skip |= ValidateComputeShaderArm(module_state, entrypoint, loc); |
| } |
| if (VendorCheckEnabled(kBPVendorAMD)) { |
| skip |= ValidateComputeShaderAmd(module_state, entrypoint, loc); |
| } |
| |
| if (IsExtEnabled(extensions.vk_khr_maintenance4)) { |
| if (module_state.static_data_.has_built_in_workgroup_size) { |
| skip |= LogWarning("BestPractices-SpirvDeprecated_WorkgroupSize", device, loc, |
| "is using the SPIR-V Workgroup built-in which SPIR-V 1.6 deprecated. When using " |
| "VK_KHR_maintenance4 or Vulkan 1.3+, the new SPIR-V LocalSizeId execution mode should be used " |
| "instead. This can be done by recompiling your shader and targeting Vulkan 1.3+."); |
| } |
| } |
| } |
| |
| if (entrypoint.stage == VK_SHADER_STAGE_TASK_BIT_EXT || entrypoint.stage == VK_SHADER_STAGE_MESH_BIT_EXT) { |
| spirv::LocalSize local_size = module_state.FindLocalSize(entrypoint); |
| if (local_size.x == 0) { |
| return skip; |
| } |
| bool is_task = |
| entrypoint.execution_model == spv::ExecutionModelTaskEXT || entrypoint.execution_model == spv::ExecutionModelTaskNV; |
| |
| // Assume core checks caught any overflow |
| uint64_t invocations = |
| static_cast<uint64_t>(local_size.x) * static_cast<uint64_t>(local_size.y) * static_cast<uint64_t>(local_size.z); |
| const uint32_t preferred_size = is_task ? phys_dev_ext_props.mesh_shader_props_ext.maxPreferredTaskWorkGroupInvocations |
| : phys_dev_ext_props.mesh_shader_props_ext.maxPreferredMeshWorkGroupInvocations; |
| if (invocations > preferred_size) { |
| skip |= LogPerformanceWarning( |
| "BestPractices-Mesh-MaxPreferredWorkGroupInvocations", module_state.handle(), loc, |
| "shader %s has a total invocation size of %" PRIu64 " (%s) is more than %s (%" PRIu32 ").", |
| entrypoint.Describe().c_str(), invocations, local_size.ToString().c_str(), |
| is_task ? "maxPreferredTaskWorkGroupInvocations" : "maxPreferredMeshWorkGroupInvocations", preferred_size); |
| } |
| } |
| |
| return skip; |
| } |