如何使用glslang

问题描述 投票:0回答:5

我正在尝试使用 glslang 将 glsl 着色器代码编译为 SPIR-V 二进制文件。 glslang 项目可以在这里找到:

https://github.com/KhronosGroup/glslang

通过在命令行中手动运行 glslangValidator.exe 可以很好地工作。 但我想使用 C++ 接口。

我已经按照 github 页面上的描述构建了项目,现在我正在努力解决如何实际使用该界面的问题。

我宁愿不在我的解决方案中实际包含任何项目(我正在使用 Visual Studio),而是链接使用它所需的 .lib 和标头。 我只是找不到我需要链接哪些。 github页面只提到了ShaderLang.h和StandAlone.cpp,这是不够的。

有人可以解释如何设置一个可以使用 glslang 的项目吗?我只需要它来将 glsl 着色器代码编译为 SPIR-V 二进制文件(带有有关着色器编译的调试信息)。 我想对于已经做过或有更多经验的人来说,这将是一个非常简单的问题。

c++ glsl shader vulkan spir-v
5个回答
5
投票

您需要使用几个库。一个可供查看的消费者示例是 LunarGLASS:https://github.com/LunarG/LunarGLASS。在那里,您可以看到该文件:

https://github.com/LunarG/LunarGLASS/blob/master/CMakeLists.txt

哪个里面包含这个库:

set(GLSLANGLIBS
    glslang
    HLSL
    OSDependent
    OGLCompiler
    SPIRV)

glslang 的自述文件包含一些重要信息。此外,在 glslang 中,glslangValidator 工具(基本上是 StandAlone.cpp)展示了如何使用库的 API。您还可以在 LunarGLASS 项目中查看 Frontends/glslang 目录以进行类似的用途。


5
投票

Shaderc 项目位于 https://github.com/google/shaderc 提供了一个易于使用的 C++ API,它将 Glslang 的编译器包装到 SPIR-V。

有关示例用法,请参阅 https://github.com/google/shaderc/blob/master/examples/online-compile/main.cc


5
投票

您可以使用

