blob: cda16a0b454e1c53917b8f206eebf99bea4b282f [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 "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;
}