/*
 * Copyright (c) 2015-2026 The Khronos Group Inc.
 * Copyright (c) 2015-2026 Valve Corporation
 * Copyright (c) 2015-2026 LunarG, 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 "shader_helper.h"

#include "test_common.h"
#include "glslang/SPIRV/GlslangToSpv.h"
#include <glslang/Public/ShaderLang.h>

#ifdef VVL_USE_SLANG
#pragma push_macro("None")
#pragma push_macro("Bool")
#undef None
#undef Bool

#include "slang.h"
#include "slang-com-ptr.h"

#pragma pop_macro("None")
#pragma pop_macro("Bool")
#endif

static void ProcessConfigFile(const VkPhysicalDeviceLimits& device_limits, TBuiltInResource& out_resources) {
    // These are the default resources for TBuiltInResources.
    out_resources.maxLights = 32;
    out_resources.maxClipPlanes = 6;
    out_resources.maxTextureUnits = 32;
    out_resources.maxTextureCoords = 32;
    out_resources.maxVertexAttribs = 64;
    out_resources.maxVertexUniformComponents = 4096;
    out_resources.maxVaryingFloats = 64;
    out_resources.maxVertexTextureImageUnits = 32;
    out_resources.maxCombinedTextureImageUnits = 80;
    out_resources.maxTextureImageUnits = 32;
    out_resources.maxFragmentUniformComponents = 4096;
    out_resources.maxDrawBuffers = 32;
    out_resources.maxVertexUniformVectors = 128;
    out_resources.maxVaryingVectors = 8;
    out_resources.maxFragmentUniformVectors = 16;
    out_resources.maxVertexOutputVectors = 16;
    out_resources.maxFragmentInputVectors = 15;
    out_resources.minProgramTexelOffset = -8;
    out_resources.maxProgramTexelOffset = 7;
    out_resources.maxClipDistances = device_limits.maxClipDistances;
    out_resources.maxComputeWorkGroupCountX = device_limits.maxComputeWorkGroupCount[0];
    out_resources.maxComputeWorkGroupCountY = device_limits.maxComputeWorkGroupCount[1];
    out_resources.maxComputeWorkGroupCountZ = device_limits.maxComputeWorkGroupCount[2];
    out_resources.maxComputeWorkGroupSizeX = device_limits.maxComputeWorkGroupSize[0];
    out_resources.maxComputeWorkGroupSizeY = device_limits.maxComputeWorkGroupSize[1];
    out_resources.maxComputeWorkGroupSizeZ = device_limits.maxComputeWorkGroupSize[2];
    out_resources.maxComputeUniformComponents = 1024;
    out_resources.maxComputeTextureImageUnits = 16;
    out_resources.maxComputeImageUniforms = 8;
    out_resources.maxComputeAtomicCounters = 8;
    out_resources.maxComputeAtomicCounterBuffers = 1;
    out_resources.maxVaryingComponents = 60;
    out_resources.maxVertexOutputComponents = device_limits.maxVertexOutputComponents;
    out_resources.maxGeometryInputComponents = device_limits.maxGeometryInputComponents;
    out_resources.maxGeometryOutputComponents = device_limits.maxGeometryOutputComponents;
    out_resources.maxFragmentInputComponents = device_limits.maxFragmentInputComponents;
    out_resources.maxImageUnits = 8;
    out_resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
    out_resources.maxCombinedShaderOutputResources = 8;
    out_resources.maxImageSamples = 0;
    out_resources.maxVertexImageUniforms = 0;
    out_resources.maxTessControlImageUniforms = 0;
    out_resources.maxTessEvaluationImageUniforms = 0;
    out_resources.maxGeometryImageUniforms = 0;
    out_resources.maxFragmentImageUniforms = 8;
    out_resources.maxCombinedImageUniforms = 8;
    out_resources.maxGeometryTextureImageUnits = 16;
    out_resources.maxGeometryOutputVertices = device_limits.maxGeometryOutputVertices;
    out_resources.maxGeometryTotalOutputComponents = device_limits.maxGeometryTotalOutputComponents;
    out_resources.maxGeometryUniformComponents = 1024;
    out_resources.maxGeometryVaryingComponents = 64;
    out_resources.maxTessControlInputComponents = 128;
    out_resources.maxTessControlOutputComponents = 128;
    out_resources.maxTessControlTextureImageUnits = 16;
    out_resources.maxTessControlUniformComponents = 1024;
    out_resources.maxTessControlTotalOutputComponents = 4096;
    out_resources.maxTessEvaluationInputComponents = 128;
    out_resources.maxTessEvaluationOutputComponents = 128;
    out_resources.maxTessEvaluationTextureImageUnits = 16;
    out_resources.maxTessEvaluationUniformComponents = 1024;
    out_resources.maxTessPatchComponents = 120;
    out_resources.maxPatchVertices = 32;
    out_resources.maxTessGenLevel = 64;
    out_resources.maxViewports = device_limits.maxViewports;
    out_resources.maxVertexAtomicCounters = 0;
    out_resources.maxTessControlAtomicCounters = 0;
    out_resources.maxTessEvaluationAtomicCounters = 0;
    out_resources.maxGeometryAtomicCounters = 0;
    out_resources.maxFragmentAtomicCounters = 8;
    out_resources.maxCombinedAtomicCounters = 8;
    out_resources.maxAtomicCounterBindings = 1;
    out_resources.maxVertexAtomicCounterBuffers = 0;
    out_resources.maxTessControlAtomicCounterBuffers = 0;
    out_resources.maxTessEvaluationAtomicCounterBuffers = 0;
    out_resources.maxGeometryAtomicCounterBuffers = 0;
    out_resources.maxFragmentAtomicCounterBuffers = 1;
    out_resources.maxCombinedAtomicCounterBuffers = 1;
    out_resources.maxAtomicCounterBufferSize = 16384;
    out_resources.maxTransformFeedbackBuffers = 4;
    out_resources.maxTransformFeedbackInterleavedComponents = 64;
    out_resources.maxCullDistances = device_limits.maxCullDistances;
    out_resources.maxCombinedClipAndCullDistances = 8;
    out_resources.maxSamples = 4;
    out_resources.maxMeshOutputVerticesNV = 256;
    out_resources.maxMeshOutputPrimitivesNV = 512;
    out_resources.maxMeshWorkGroupSizeX_NV = 32;
    out_resources.maxMeshWorkGroupSizeY_NV = 1;
    out_resources.maxMeshWorkGroupSizeZ_NV = 1;
    out_resources.maxTaskWorkGroupSizeX_NV = 32;
    out_resources.maxTaskWorkGroupSizeY_NV = 1;
    out_resources.maxTaskWorkGroupSizeZ_NV = 1;
    out_resources.maxMeshViewCountNV = 4;
    out_resources.maxMeshOutputVerticesEXT = 256;
    out_resources.maxMeshOutputPrimitivesEXT = 512;
    out_resources.maxMeshWorkGroupSizeX_EXT = 32;
    out_resources.maxMeshWorkGroupSizeY_EXT = 1;
    out_resources.maxMeshWorkGroupSizeZ_EXT = 1;
    out_resources.maxTaskWorkGroupSizeX_EXT = 32;
    out_resources.maxTaskWorkGroupSizeY_EXT = 1;
    out_resources.maxTaskWorkGroupSizeZ_EXT = 1;
    out_resources.maxMeshViewCountEXT = 4;

    out_resources.limits.nonInductiveForLoops = 1;
    out_resources.limits.whileLoops = 1;
    out_resources.limits.doWhileLoops = 1;
    out_resources.limits.generalUniformIndexing = 1;
    out_resources.limits.generalAttributeMatrixVectorIndexing = 1;
    out_resources.limits.generalVaryingIndexing = 1;
    out_resources.limits.generalSamplerIndexing = 1;
    out_resources.limits.generalVariableIndexing = 1;
    out_resources.limits.generalConstantMatrixVectorIndexing = 1;
}

//
// Convert VK shader type to compiler's
//
static EShLanguage FindLanguage(const VkShaderStageFlagBits shader_type) {
    switch (shader_type) {
        case VK_SHADER_STAGE_VERTEX_BIT:
            return EShLangVertex;

        case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
            return EShLangTessControl;

        case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
            return EShLangTessEvaluation;

        case VK_SHADER_STAGE_GEOMETRY_BIT:
            return EShLangGeometry;

        case VK_SHADER_STAGE_FRAGMENT_BIT:
            return EShLangFragment;

        case VK_SHADER_STAGE_COMPUTE_BIT:
            return EShLangCompute;

        case VK_SHADER_STAGE_RAYGEN_BIT_KHR:
            return EShLangRayGen;

        case VK_SHADER_STAGE_ANY_HIT_BIT_KHR:
            return EShLangAnyHit;

        case VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR:
            return EShLangClosestHit;

        case VK_SHADER_STAGE_MISS_BIT_KHR:
            return EShLangMiss;

        case VK_SHADER_STAGE_INTERSECTION_BIT_KHR:
            return EShLangIntersect;

        case VK_SHADER_STAGE_CALLABLE_BIT_KHR:
            return EShLangCallable;

        case VK_SHADER_STAGE_TASK_BIT_EXT:
            return EShLangTask;

        case VK_SHADER_STAGE_MESH_BIT_EXT:
            return EShLangMesh;

        default:
            assert(false);
            return EShLangVertex;
    }
}

struct GlslangTargetEnv {
    GlslangTargetEnv(const spv_target_env env) {
        switch (env) {
            case SPV_ENV_UNIVERSAL_1_0:
                language_version = glslang::EShTargetSpv_1_0;
                break;
            case SPV_ENV_UNIVERSAL_1_1:
                language_version = glslang::EShTargetSpv_1_1;
                break;
            case SPV_ENV_UNIVERSAL_1_2:
                language_version = glslang::EShTargetSpv_1_2;
                break;
            case SPV_ENV_UNIVERSAL_1_3:
                language_version = glslang::EShTargetSpv_1_3;
                break;
            case SPV_ENV_UNIVERSAL_1_4:
                language_version = glslang::EShTargetSpv_1_4;
                break;
            case SPV_ENV_UNIVERSAL_1_5:
                language_version = glslang::EShTargetSpv_1_5;
                break;
            case SPV_ENV_UNIVERSAL_1_6:
                language_version = glslang::EShTargetSpv_1_6;
                break;
            case SPV_ENV_VULKAN_1_0:
                client_version = glslang::EShTargetVulkan_1_0;
                break;
            case SPV_ENV_VULKAN_1_1:
                client_version = glslang::EShTargetVulkan_1_1;
                language_version = glslang::EShTargetSpv_1_3;
                break;
            case SPV_ENV_VULKAN_1_2:
                client_version = glslang::EShTargetVulkan_1_2;
                language_version = glslang::EShTargetSpv_1_5;
                break;
            case SPV_ENV_VULKAN_1_3:
                client_version = glslang::EShTargetVulkan_1_3;
                language_version = glslang::EShTargetSpv_1_6;
                break;
            default:
                assert(false && "Invalid SPIR-V environment");
                break;
        }
    }

    operator glslang::EShTargetLanguageVersion() const { return language_version; }

    operator glslang::EShTargetClientVersion() const { return client_version; }

  private:
    glslang::EShTargetLanguageVersion language_version = glslang::EShTargetSpv_1_0;
    glslang::EShTargetClientVersion client_version = glslang::EShTargetVulkan_1_0;
};

//
// Compile a given string containing GLSL into SPV for use by VK
// Return value of false means an error was encountered.
//
bool GLSLtoSPV(const VkPhysicalDeviceLimits& device_limits, const VkShaderStageFlagBits shader_type, const char* p_shader,
               std::vector<uint32_t>& spirv, const spv_target_env spv_env) {
    TBuiltInResource resources;
    ProcessConfigFile(device_limits, resources);

    EShMessages messages = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
    EShLanguage stage = FindLanguage(shader_type);
    glslang::TShader shader(stage);
    GlslangTargetEnv glslang_env(spv_env);
    shader.setEnvTarget(glslang::EshTargetSpv, glslang_env);
    shader.setEnvClient(glslang::EShClientVulkan, glslang_env);

    const char* shader_strings[1];
    shader_strings[0] = p_shader;
    shader.setStrings(shader_strings, 1);

    if (!shader.parse(&resources, 100, false, messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        return false;  // something didn't work
    }

    glslang::TProgram program;
    program.addShader(&shader);

    if (!program.link(messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        return false;
    }

    glslang::SpvOptions spv_options;
    glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &spv_options);

    return true;
}

//
// Compile a given string containing SPIR-V assembly into SPV for use by VK
// Return value of false means an error was encountered.
//
bool ASMtoSPV(const spv_target_env target_env, const uint32_t options, const char* p_asm, std::vector<uint32_t>& spv) {
    spv_binary binary;
    spv_diagnostic diagnostic = nullptr;
    spv_context context = spvContextCreate(target_env);
    spv_result_t error = spvTextToBinaryWithOptions(context, p_asm, strlen(p_asm), options, &binary, &diagnostic);
    spvContextDestroy(context);
    if (error) {
        spvDiagnosticPrint(diagnostic);
        spvDiagnosticDestroy(diagnostic);
        return false;
    }
    spv.insert(spv.end(), binary->code, binary->code + binary->wordCount);
    spvBinaryDestroy(binary);

    return true;
}

void CheckSlangSupport() {
#ifndef VVL_USE_SLANG
    GTEST_SKIP() << "Slang not supported on this platform";
#endif
}

bool SlangToSPV(const spv_target_env target_env, const char* slang_shader, const char* entry_point_name,
                std::vector<uint8_t>& out_bytes) {
#ifndef VVL_USE_SLANG
    (void)target_env;
    (void)slang_shader;
    (void)entry_point_name;
    (void)out_bytes;
    return false;
#else
    // Function adapted from
    // https://github.com/shader-slang/slang/blob/master/examples/hello-world/main.cpp#L114
    // Used https://github.com/shader-slang/slang/issues/6678 to figure out how to use
    // `slang::IModule::loadModuleFromSourceString`

    using namespace Slang;

    // First we need to create slang global session with work with the Slang API.
    ComPtr<slang::IGlobalSession> slang_session;
    {
        SlangGlobalSessionDesc slang_session_desc = {};
        // slang_session_desc.enableGLSL = true; // Could be needed in the future
        slang::createGlobalSession(&slang_session_desc, slang_session.writeRef());
    }

    // Next we create a compilation session to generate SPIRV code from Slang source.
    slang::TargetDesc targetDesc = {};
    targetDesc.format = SLANG_SPIRV;
    // Default currently is spirv_1_3 (vulkan 1.1) as that is what Slang does by default
    std::string profile = "spirv_1_3";
    if (target_env == SPV_ENV_VULKAN_1_2) {
        profile = "spirv_1_5";
    } else if (target_env == SPV_ENV_VULKAN_1_3 || target_env == SPV_ENV_VULKAN_1_4) {
        profile = "spirv_1_6";
    }
    targetDesc.profile = slang_session->findProfile(profile.c_str());
    targetDesc.flags = 0;
    slang::SessionDesc sessionDesc = {};
    sessionDesc.targets = &targetDesc;
    sessionDesc.targetCount = 1;

    std::vector<slang::CompilerOptionEntry> options;
    options.push_back(
        {slang::CompilerOptionName::EmitSpirvDirectly, {slang::CompilerOptionValueKind::Int, 1, 0, nullptr, nullptr}});
    // https://github.com/shader-slang/slang/issues/6248
    options.push_back(
        {slang::CompilerOptionName::VulkanUseEntryPointName, {slang::CompilerOptionValueKind::Int, 1, 0, nullptr, nullptr}});
    sessionDesc.compilerOptionEntries = options.data();
    sessionDesc.compilerOptionEntryCount = options.size();

    ComPtr<slang::ISession> session;
    SlangResult result = slang_session->createSession(sessionDesc, session.writeRef());
    if (result != 0) {
        ADD_FAILURE() << "Slang failure: slang_session->createSession()";
    }

    // Handle errors
    Slang::ComPtr<ISlangBlob> diagnostics;

    // Compile the source code
    // Once the session has been obtained, we can start loading code into it.
    // Here we use `loadModuleFromSourceString` and not `loadModule`.
    // moduleName and path are just placeholders
    Slang::ComPtr<slang::IModule> slang_module;
    slang_module = session->loadModuleFromSourceString("my_shader", "my_shader.slang", slang_shader, diagnostics.writeRef());
    if (slang_module == NULL) {
        ADD_FAILURE() << "Slang failure: loadModuleFromSourceString()\n" << ((const char*)diagnostics->getBufferPointer());
        return false;
    }

    // Loading the module will compile and check all the shader code in it,
    // including the shader entry points we want to use. Now that the module is loaded
    // we can look up those entry points by name.
    //
    // Note: If you are using this `loadModule` approach to load your shader code it is
    // important to tag your entry point functions with the `[shader("...")]` attribute
    // (e.g., `[shader("compute")] void computeMain(...)`). Without that information there
    // is no unambiguous way for the compiler to know which functions represent entry
    // points when it parses your code via `loadModule()`.
    //
    ComPtr<slang::IEntryPoint> entry_point;
    result = slang_module->findEntryPointByName(entry_point_name, entry_point.writeRef());
    if (result != 0) {
        ADD_FAILURE() << "Slang failure: loadModuleFromSourceString()\nCould not find entry point \"" << entry_point_name << "\"";
        return false;
    }

    // At this point we have a few different Slang API objects that represent
    // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`.
    //
    // A single Slang module could contain many different entry points (e.g.,
    // four vertex entry points, three fragment entry points, and two compute
    // shaders), and before we try to generate output code for our target API
    // we need to identify which entry points we plan to use together.
    //
    // Modules and entry points are both examples of *component types* in the
    // Slang API. The API also provides a way to build a *composite* out of
    // other pieces, and that is what we are going to do with our module
    // and entry points.
    //
    std::vector<slang::IComponentType*> componentTypes;
    componentTypes.emplace_back(slang_module);
    componentTypes.emplace_back(entry_point);

    // Actually creating the composite component type is a single operation
    // on the Slang session, but the operation could potentially fail if
    // something about the composite was invalid (e.g., you are trying to
    // combine multiple copies of the same module), so we need to deal
    // with the possibility of diagnostic output.
    //
    ComPtr<slang::IComponentType> composedProgram;
    {
        result = session->createCompositeComponentType(componentTypes.data(), (SlangInt)componentTypes.size(),
                                                       composedProgram.writeRef(), diagnostics.writeRef());
        if (result != 0) {
            ADD_FAILURE() << "Slang failure: createCompositeComponentType()\n" << ((const char*)diagnostics->getBufferPointer());
            return false;
        }
    }

    // Now we can call `composedProgram->getEntryPointCode()` to retrieve the
    // compiled SPIRV code that we will use to create a vulkan compute pipeline.
    // This will trigger the final Slang compilation and spirv code generation.
    ComPtr<slang::IBlob> spirvCode;
    {
        result = composedProgram->getEntryPointCode(0, 0, spirvCode.writeRef(), diagnostics.writeRef());
        if (result != 0) {
            ADD_FAILURE() << "Slang failure: createCompositeComponentType()\n" << ((const char*)diagnostics->getBufferPointer());
            return false;
        }
    }

    out_bytes.resize(spirvCode->getBufferSize());
    std::memcpy(out_bytes.data(), spirvCode->getBufferPointer(), spirvCode->getBufferSize());

    return true;
#endif
}

const VkPipelineShaderStageCreateInfo& VkShaderObj::GetStageCreateInfo(void* update_pnext) {
    // With things (but not limited to) descriptor heap, it is desired to have a pNext chain, but instead of trying to put it when
    // creating a VkShaderObj (which already has way too many parameters) just apply it when we grab it as we want the pNext
    // per-pipeline, not per-shaderModule in practice.
    if (update_pnext) {
        m_stage_info.pNext = update_pnext;
    }

    return m_stage_info;
}

VkShaderObj::VkShaderObj(vkt::Device& device, const char* source, VkShaderStageFlagBits stage, const spv_target_env env,
                         SpvSourceType source_type, const VkSpecializationInfo* spec_info, const char* entry_point,
                         const void* shader_module_ci_pNext, const void* pipeline_shader_stage_ci_pNext)
    : m_device(&device), m_source(source), m_spv_env(env) {
    m_stage_info = vku::InitStructHelper();
    m_stage_info.flags = 0;
    m_stage_info.stage = stage;
    m_stage_info.module = VK_NULL_HANDLE;
    m_stage_info.pName = entry_point;
    m_stage_info.pSpecializationInfo = spec_info;
    m_stage_info.pNext = pipeline_shader_stage_ci_pNext;
    if (source_type == SPV_SOURCE_GLSL) {
        InitFromGLSL(shader_module_ci_pNext);
    } else if (source_type == SPV_SOURCE_ASM) {
        InitFromASM();
    } else if (source_type == SPV_SOURCE_SLANG) {
        InitFromSlang();
    }
}

bool VkShaderObj::InitFromGLSL(const void* shader_module_ci_pNext) {
    std::vector<uint32_t> spv;
    GLSLtoSPV(m_device->Physical().limits_, m_stage_info.stage, m_source, spv, m_spv_env);

    VkShaderModuleCreateInfo module_ci = vku::InitStructHelper();
    module_ci.pNext = shader_module_ci_pNext;
    module_ci.codeSize = spv.size() * sizeof(uint32_t);
    module_ci.pCode = spv.data();

    Init(*m_device, module_ci);
    m_stage_info.module = handle();
    return VK_NULL_HANDLE != handle();
}

// Because shaders are currently validated at pipeline creation time, there are test cases that might fail shader module
// creation due to supplying an invalid/unknown SPIR-V capability/operation. This is called after VkShaderObj creation when
// tests are found to crash on a CI device
VkResult VkShaderObj::InitFromGLSLTry(const vkt::Device* custom_device) {
    std::vector<uint32_t> spv;
    // 99% of tests just use the framework's VkDevice, but this allows for tests to use custom device object
    // Can't set at contructor time since all reference members need to be initialized then.
    VkPhysicalDeviceLimits limits = (custom_device) ? custom_device->Physical().limits_ : m_device->Physical().limits_;
    GLSLtoSPV(limits, m_stage_info.stage, m_source, spv, m_spv_env);

    VkShaderModuleCreateInfo moduleCreateInfo = vku::InitStructHelper();
    moduleCreateInfo.codeSize = spv.size() * sizeof(uint32_t);
    moduleCreateInfo.pCode = spv.data();

    const auto result = InitTry(((custom_device) ? *custom_device : *m_device), moduleCreateInfo);
    m_stage_info.module = handle();
    return result;
}

bool VkShaderObj::InitFromASM() {
    std::vector<uint32_t> spv;
    ASMtoSPV(m_spv_env, 0, m_source, spv);

    VkShaderModuleCreateInfo moduleCreateInfo = vku::InitStructHelper();
    moduleCreateInfo.codeSize = spv.size() * sizeof(uint32_t);
    moduleCreateInfo.pCode = spv.data();

    Init(*m_device, moduleCreateInfo);
    m_stage_info.module = handle();
    return VK_NULL_HANDLE != handle();
}

VkResult VkShaderObj::InitFromASMTry() {
    std::vector<uint32_t> spv;
    ASMtoSPV(m_spv_env, 0, m_source, spv);

    VkShaderModuleCreateInfo moduleCreateInfo = vku::InitStructHelper();
    moduleCreateInfo.codeSize = spv.size() * sizeof(uint32_t);
    moduleCreateInfo.pCode = spv.data();

    const auto result = InitTry(*m_device, moduleCreateInfo);
    m_stage_info.module = handle();
    return result;
}

bool VkShaderObj::InitFromSlang() {
#ifndef VVL_USE_SLANG
    return false;
#else
    std::vector<uint8_t> bytes;
    if (!SlangToSPV(m_spv_env, m_source, m_stage_info.pName, bytes)) {
        return false;
    }
    VkShaderModuleCreateInfo module_ci = vku::InitStructHelper();
    module_ci.codeSize = bytes.size();
    module_ci.pCode = (uint32_t*)bytes.data();

    const auto result = InitTry(*m_device, module_ci);
    m_stage_info.module = handle();
    return result == VK_SUCCESS;
#endif
}

// static
VkShaderObj VkShaderObj::CreateFromGLSL(VkRenderFramework* framework, const char* source, VkShaderStageFlagBits stage,
                                        const spv_target_env spv_env, const VkSpecializationInfo* spec_info,
                                        const char* entry_point) {
    auto shader = VkShaderObj(*framework->DeviceObj(), source, stage, spv_env, SPV_SOURCE_GLSL_TRY, spec_info, entry_point);
    if (VK_SUCCESS == shader.InitFromGLSLTry()) {
        return shader;
    }
    return {};
}

// static
VkShaderObj VkShaderObj::CreateFromASM(VkRenderFramework* framework, const char* source, VkShaderStageFlagBits stage,
                                       const spv_target_env spv_env, const VkSpecializationInfo* spec_info,
                                       const char* entry_point) {
    auto shader = VkShaderObj(*framework->DeviceObj(), source, stage, spv_env, SPV_SOURCE_ASM_TRY, spec_info, entry_point);
    if (VK_SUCCESS == shader.InitFromASMTry()) {
        return shader;
    }
    return {};
}

VkDescriptorSetAndBindingMappingEXT MakeSetAndBindingMapping(uint32_t set, uint32_t binding, uint32_t count,
                                                             VkSpirvResourceTypeFlagsEXT mask) {
    VkDescriptorSetAndBindingMappingEXT mapping = vku::InitStructHelper();
    mapping.descriptorSet = set;
    mapping.firstBinding = binding;
    mapping.bindingCount = count;
    mapping.resourceMask = mask;
    return mapping;
}

VkDescriptorSetAndBindingMappingEXT MakeZeroSetAndBindingMapping(uint32_t set, uint32_t binding,
                                                                 VkDescriptorMappingSourceEXT source) {
    VkDescriptorSetAndBindingMappingEXT mapping = vku::InitStructHelper();
    mapping.descriptorSet = set;
    mapping.firstBinding = binding;
    mapping.bindingCount = 1;
    mapping.resourceMask = VK_SPIRV_RESOURCE_TYPE_ALL_EXT;
    mapping.source = source;
    mapping.sourceData = {};
    return mapping;
}