glslang
C 接口 (https://github.com/KhronosGroup/glslang/pull/2038) 将着色器源代码编译为 SPIR-V,如下所示。

const char* shaderCodeVertex = ...;

const glslang_input_t input =
{
    .language = GLSLANG_SOURCE_GLSL,
    .stage = GLSLANG_STAGE_VERTEX,
    .client = GLSLANG_CLIENT_VULKAN,
    .client_version = GLSLANG_TARGET_VULKAN_1_1,
    .target_language = GLSLANG_TARGET_SPV,
    .target_language_version = GLSLANG_TARGET_SPV_1_3,
    .code = shaderCodeVertex,
    .default_version = 100,
    .default_profile = GLSLANG_NO_PROFILE,
    .force_default_version_and_profile = false,
    .forward_compatible = false,
    .messages = GLSLANG_MSG_DEFAULT_BIT,
};

glslang_initialize_process();

glslang_shader_t* shader = glslang_shader_create( &input );

if ( !glslang_shader_preprocess(shader, &input) )
{
    // use glslang_shader_get_info_log() and glslang_shader_get_info_debug_log()
}

if ( !glslang_shader_parse(shader, &input) )
{
    // use glslang_shader_get_info_log() and glslang_shader_get_info_debug_log()
}

glslang_program_t* program = glslang_program_create();
glslang_program_add_shader( program, shader );

if (!glslang_program_link(program, GLSLANG_MSG_SPV_RULES_BIT | GLSLANG_MSG_VULKAN_RULES_BIT))
{
    // use glslang_program_get_info_log() and glslang_program_get_info_debug_log();
}

glslang_program_SPIRV_generate( program, input.stage );

if ( glslang_program_SPIRV_get_messages(program) )
{
    printf("%s", glslang_program_SPIRV_get_messages(program));
}

glslang_shader_delete( shader );

编译后的 SPIR-V blob 可以通过以下方式与 Vulkan 一起使用。

const VkShaderModuleCreateInfo ci =
{
    .sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
    .codeSize = glslang_program_SPIRV_get_size(program) * sizeof(unsigned int),
    .pCode    = glslang_program_SPIRV_get_ptr(program)
};

VkResult result = vkCreateShaderModule(device, &ci, nullptr, ...);

glslang_program_delete( program );

1
投票

以下内容对我有用。 CMakeLists.txt:

...
find_package(glslang CONFIG REQUIRED)
find_package(Threads REQUIRED)
...
target_link_libraries(target_name 
   PUBLIC 
     glslang::glslang 
     glslang::SPIRV 
     glslang::glslang-default-resource-limits)
...

glslang::glslang-default-resource-limits
glslang_default_resource()
功能所必需的。

main()
功能:

#include <glslang/Include/glslang_c_interface.h>

int main() {
   glslang_initialize_process();
   ...
   glslang_finalize_process();
   return 0;
}

别忘了将

resource
传递给
glslang_input_t 
:

#include <glslang/Include/glslang_c_interface.h>
#include <glslang/Public/resource_limits_c.h>
...
    const glslang_input_t input =
    {
        .language = GLSLANG_SOURCE_GLSL,
        .stage = GLSLANG_STAGE_COMPUTE,
        .client = GLSLANG_CLIENT_VULKAN,
        .client_version = GLSLANG_TARGET_VULKAN_1_1,
        .target_language = GLSLANG_TARGET_SPV,
        .target_language_version = GLSLANG_TARGET_SPV_1_3,
        .code = shaderCode,
        .default_version = 460,
        .default_profile = GLSLANG_CORE_PROFILE,
        .force_default_version_and_profile = 0,
        .forward_compatible = 0,
        .messages = GLSLANG_MSG_DEFAULT_BIT,
        .resource = glslang_default_resource() // Load defaults or create resource manualy!
    };
...

着色器加载器:

    const char* shaderCode = ...;
    glslang_shader_t* shader = glslang_shader_create(&input);
    if (!glslang_shader_preprocess(shader, &input)) { /* errors and logs */ }
    if (!glslang_shader_parse(shader, &input)) { /* errors and logs */ }
    glslang_program_t* program = glslang_program_create();
    glslang_program_add_shader(program, shader);
    if (!glslang_program_link(program, GLSLANG_MSG_SPV_RULES_BIT | GLSLANG_MSG_VULKAN_RULES_BIT)) 
    { /* errors and logs */ }
    glslang_program_SPIRV_generate(program, input.stage);
    if (glslang_program_SPIRV_get_messages(program)) 
    { /* errors and logs */ }
    const VkShaderModuleCreateInfo info =
    {
        .sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
        .codeSize = glslang_program_SPIRV_get_size(program) * sizeof(unsigned int),
        .pCode    = glslang_program_SPIRV_get_ptr(program)
    };

    VkShaderModule shaderModule;
    if (vkCreateShaderModule(device, &info, NULL, &shaderModule) != VK_SUCCESS) {
        /* errors and logs */
    }

    ...

    glslang_shader_delete(shader);
    glslang_program_delete(program);

0
投票

如何使用C++接口

虽然是一个有点老的问题,但我最近在使用 C++ 接口方面遇到了困难,所以我决定在这里发布我的发现。

包含者

这可能是最棘手的部分。这是我们在编写 C 或 C++ 程序时不会考虑的事情,但 GLSL 允许通过

#include
预处理指令包含其他着色器文件,并且它的行为不是为我们定义的。

在文件

glslang/Public/ShaderLang.h
中我们定义了一个抽象类,总结一下:

class Includer {
public:
    struct IncludeResult {
        // [...]
    };

    virtual IncludeResult* includeSystem(const char* /*headerName*/,
                                         const char* /*includerName*/,
                                         size_t /*inclusionDepth*/) { return nullptr; }
    virtual IncludeResult* includeLocal(const char* /*headerName*/,
                                        const char* /*includerName*/,
                                        size_t /*inclusionDepth*/) { return nullptr; }
    virtual void releaseInclude(IncludeResult*) = 0;
    virtual ~Includer() {}
};

它的用法只是非常模糊地描述,但从我所收集的信息(通过阅读代码)我们应该创建一个继承自

Includer
的类,并实现上面显示的方法。从本质上讲,这些方法在本地文件系统中搜索匹配
headerName
的 GLSL 文件,读取其内容,并在新的
Includer::IncludeResult
结构中返回其内容。

更麻烦的是,此标头查找本身可能会导致递归查询!

自定义包含器

这个实现大致是从我自己的代码复制/粘贴的(不幸的是我无法开源atm)。理解它的工作原理应该相当容易:

// includes
#include <glslang/Public/ShaderLang.h>
#include <map>
#include <vector>

class GlslShaderIncluder : public glslang::TShader::Includer
{
public:
//    explicit GlslShaderIncluder(fileio::Directory* shaderdir)
//        : mShaderdir(shaderdir) {}

    // Note "local" vs. "system" is not an "either/or": "local" is an
    // extra thing to do over "system". Both might get called, as per
    // the C++ specification.
    //
    // For the "system" or <>-style includes; search the "system" paths.
    virtual IncludeResult* includeSystem(
        const char* headerName, const char* includerName, size_t inclusionDepth) override;

    // For the "local"-only aspect of a "" include. Should not search in the
    // "system" paths, because on returning a failure, the parser will
    // call includeSystem() to look in the "system" locations.
    virtual IncludeResult* includeLocal(
        const char* headerName, const char* includerName, size_t inclusionDepth) override;

    virtual void releaseInclude(IncludeResult*) override;

private:
    static inline const std::string sEmpty = "";
    static inline IncludeResult smFailResult =
        IncludeResult(sEmpty, "Header does not exist!", 0, nullptr);

//    const fileio::Directory* mShaderdir {nullptr};
    std::map<std::string, IncludeResult*> mIncludes;
    std::map<std::string, std::vector<char>> mSources;
};

主要由于时间限制,实施工作尚未完成。不支持系统包含(缺少用例),但这应该类似于处理本地包含的方式。该代码还依赖于自定义文件 I/O 库,因此这些部分被注释掉(它也可以使用

std::ifstream
fopen
fileio
命名空间在下面使用
std::filesystem
):

IncludeResult* GlslShaderIncluder::includeSystem(
    const char* headerName, const char* includerName, size_t inclusionDepth)
{
    // TODO: This should be used if a shader file says "#include <source>",
    // in which case it includes a "system" file instead of a local file.
    log_error("GlslShaderIncluder::includeSystem() is not implemented!");
    log_error("includeSystem({}, {}, {})", headerName, includerName, inclusionDepth);
    return nullptr;
}

IncludeResult* GlslShaderIncluder::includeLocal(
    const char* headerName, const char* includerName, size_t inclusionDepth)
{
    log_debug("includeLocal({}, {}, {})", headerName, includerName, inclusionDepth);

//    std::string resolvedHeaderName =
//        fileio::directory_get_absolute_path(mShaderdir, headerName);
    if (auto it = mIncludes.find(resolvedHeaderName); it != mIncludes.end())
    {
        // `headerName' was already present, so return that, and probably log about it
        return it->second;
    }

//    if (!fileio::file_exists(mShaderdir, headerName))
//    {
//        log_error("#Included GLSL shader file \"{}\" does not exist!", resolvedHeaderName);
//        return &smFailResult;
//    }

//    mSources[resolvedHeaderName] = {}; // insert an empty vector!
//    fileio::File* file = fileio::file_open(
//        mShaderdir, headerName, fileio::FileModeFlag::read);
//    if (file == nullptr)
//    {
//        log_error("Failed to open #included GLSL shader file: {}", resolvedHeaderName);
//        return &smFailResult;
//    }

//    if (!fileio::file_read_into_buffer(file, mSources[resolvedHeaderName]))
//    {
//        log_error("Failed to read #included GLSL shader file: {}", resolvedHeaderName);
//        fileio::file_close(file);
//        return &smFailResult;
//    }

    IncludeResult* result = new IncludeResult(
        resolvedHeaderName, mSources[resolvedHeaderName].data(),
        mSources[resolvedHeaderName].size(), nullptr);

    auto [it, b] = mIncludes.emplace(std::make_pair(resolvedHeaderName, result));
    if (!b)
    {
        log_error("Failed to insert IncludeResult into std::map!");
        return &smFailResult;
    }
    return it->second;
}

void GlslShaderIncluder::releaseInclude(IncludeResult* result)
{
    log_debug("releaseInclude(result->headerName: {})", result->headerName);
    if (auto it = mSources.find(result->headerName); it != mSources.end())
    {
        mSources.erase(it);
    }
    if (auto it = mIncludes.find(result->headerName); it != mIncludes.end())
    {
        // EDIT: I have forgotten to use "delete" here on the IncludeResult, but should probably be done!
        mIncludes.erase(it);
    }
}

使用包含器

定义自定义包含器后,我们可以继续将其注入到

glslang
加载器(这也是一个对象)中:

std::vector<char> buffer; // contains the shader file

glslang::TShader shader(EShLangVertex); // example, use this code for each separate shader file in the shader program

const char* sources[1] = { buffer.data() };
shader.setStrings(sources, 1);

// Use appropriate Vulkan version
glslang::EShTargetClientVersion targetApiVersion = glslang::EShTargetVulkan_1_3;
shader.setEnvClient(glslang::EShClientVulkan, targetApiVersion);

glslang::EShTargetLanguageVersion spirvVersion = glslang::EShTargetSpv_1_3;
shader.setEnvTarget(glslang::EshTargetSpv, spirvVersion);

shader.setEntryPoint("main"); // We can specify a different entry point

// The resource is an entire discussion in and by itself, here just use default.
TBuiltInResource* resources = GetDefaultResources();
// int defaultVersion = 110, // use 100 for ES environment, overridden by #version in shader
const int defaultVersion = 450;
const bool forwardCompatible = false;
const EShMessages messageFlags = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
EProfile defaultProfile = ENoProfile; // NOTE: Only for desktop, before profiles showed up!

// NOTE: Here a custom file I/O library is used, your implementation may be different.
fileio::Directory* shaderdir = ...
GlslShaderIncluder includer(shaderdir);

std::string preprocessedStr;
if (!shader.preprocess(
    resources, defaultVersion, defaultProfile, false, forwardCompatible, messageFlags, &preprocessedStr, includer))
{
    log_error("Failed to preprocess shader: {}", shader.getInfoLog());
    // FAIL
}
const char* preprocessedSources[1] = { preprocessedStr.c_str() };
shader.setStrings(preprocessedSources, 1);

if (!shader.parse(resources, defaultVersion, defaultProfile, false,
    forwardCompatible, messageFlags, includer))
{
    vtek_log_error("Failed to parse shader: {}", shader.getInfoLog());
    // FAIL
}

glslang::TProgram program;
program.addShader(&shader);
if (!program.link(messageFlags))
{
    vtek_log_error("Failed to link shader: {}", program.getInfoLog());
    // FAIL
}

// Convert the intermediate generated by glslang to Spir-V
glslang::TIntermediate& intermediateRef = *(program.getIntermediate(lang));
std::vector<uint32_t> spirv;
glslang::SpvOptions options{};
options.validate = true;
// TODO: We can also provide a logger to glslang.
// glslang::spv::SpvBuildLogger logger;
// glslang::GlslangToSpv(intermediateRef, spirv, &logger, &options);
glslang::GlslangToSpv(intermediateRef, spirv, &options);

最后,我们有一个数组

std::vector<uint32_t> spirv
,其中包含 SPIR-V 字节码。这可用于创建 Vulkan 着色器模块。这里,我必须注意,
codeSize
应该乘以4,以确保我们向GPU发送正确的字节数:

VkShaderModule module;
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = spirvCode.size() * sizeof(uint32_t);
createInfo.pCode = spirvCode.data();
VkResult result = vkCreateShaderModule(dev, &createInfo, nullptr, &module);
if (result != VK_SUCCESS)
{
    log_error("Failed to create {} shader module!", type);
    // FAIL
}

大致来说就是这样。该系统处理递归本地包含,并进行一些错误处理。我希望它能帮助下一个人。

© www.soinside.com 2019 - 2024. All rights reserved.