| /* Copyright (c) 2015-2026 The Khronos Group Inc. |
| * Copyright (c) 2015-2026 Valve Corporation |
| * Copyright (c) 2015-2026 LunarG, Inc. |
| * Copyright (C) 2015-2026 Google Inc. |
| * Modifications Copyright (C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved. |
| * |
| * 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 "stateless/stateless_validation.h" |
| #include "generated/enum_flag_bits.h" |
| #include "containers/container_utils.h" |
| #include "utils/math_utils.h" |
| #include "utils/vk_api_utils.h" |
| |
| namespace stateless { |
| static inline bool IsMeshCommand(VkIndirectCommandsTokenTypeEXT type) { |
| return IsValueIn( |
| type, |
| {VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_NV_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_NV_EXT}); |
| } |
| |
| static inline bool IsComputeCommand(VkIndirectCommandsTokenTypeEXT type) { |
| return IsValueIn( |
| type, {VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_EXECUTION_SET_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT}); |
| } |
| |
| static inline bool IsRayTracingCommand(VkIndirectCommandsTokenTypeEXT type) { |
| return IsValueIn( |
| type, {VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_EXECUTION_SET_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT}); |
| } |
| |
| bool Device::ValidateIndirectExecutionSetPipelineInfo(const Context& context, |
| const VkIndirectExecutionSetPipelineInfoEXT& pipeline_info, |
| const Location& pipeline_info_loc) const { |
| bool skip = false; |
| |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if (pipeline_info.maxPipelineCount == 0) { |
| skip |= LogError("VUID-VkIndirectExecutionSetPipelineInfoEXT-maxPipelineCount-11018", device, |
| pipeline_info_loc.dot(Field::maxPipelineCount), "is zero."); |
| } else if (pipeline_info.maxPipelineCount > props.maxIndirectPipelineCount) { |
| skip |= LogError("VUID-VkIndirectExecutionSetPipelineInfoEXT-maxPipelineCount-11018", device, |
| pipeline_info_loc.dot(Field::maxPipelineCount), |
| "(%" PRIu32 ") is larger than maxIndirectPipelineCount (%" PRIu32 ").", pipeline_info.maxPipelineCount, |
| props.maxIndirectPipelineCount); |
| } |
| |
| skip |= ValidateIndirectExecutionSetPipelineInfoEXT(context, pipeline_info, pipeline_info_loc); |
| |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectExecutionSetShaderInfo(const Context& context, const VkIndirectExecutionSetShaderInfoEXT& shader_info, |
| const Location& shader_info_loc) const { |
| bool skip = false; |
| |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if (shader_info.maxShaderCount == 0) { |
| skip |= LogError("VUID-VkIndirectExecutionSetShaderInfoEXT-maxShaderCount-11021", device, |
| shader_info_loc.dot(Field::maxShaderCount), "is zero."); |
| } else if (shader_info.maxShaderCount > props.maxIndirectShaderObjectCount) { |
| skip |= LogError("VUID-VkIndirectExecutionSetShaderInfoEXT-maxShaderCount-11022", device, |
| shader_info_loc.dot(Field::maxShaderCount), |
| "(%" PRIu32 ") is larger than maxIndirectShaderObjectCount (%" PRIu32 ").", shader_info.maxShaderCount, |
| props.maxIndirectShaderObjectCount); |
| } else if (shader_info.maxShaderCount < shader_info.shaderCount) { |
| skip |= LogError("VUID-VkIndirectExecutionSetShaderInfoEXT-maxShaderCount-11036", device, |
| shader_info_loc.dot(Field::maxShaderCount), "(%" PRIu32 ") is less than shaderCount (%" PRIu32 ").", |
| shader_info.maxShaderCount, shader_info.shaderCount); |
| } |
| |
| // implicit checks - done manually as code gen is hard to get correct |
| skip |= context.ValidateStructType(shader_info_loc, &shader_info, VK_STRUCTURE_TYPE_INDIRECT_EXECUTION_SET_SHADER_INFO_EXT, |
| false, kVUIDUndefined, "VUID-VkIndirectExecutionSetShaderInfoEXT-sType-sType"); |
| skip |= context.ValidateStructTypeArray(shader_info_loc.dot(Field::shaderCount), shader_info_loc.dot(Field::pSetLayoutInfos), |
| shader_info.shaderCount, shader_info.pSetLayoutInfos, |
| VK_STRUCTURE_TYPE_INDIRECT_EXECUTION_SET_SHADER_LAYOUT_INFO_EXT, true, false, |
| "VUID-VkIndirectExecutionSetShaderLayoutInfoEXT-sType-sType", |
| "VUID-VkIndirectExecutionSetShaderInfoEXT-pSetLayoutInfos-parameter", |
| "VUID-VkIndirectExecutionSetShaderInfoEXT-shaderCount-arraylength"); |
| |
| // Validate shaderCount once above |
| skip |= context.ValidateArray(shader_info_loc.dot(Field::shaderCount), shader_info_loc.dot(Field::pInitialShaders), |
| shader_info.shaderCount, &shader_info.pInitialShaders, false, true, kVUIDUndefined, |
| "VUID-VkIndirectExecutionSetShaderInfoEXT-pInitialShaders-parameter"); |
| skip |= |
| context.ValidateArray(shader_info_loc.dot(Field::pushConstantRangeCount), shader_info_loc.dot(Field::pPushConstantRanges), |
| shader_info.pushConstantRangeCount, &shader_info.pPushConstantRanges, false, true, kVUIDUndefined, |
| "VUID-VkIndirectExecutionSetShaderInfoEXT-pPushConstantRanges-parameter"); |
| |
| if (shader_info.pPushConstantRanges != nullptr) { |
| for (uint32_t i = 0; i < shader_info.pushConstantRangeCount; ++i) { |
| const Location pc_range_loc = shader_info_loc.dot(Field::pPushConstantRanges, i); |
| skip |= context.ValidateFlags(pc_range_loc.dot(Field::stageFlags), vvl::FlagBitmask::VkShaderStageFlagBits, |
| AllVkShaderStageFlagBits, shader_info.pPushConstantRanges[i].stageFlags, kRequiredFlags, |
| "VUID-VkPushConstantRange-stageFlags-parameter", |
| "VUID-VkPushConstantRange-stageFlags-requiredbitmask"); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool Device::manual_PreCallValidateCreateIndirectExecutionSetEXT(VkDevice device, |
| const VkIndirectExecutionSetCreateInfoEXT* pCreateInfo, |
| const VkAllocationCallbacks* pAllocator, |
| VkIndirectExecutionSetEXT* pIndirectExecutionSet, |
| const Context& context) const { |
| bool skip = false; |
| const auto& error_obj = context.error_obj; |
| |
| if (!enabled_features.deviceGeneratedCommands) { |
| skip |= LogError("VUID-vkCreateIndirectExecutionSetEXT-deviceGeneratedCommands-11013", device, error_obj.location, |
| "deviceGeneratedCommands feature was not enabled."); |
| } |
| |
| const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo); |
| const Location info_loc = create_info_loc.dot(Field::info); |
| |
| if (pCreateInfo->type == VK_INDIRECT_EXECUTION_SET_INFO_TYPE_PIPELINES_EXT) { |
| if (!pCreateInfo->info.pPipelineInfo) { |
| skip |= LogError("VUID-VkIndirectExecutionSetCreateInfoEXT-pPipelineInfo-parameter", device, |
| create_info_loc.dot(Field::type), |
| "is VK_INDIRECT_EXECUTION_SET_INFO_TYPE_PIPELINES_EXT, but info.pPipelineInfo is null."); |
| } else { |
| skip |= ValidateIndirectExecutionSetPipelineInfo(context, *pCreateInfo->info.pPipelineInfo, |
| info_loc.dot(Field::pPipelineInfo)); |
| } |
| } else if (pCreateInfo->type == VK_INDIRECT_EXECUTION_SET_INFO_TYPE_SHADER_OBJECTS_EXT) { |
| if (!enabled_features.shaderObject) { |
| skip |= |
| LogError("VUID-VkIndirectExecutionSetCreateInfoEXT-maxIndirectShaderObjectCount-11014", device, |
| create_info_loc.dot(Field::type), |
| "is VK_INDIRECT_EXECUTION_SET_INFO_TYPE_SHADER_OBJECTS_EXT but the shaderObject feature was not enabled."); |
| } else if (phys_dev_ext_props.device_generated_commands_props.maxIndirectShaderObjectCount == 0) { |
| skip |= LogError("VUID-VkIndirectExecutionSetCreateInfoEXT-maxIndirectShaderObjectCount-11014", device, |
| create_info_loc.dot(Field::type), |
| "is VK_INDIRECT_EXECUTION_SET_INFO_TYPE_SHADER_OBJECTS_EXT but maxIndirectShaderObjectCount is zero " |
| "(so is no support for device generated commands via shader object)."); |
| } |
| |
| if (!pCreateInfo->info.pShaderInfo) { |
| skip |= |
| LogError("VUID-VkIndirectExecutionSetCreateInfoEXT-pShaderInfo-parameter", device, create_info_loc.dot(Field::type), |
| "is VK_INDIRECT_EXECUTION_SET_INFO_TYPE_SHADER_OBJECTS_EXT, but info.pShaderInfo is null."); |
| } else { |
| skip |= |
| ValidateIndirectExecutionSetShaderInfo(context, *pCreateInfo->info.pShaderInfo, info_loc.dot(Field::pShaderInfo)); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectCommandsPushConstantToken(const Context& context, |
| const VkIndirectCommandsPushConstantTokenEXT& push_constant_token, |
| VkIndirectCommandsTokenTypeEXT token_type, |
| const Location& push_constant_token_loc) const { |
| bool skip = false; |
| skip |= context.ValidateFlags( |
| push_constant_token_loc.dot(Field::updateRange).dot(Field::stageFlags), vvl::FlagBitmask::VkShaderStageFlagBits, |
| AllVkShaderStageFlagBits, push_constant_token.updateRange.stageFlags, kRequiredFlags, |
| "VUID-VkPushConstantRange-stageFlags-parameter", "VUID-VkPushConstantRange-stageFlags-requiredbitmask"); |
| |
| if ((token_type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT || |
| token_type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT) && |
| push_constant_token.updateRange.size != 4) { |
| skip |= LogError("VUID-VkIndirectCommandsPushConstantTokenEXT-size-11133", device, |
| push_constant_token_loc.dot(Field::updateRange).dot(Field::size), |
| "is %" PRIu32 ", but needs to be 4 when using VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT.", |
| push_constant_token.updateRange.size); |
| } |
| if (token_type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT || |
| token_type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT) { |
| if (push_constant_token.updateRange.stageFlags != VK_SHADER_STAGE_ALL) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-type-11333", device, |
| push_constant_token_loc.dot(Field::updateRange).dot(Field::stageFlags), |
| "is %s but stageFlags is %s (needs to be VK_SHADER_STAGE_ALL).", |
| string_VkIndirectCommandsTokenTypeEXT(token_type), |
| string_VkShaderStageFlags(push_constant_token.updateRange.stageFlags).c_str()); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectCommandsIndexBufferToken(const Context& context, |
| const VkIndirectCommandsIndexBufferTokenEXT& index_buffer_token, |
| const Location& index_buffer_token_loc) const { |
| bool skip = false; |
| skip |= context.ValidateFlags(index_buffer_token_loc.dot(Field::mode), vvl::FlagBitmask::VkIndirectCommandsInputModeFlagBitsEXT, |
| AllVkIndirectCommandsInputModeFlagBitsEXT, index_buffer_token.mode, kRequiredSingleBit, |
| "VUID-VkIndirectCommandsIndexBufferTokenEXT-mode-parameter", |
| "VUID-VkIndirectCommandsIndexBufferTokenEXT-mode-11135"); |
| |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if ((index_buffer_token.mode & props.supportedIndirectCommandsInputModes) == 0) { |
| skip |= LogError("VUID-VkIndirectCommandsIndexBufferTokenEXT-mode-11136", device, index_buffer_token_loc.dot(Field::mode), |
| "is %s, but that is not supported by supportedIndirectCommandsInputModes (%s).", |
| string_VkIndirectCommandsInputModeFlagBitsEXT(index_buffer_token.mode), |
| string_VkIndirectCommandsInputModeFlagsEXT(props.supportedIndirectCommandsInputModes).c_str()); |
| } |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectCommandsExecutionSetToken(const Context& context, |
| const VkIndirectCommandsExecutionSetTokenEXT& exe_set_token, |
| const Location& exe_set_token_loc) const { |
| bool skip = false; |
| skip |= context.ValidateRangedEnum(exe_set_token_loc.dot(Field::type), vvl::Enum::VkIndirectExecutionSetInfoTypeEXT, |
| exe_set_token.type, "VUID-VkIndirectCommandsExecutionSetTokenEXT-type-parameter"); |
| |
| skip |= context.ValidateFlags(exe_set_token_loc.dot(Field::shaderStages), vvl::FlagBitmask::VkShaderStageFlagBits, |
| AllVkShaderStageFlagBits, exe_set_token.shaderStages, kRequiredFlags, |
| "VUID-VkIndirectCommandsExecutionSetTokenEXT-shaderStages-parameter", |
| "VUID-VkIndirectCommandsExecutionSetTokenEXT-shaderStages-requiredbitmask"); |
| |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if ((exe_set_token.shaderStages & (props.supportedIndirectCommandsShaderStagesPipelineBinding | |
| props.supportedIndirectCommandsShaderStagesShaderBinding)) == 0) { |
| skip |= LogError("VUID-VkIndirectCommandsExecutionSetTokenEXT-shaderStages-11137", device, |
| exe_set_token_loc.dot(Field::shaderStages), |
| "is %s, but that is not supported by supportedIndirectCommandsShaderStagesPipelineBinding (%s) or " |
| "supportedIndirectCommandsShaderStagesShaderBinding (%s).", |
| string_VkShaderStageFlags(exe_set_token.shaderStages).c_str(), |
| string_VkShaderStageFlags(props.supportedIndirectCommandsShaderStagesPipelineBinding).c_str(), |
| string_VkShaderStageFlags(props.supportedIndirectCommandsShaderStagesShaderBinding).c_str()); |
| } |
| |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectCommandsLayoutToken(const Context& context, const VkIndirectCommandsLayoutTokenEXT& token, |
| const Location& token_loc) const { |
| bool skip = false; |
| |
| const Location data_loc = token_loc.dot(Field::data); |
| switch (token.type) { |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_EXT: |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT: |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT: |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT: |
| if (!token.data.pPushConstant) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutTokenEXT-pPushConstant-parameter", device, token_loc.dot(Field::type), |
| "is %s, but data.pPushConstant is null.", string_VkIndirectCommandsTokenTypeEXT(token.type)); |
| } else { |
| skip |= ValidateIndirectCommandsPushConstantToken(context, *token.data.pPushConstant, token.type, |
| data_loc.dot(Field::pPushConstant)); |
| } |
| break; |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_EXT: |
| if (!token.data.pVertexBuffer) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutTokenEXT-pVertexBuffer-parameter", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_EXT, but data.pVertexBuffer is null."); |
| } |
| break; |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_EXT: |
| if (!token.data.pIndexBuffer) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-pIndexBuffer-parameter", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_EXT, but data.pIndexBuffer is null."); |
| } else { |
| skip |= |
| ValidateIndirectCommandsIndexBufferToken(context, *token.data.pIndexBuffer, data_loc.dot(Field::pIndexBuffer)); |
| } |
| break; |
| case VK_INDIRECT_COMMANDS_TOKEN_TYPE_EXECUTION_SET_EXT: |
| if (!token.data.pExecutionSet) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutTokenEXT-pExecutionSet-parameter", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_EXECUTION_SET_EXT, but data.pExecutionSet is null."); |
| } else { |
| skip |= ValidateIndirectCommandsExecutionSetToken(context, *token.data.pExecutionSet, |
| data_loc.dot(Field::pExecutionSet)); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if (IsMeshCommand(token.type)) { |
| if (!enabled_features.meshShader || !enabled_features.taskShader) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-meshShader-11126", device, token_loc.dot(Field::type), |
| "is %s but meshShader and taskShader features are disabled.", |
| string_VkIndirectCommandsTokenTypeEXT(token.type)); |
| } else if (!props.deviceGeneratedCommandsMultiDrawIndirectCount) { |
| if (token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_EXT) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-deviceGeneratedCommandsMultiDrawIndirectCount-11130", |
| device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_EXT but " |
| "deviceGeneratedCommandsMultiDrawIndirectCount is not supported."); |
| } else if (token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_NV_EXT) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-deviceGeneratedCommandsMultiDrawIndirectCount-11131", |
| device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_NV_EXT but " |
| "deviceGeneratedCommandsMultiDrawIndirectCount is not supported."); |
| } |
| } |
| } else if (token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_COUNT_EXT || |
| token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_COUNT_EXT) { |
| if (!props.deviceGeneratedCommandsMultiDrawIndirectCount) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutTokenEXT-deviceGeneratedCommandsMultiDrawIndirectCount-11129", device, |
| token_loc.dot(Field::type), "is %s but deviceGeneratedCommandsMultiDrawIndirectCount is not supported.", |
| string_VkIndirectCommandsTokenTypeEXT(token.type)); |
| } |
| } else if (token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT && !enabled_features.rayTracingMaintenance1) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-rayTracingMaintenance1-11128", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT but rayTracingMaintenance1 was not enabled."); |
| } else if (token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_EXT || |
| token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_DATA_SEQUENCE_INDEX_EXT) { |
| if (!enabled_features.descriptorHeap) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutTokenEXT-descriptorHeap-11332", device, token_loc.dot(Field::type), |
| "is %s but descriptorHeap feature was not enabled.", string_VkIndirectCommandsTokenTypeEXT(token.type)); |
| } |
| } |
| |
| // offset is ignored for SEQUENCE_INDEX |
| if (token.type != VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT) { |
| if (token.offset > props.maxIndirectCommandsTokenOffset) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-offset-11124", device, token_loc.dot(Field::offset), |
| "(%" PRIu32 ") is greater than maxIndirectCommandsTokenOffset (%" PRIu32 ")", token.offset, |
| props.maxIndirectCommandsTokenOffset); |
| } |
| if (!IsIntegerMultipleOf(token.offset, 4)) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-offset-11125", device, token_loc.dot(Field::offset), |
| "(%" PRIu32 ") is not aligned to 4.", token.offset); |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool Device::ValidateIndirectCommandsLayoutStage(const Context& context, const VkIndirectCommandsLayoutTokenEXT& token, |
| const Location& token_loc, VkShaderStageFlags shader_stages, |
| bool has_stage_graphics, bool has_stage_compute, bool has_stage_ray_tracing, |
| bool has_stage_mesh) const { |
| bool skip = false; |
| // Check if type has supported shaderStage |
| if (!has_stage_graphics && |
| IsValueIn( |
| token.type, |
| {VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_COUNT_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_COUNT_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_NV_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_NV_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_EXT})) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11104", device, token_loc.dot(Field::type), |
| "is %s, but shaderStages (%s) doesn't contain graphics stages.", |
| string_VkIndirectCommandsTokenTypeEXT(token.type), string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| if (!has_stage_compute && token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_EXT) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11105", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_EXT, but shaderStages (%s) doesn't contain " |
| "VK_SHADER_STAGE_COMPUTE_BIT.", |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| if (!has_stage_mesh && IsValueIn(token.type, {VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_EXT})) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11106", device, token_loc.dot(Field::type), |
| "is %s, but shaderStages (%s) doesn't contain mesh stages.", |
| string_VkIndirectCommandsTokenTypeEXT(token.type), string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| if (!has_stage_mesh && IsValueIn(token.type, {VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_NV_EXT, |
| VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_MESH_TASKS_COUNT_NV_EXT})) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11107", device, token_loc.dot(Field::type), |
| "is %s, but shaderStages (%s) doesn't contain mesh stages.", |
| string_VkIndirectCommandsTokenTypeEXT(token.type), string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| if (!has_stage_ray_tracing && token.type == VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT) { |
| skip |= LogError( |
| "VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11108", device, token_loc.dot(Field::type), |
| "is VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT, but shaderStages (%s) doesn't contain ray tracing stages.", |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| |
| // Check if certain stages, it is limited to certain tokens |
| if (has_stage_graphics && |
| IsValueIn(token.type, {VK_INDIRECT_COMMANDS_TOKEN_TYPE_TRACE_RAYS2_EXT, VK_INDIRECT_COMMANDS_TOKEN_TYPE_DISPATCH_EXT})) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11109", device, token_loc.dot(Field::type), |
| "is %s but shaderStages is graphics (%s).", string_VkIndirectCommandsTokenTypeEXT(token.type), |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } else if (has_stage_compute && !IsComputeCommand(token.type)) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11110", device, token_loc.dot(Field::type), |
| "is %s but shaderStages is VK_SHADER_STAGE_COMPUTE_BIT.", string_VkIndirectCommandsTokenTypeEXT(token.type)); |
| } else if (has_stage_ray_tracing && !IsRayTracingCommand(token.type)) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11111", device, token_loc.dot(Field::type), |
| "is %s but shaderStages is ray tracing (%s).", string_VkIndirectCommandsTokenTypeEXT(token.type), |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| |
| return skip; |
| } |
| |
| bool Device::manual_PreCallValidateCreateIndirectCommandsLayoutEXT(VkDevice device, |
| const VkIndirectCommandsLayoutCreateInfoEXT* pCreateInfo, |
| const VkAllocationCallbacks* pAllocator, |
| VkIndirectCommandsLayoutEXT* pIndirectCommandsLayout, |
| const Context& context) const { |
| bool skip = false; |
| const auto& error_obj = context.error_obj; |
| |
| if (!enabled_features.deviceGeneratedCommands) { |
| skip |= LogError("VUID-vkCreateIndirectCommandsLayoutEXT-deviceGeneratedCommands-11089", device, error_obj.location, |
| "deviceGeneratedCommands feature was not enabled."); |
| } |
| |
| const Location create_info_loc = error_obj.location.dot(Field::pCreateInfo); |
| const auto& props = phys_dev_ext_props.device_generated_commands_props; |
| if (pCreateInfo->indirectStride > props.maxIndirectCommandsIndirectStride) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-indirectStride-11090", device, |
| create_info_loc.dot(Field::indirectStride), |
| "(%" PRIu32 ") is greater than maxIndirectCommandsIndirectStride (%" PRIu32 ")", |
| pCreateInfo->indirectStride, props.maxIndirectCommandsIndirectStride); |
| } |
| const VkShaderStageFlags shader_stages = pCreateInfo->shaderStages; |
| if (shader_stages & ~props.supportedIndirectCommandsShaderStages) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11091", device, |
| create_info_loc.dot(Field::shaderStages), |
| "is %s which contain stages not found in supportedIndirectCommandsShaderStages (%s)", |
| string_VkShaderStageFlags(shader_stages).c_str(), |
| string_VkShaderStageFlags(props.supportedIndirectCommandsShaderStages).c_str()); |
| } |
| if (pCreateInfo->tokenCount > props.maxIndirectCommandsTokenCount) { |
| skip |= |
| LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-tokenCount-11092", device, create_info_loc.dot(Field::tokenCount), |
| "(%" PRIu32 ") is greater than maxIndirectCommandsTokenCount (%" PRIu32 ")", pCreateInfo->tokenCount, |
| props.maxIndirectCommandsTokenCount); |
| } |
| |
| if (shader_stages & VK_SHADER_STAGE_FRAGMENT_BIT) { |
| if (!(shader_stages & (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_MESH_BIT_EXT))) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11113", device, |
| create_info_loc.dot(Field::shaderStages), |
| "(%s) contains VK_SHADER_STAGE_FRAGMENT_BIT but does not contains VK_SHADER_STAGE_VERTEX_BIT or " |
| "VK_SHADER_STAGE_MESH_BIT_EXT.", |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| } |
| |
| const bool has_stage_graphics = shader_stages & kShaderStageAllGraphics; |
| const bool has_stage_compute = shader_stages & VK_SHADER_STAGE_COMPUTE_BIT; |
| const bool has_stage_ray_tracing = shader_stages & kShaderStageAllRayTracing; |
| const bool has_stage_mesh = shader_stages & VK_SHADER_STAGE_MESH_BIT_EXT; // also VK_SHADER_STAGE_MESH_BIT_NV |
| { |
| // Mesh can/will have a fragment stage in it |
| const VkShaderStageFlags all_mesh_stages = |
| VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT | VK_SHADER_STAGE_FRAGMENT_BIT; |
| |
| bool valid_stages = true; |
| if (has_stage_graphics && ((shader_stages | kShaderStageAllGraphics) != kShaderStageAllGraphics)) { |
| valid_stages = false; |
| } else if (has_stage_compute && ((shader_stages | VK_SHADER_STAGE_COMPUTE_BIT) != VK_SHADER_STAGE_COMPUTE_BIT)) { |
| valid_stages = false; |
| } else if (has_stage_ray_tracing && ((shader_stages | kShaderStageAllRayTracing) != kShaderStageAllRayTracing)) { |
| valid_stages = false; |
| } else if (has_stage_mesh && ((shader_stages | all_mesh_stages) != all_mesh_stages)) { |
| valid_stages = false; |
| } |
| if (!valid_stages && shader_stages == VK_SHADER_STAGE_ALL && enabled_features.descriptorHeap) { |
| valid_stages = true; |
| } |
| if (!valid_stages) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-shaderStages-11112", device, |
| create_info_loc.dot(Field::shaderStages), |
| "is %s but you can't mix graphics/compute/mesh/rayTracing stages with each other.", |
| string_VkShaderStageFlags(shader_stages).c_str()); |
| } |
| } |
| |
| ASSERT_AND_RETURN_SKIP(pCreateInfo->pTokens); |
| |
| uint32_t current_token_offset = 0; |
| |
| for (uint32_t i = 0; i < pCreateInfo->tokenCount; ++i) { |
| const Location token_loc = create_info_loc.dot(Field::pTokens, i); |
| const auto& token = pCreateInfo->pTokens[i]; |
| skip |= ValidateIndirectCommandsLayoutToken(context, token, token_loc); |
| skip |= ValidateIndirectCommandsLayoutStage(context, token, token_loc, shader_stages, has_stage_graphics, has_stage_compute, |
| has_stage_ray_tracing, has_stage_mesh); |
| |
| if (token.type != VK_INDIRECT_COMMANDS_TOKEN_TYPE_SEQUENCE_INDEX_EXT) { |
| if (token.offset < current_token_offset) { |
| skip |= LogError("VUID-VkIndirectCommandsLayoutCreateInfoEXT-pTokens-11103", device, token_loc.dot(Field::offset), |
| "(%" PRIu32 ") is less than pTokens[%" PRIu32 "].offset (%" PRIu32 ")", token.offset, i - 1, |
| pCreateInfo->pTokens[i - 1].offset); |
| } |
| // is a monotonic increasing value so can give previous value |
| current_token_offset = token.offset; |
| } |
| } |
| |
| return skip; |
| } |
| |
| bool Device::ValidateGeneratedCommandsInfo(VkCommandBuffer command_buffer, |
| const VkGeneratedCommandsInfoEXT& generated_commands_info, |
| const Location& info_loc) const { |
| bool skip = false; |
| |
| if (generated_commands_info.sequenceCountAddress != 0 && !IsPointerAligned(generated_commands_info.sequenceCountAddress, 4)) { |
| skip |= LogError("VUID-VkGeneratedCommandsInfoEXT-sequenceCountAddress-11073", command_buffer, |
| info_loc.dot(Field::sequenceCountAddress), "(0x%" PRIx64 ") is not aligned to 4.", |
| generated_commands_info.sequenceCountAddress); |
| } |
| if (generated_commands_info.maxSequenceCount == 0) { |
| skip |= LogError("VUID-VkGeneratedCommandsInfoEXT-maxSequenceCount-10246", command_buffer, |
| info_loc.dot(Field::maxSequenceCount), "is zero."); |
| } |
| |
| if (!IsPointerAligned(generated_commands_info.indirectAddress, 4)) { |
| skip |= |
| LogError("VUID-VkGeneratedCommandsInfoEXT-indirectAddress-11074", command_buffer, info_loc.dot(Field::indirectAddress), |
| "(0x%" PRIx64 ") is not aligned to 4.", generated_commands_info.indirectAddress); |
| } |
| |
| if (generated_commands_info.indirectAddressSize == 0) { |
| skip |= LogError("VUID-VkGeneratedCommandsInfoEXT-indirectAddressSize-11077", command_buffer, |
| info_loc.dot(Field::indirectAddressSize), "is zero."); |
| } |
| |
| return skip; |
| } |
| |
| bool Device::manual_PreCallValidateCmdPreprocessGeneratedCommandsEXT(VkCommandBuffer commandBuffer, |
| const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo, |
| VkCommandBuffer stateCommandBuffer, |
| const Context& context) const { |
| bool skip = false; |
| const auto& error_obj = context.error_obj; |
| if (!enabled_features.deviceGeneratedCommands) { |
| skip |= LogError("VUID-vkCmdPreprocessGeneratedCommandsEXT-deviceGeneratedCommands-11087", device, error_obj.location, |
| "deviceGeneratedCommands feature was not enabled."); |
| } |
| |
| const Location info_loc = error_obj.location.dot(Field::pGeneratedCommandsInfo); |
| if (pGeneratedCommandsInfo->shaderStages & |
| ~phys_dev_ext_props.device_generated_commands_props.supportedIndirectCommandsShaderStages) { |
| skip |= LogError( |
| "VUID-vkCmdPreprocessGeneratedCommandsEXT-supportedIndirectCommandsShaderStages-11088", commandBuffer, |
| info_loc.dot(Field::shaderStages), "(%s) contains stages not found in supportedIndirectCommandsShaderStages (%s).", |
| string_VkShaderStageFlags(pGeneratedCommandsInfo->shaderStages).c_str(), |
| string_VkShaderStageFlags(phys_dev_ext_props.device_generated_commands_props.supportedIndirectCommandsShaderStages) |
| .c_str()); |
| } |
| |
| skip |= ValidateGeneratedCommandsInfo(commandBuffer, *pGeneratedCommandsInfo, info_loc); |
| |
| return skip; |
| } |
| |
| bool Device::manual_PreCallValidateCmdExecuteGeneratedCommandsEXT(VkCommandBuffer commandBuffer, VkBool32 isPreprocessed, |
| const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo, |
| const Context& context) const { |
| bool skip = false; |
| const auto& error_obj = context.error_obj; |
| |
| if (!enabled_features.deviceGeneratedCommands) { |
| skip |= LogError("VUID-vkCmdExecuteGeneratedCommandsEXT-deviceGeneratedCommands-11059", device, error_obj.location, |
| "deviceGeneratedCommands feature was not enabled."); |
| } |
| |
| const Location info_loc = error_obj.location.dot(Field::pGeneratedCommandsInfo); |
| if (pGeneratedCommandsInfo->shaderStages & |
| ~phys_dev_ext_props.device_generated_commands_props.supportedIndirectCommandsShaderStages) { |
| skip |= LogError( |
| "VUID-vkCmdExecuteGeneratedCommandsEXT-supportedIndirectCommandsShaderStages-11061", commandBuffer, |
| info_loc.dot(Field::shaderStages), "(%s) contains stages not found in supportedIndirectCommandsShaderStages (%s).", |
| string_VkShaderStageFlags(pGeneratedCommandsInfo->shaderStages).c_str(), |
| string_VkShaderStageFlags(phys_dev_ext_props.device_generated_commands_props.supportedIndirectCommandsShaderStages) |
| .c_str()); |
| } |
| |
| skip |= ValidateGeneratedCommandsInfo(commandBuffer, *pGeneratedCommandsInfo, info_loc); |
| |
| return skip; |
| } |
| } // namespace stateless |