/* Copyright (c) 2015-2024 The Khronos Group Inc.
 * Copyright (c) 2015-2024 Valve Corporation
 * Copyright (c) 2015-2024 LunarG, Inc.
 * Copyright (C) 2015-2024 Google Inc.
 *
 * 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 "utils/vk_layer_utils.h"

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_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_SEQUENCE_INDEX_EXT});
}

bool StatelessValidation::ValidateIndirectExecutionSetPipelineInfo(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(pipeline_info, pipeline_info_loc);

    return skip;
}

bool StatelessValidation::ValidateIndirectExecutionSetShaderInfo(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 |= ValidateStructType(shader_info_loc, &shader_info, VK_STRUCTURE_TYPE_INDIRECT_EXECUTION_SET_SHADER_INFO_EXT, false,
                               kVUIDUndefined, "VUID-VkIndirectExecutionSetShaderInfoEXT-sType-sType");
    skip |= 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 |= 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 |= 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 |= ValidateFlags(pc_range_loc.dot(Field::stageFlags), vvl::FlagBitmask::VkShaderStageFlagBits,
                                  AllVkShaderStageFlagBits, shader_info.pPushConstantRanges[i].stageFlags, kRequiredFlags,
                                  VK_NULL_HANDLE, "VUID-VkPushConstantRange-stageFlags-parameter",
                                  "VUID-VkPushConstantRange-stageFlags-requiredbitmask");
        }
    }

    return skip;
}

bool StatelessValidation::manual_PreCallValidateCreateIndirectExecutionSetEXT(
    VkDevice device, const VkIndirectExecutionSetCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator,
    VkIndirectExecutionSetEXT* pIndirectExecutionSet, const ErrorObject& error_obj) const {
    bool skip = false;
    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(*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(*pCreateInfo->info.pShaderInfo, info_loc.dot(Field::pShaderInfo));
        }
    }

    return skip;
};

bool StatelessValidation::ValidateIndirectCommandsPushConstantToken(
    const VkIndirectCommandsPushConstantTokenEXT& push_constant_token, VkIndirectCommandsTokenTypeEXT token_type,
    const Location& push_constant_token_loc) const {
    bool skip = false;
    skip |= ValidateFlags(push_constant_token_loc.dot(Field::updateRange).dot(Field::stageFlags),
                          vvl::FlagBitmask::VkShaderStageFlagBits, AllVkShaderStageFlagBits,
                          push_constant_token.updateRange.stageFlags, kRequiredFlags, VK_NULL_HANDLE,
                          "VUID-VkPushConstantRange-stageFlags-parameter", "VUID-VkPushConstantRange-stageFlags-requiredbitmask");

    if (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);
    }

    return skip;
};

bool StatelessValidation::ValidateIndirectCommandsIndexBufferToken(const VkIndirectCommandsIndexBufferTokenEXT& index_buffer_token,
                                                                   const Location& index_buffer_token_loc) const {
    bool skip = false;
    skip |= ValidateFlags(index_buffer_token_loc.dot(Field::mode), vvl::FlagBitmask::VkIndirectCommandsInputModeFlagBitsEXT,
                          AllVkIndirectCommandsInputModeFlagBitsEXT, index_buffer_token.mode, kRequiredSingleBit, VK_NULL_HANDLE,
                          "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 StatelessValidation::ValidateIndirectCommandsExecutionSetToken(const VkIndirectCommandsExecutionSetTokenEXT& exe_set_token,
                                                                    const Location& exe_set_token_loc) const {
    bool skip = false;
    skip |= ValidateRangedEnum(exe_set_token_loc.dot(Field::type), vvl::Enum::VkIndirectExecutionSetInfoTypeEXT, exe_set_token.type,
                               "VUID-VkIndirectCommandsExecutionSetTokenEXT-type-parameter");

    skip |= ValidateFlags(exe_set_token_loc.dot(Field::shaderStages), vvl::FlagBitmask::VkShaderStageFlagBits,
                          AllVkShaderStageFlagBits, exe_set_token.shaderStages, kRequiredFlags, VK_NULL_HANDLE,
                          "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 StatelessValidation::ValidateIndirectCommandsLayoutToken(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:
            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(*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(*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(*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.");
    }

    // 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 (SafeModulo(token.offset, 4) != 0) {
            skip |= LogError("VUID-VkIndirectCommandsLayoutTokenEXT-offset-11125", device, token_loc.dot(Field::offset),
                             "(%" PRIu32 ") is not aligned to 4.", token.offset);
        }
    }

    return skip;
}

bool StatelessValidation::ValidateIndirectCommandsLayoutStage(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 StatelessValidation::manual_PreCallValidateCreateIndirectCommandsLayoutEXT(
    VkDevice device, const VkIndirectCommandsLayoutCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator,
    VkIndirectCommandsLayoutEXT* pIndirectCommandsLayout, const ErrorObject& error_obj) const {
    bool skip = false;

    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) {
            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(token, token_loc);
        skip |= ValidateIndirectCommandsLayoutStage(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 StatelessValidation::ValidateGeneratedCommandsInfo(VkCommandBuffer command_buffer,
                                                        const VkGeneratedCommandsInfoEXT& generated_commands_info,
                                                        const Location& info_loc) const {
    bool skip = false;

    if (generated_commands_info.sequenceCountAddress != 0) {
        if (SafeModulo(generated_commands_info.sequenceCountAddress, 4) != 0) {
            skip |= LogError("VUID-VkGeneratedCommandsInfoEXT-sequenceCountAddress-11073", command_buffer,
                             info_loc.dot(Field::sequenceCountAddress), "(%" PRIuLEAST64 ") 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 (generated_commands_info.indirectAddress == 0) {
        skip |= LogError("VUID-VkGeneratedCommandsInfoEXT-indirectAddress-11076", command_buffer,
                         info_loc.dot(Field::indirectAddress), "is NULL.");
    } else if (SafeModulo(generated_commands_info.indirectAddress, 4) != 0) {
        skip |=
            LogError("VUID-VkGeneratedCommandsInfoEXT-indirectAddress-11074", command_buffer, info_loc.dot(Field::indirectAddress),
                     "(%" PRIuLEAST64 ") 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 StatelessValidation::manual_PreCallValidateCmdPreprocessGeneratedCommandsEXT(
    VkCommandBuffer commandBuffer, const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo, VkCommandBuffer stateCommandBuffer,
    const ErrorObject& error_obj) const {
    bool skip = false;
    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 StatelessValidation::manual_PreCallValidateCmdExecuteGeneratedCommandsEXT(
    VkCommandBuffer commandBuffer, VkBool32 isPreprocessed, const VkGeneratedCommandsInfoEXT* pGeneratedCommandsInfo,
    const ErrorObject& error_obj) const {
    bool skip = false;

    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;
}