shader-slang / slang

Making it easier to work with shaders
http://shader-slang.com
Other
3.15k stars 214 forks source link

Reflection API : How to know if push constants are used by an entry point (using HLSL syntax) #5685

Open PierreEVEN opened 5 days ago

PierreEVEN commented 5 days ago

Hi,

I would like to know if a push constant buffer is used by a given entry point.

I was initially using the hlsl syntax to declare my push constants:

struct PushConstsStruct
{
    float4x4 model;
};
[[vk::push_constant]] PushConstsStruct pc;

(Note : I replaced it with the slang syntax later, which led me to another issue : https://github.com/shader-slang/slang/issues/5676)

With available reflection functions, the only way I found to know if a given parameter is used is :

slang::IMetadata* metadata;
program->getEntryPointMetadata(0, 0, &metadata, diagnostics.writeRef());
if (diagnostics)
    return result.push_error({static_cast<const char*>(diagnostics->getBufferPointer())});

StageData data;
slang::ProgramLayout* program_lay = program->getLayout();
for (unsigned par_i = 0; par_i < program_lay ->getParameterCount(); par_i++)
{
   // will always be set to false if it is a push constant
    bool b_is_used = true;
    slang::VariableLayoutReflection* parameter = program_lay ->getParameterByIndex(par_i);
    metadata->isParameterLocationUsed(static_cast<SlangParameterCategory>(parameter->getCategory()), 0, parameter->getBindingIndex(), b_is_used);
    ...
}

which doesn't works as it always return false.

I'm not sure that isParameterLocationUsed() is expected to works with push constants, but it's the only available method so I guess there is something missing.

here is the full shader : playground-link

And how I use the reflection Api :


slang::IModule* module = {...} // target is spirv_1.5

for (SlangInt32 ep_i = 0; ep_i < module->getDefinedEntryPointCount(); ++ep_i)
{
    Slang::ComPtr<slang::IEntryPoint> entry_point;
    if (SLANG_FAILED(module->getDefinedEntryPoint(ep_i, entry_point.writeRef())))
        return "error";

    std::vector<slang::IComponentType*> components;
    components.emplace_back(entry_point);
    Slang::ComPtr<slang::IComponentType> program;
    if (SLANG_FAILED(session->createCompositeComponentType(components.data(), components.size(), program.writeRef())))
        return "error";

    Slang::ComPtr<slang::IComponentType> linkedProgram;
    Slang::ComPtr<slang::IBlob>          diagnostics;
    program->link(linkedProgram.writeRef(), diagnostics.writeRef());
    if (diagnostics)
        return "error";

    slang::IMetadata* metadata;
    linkedProgram->getEntryPointMetadata(0, 0, &metadata, diagnostics.writeRef());
    if (diagnostics)
        return "error";

    StageData             data;
    slang::ProgramLayout* shaderReflection = linkedProgram->getLayout();
    for (unsigned par_i = 0; par_i < shaderReflection->getParameterCount(); par_i++)
    {
        bool                             b_is_used = true;
        slang::VariableLayoutReflection* parameter = shaderReflection->getParameterByIndex(par_i);
        metadata->isParameterLocationUsed(static_cast<SlangParameterCategory>(parameter->getCategory()), 0, parameter->getBindingIndex(), b_is_used);

        if (parameter->getCategory() == slang::PushConstantBuffer && !b_is_used)
                std::cout << std::format("ERROR : UNUSED PC !! {} \n", parameter->getName());
    }
}
jkwak-work commented 4 days ago

Maybe you should use getBindingSpace() like this:

metadata->isParameterLocationUsed(
    static_cast<SlangParameterCategory>(parameter->getCategory()),
    parameter->getBindingSpace(), // <==== THIS ONE
    parameter->getBindingIndex(),
    b_is_used);

If you dump the reflection JSON file, it may help you to debug it.

slangc.exe -reflection-json myShader.json myShader.slang
tangent-vector commented 4 days ago

It would help to have a complete reproducer for this issue, so that somebody on the Slang dev team can debug it.

In principle the IMetaData::isParameterLocationUsed API should work for things like push constants, but to my knowledge this scenario has not been tested before. Thus it is entirely possible that this is a bug in the Slang implementation, but it is also possible that there is some issue in the application code.

PierreEVEN commented 2 days ago

Sure ! I've made a short simplified example : (it will fail if 'pc' is not detected as used) (I don't know what kind of format you would prefer for a reproducer)

slang shader :

struct PushConsts
{
    float3 data;
};
[[vk::push_constant]] PushConsts pc;

[shader("fragment")]
float3 fragment()
{
    return pc.data;
}

c++ code : (tested with msvc / cpp17 / latest slang version)

#include <filesystem>
#include <iostream>
#include <optional>
#include <string>

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

std::optional<std::string> test_is_parameter_location_used(const std::filesystem::path& module_path)
{
    Slang::ComPtr<slang::IBlob> diagnostics;

    // CREATE GLOBAL SESSION
    Slang::ComPtr<slang::IGlobalSession> global_session;
    if (SLANG_FAILED(slang::createGlobalSession(global_session.writeRef())))
        return "Failed to create global slang compiler session";

    // CREATE SESSION
    slang::SessionDesc sessionDesc;
    slang::TargetDesc targetDesc;
    targetDesc.format = SLANG_SPIRV;
    targetDesc.profile = global_session->findProfile("spirv_1_5");
    if (targetDesc.profile == SLANG_PROFILE_UNKNOWN)
        return "Failed to find slang profile 'spirv_1_5'";
    sessionDesc.targets = &targetDesc;
    sessionDesc.targetCount = 1;
    Slang::ComPtr<slang::ISession> session;
    if (SLANG_FAILED(global_session->createSession(sessionDesc, session.writeRef())))
        return "Failed to create slang compiler session";

    // LOAD EXAMPLE MODULE FROM PATH 'module_path'
    slang::IModule* module = session->loadModule(module_path.string().c_str(), diagnostics.writeRef());
    if (diagnostics)
        return static_cast<const char*>(diagnostics->getBufferPointer());

    // ITERATE OVER ENTRY POINTS
    for (SlangInt32 ep_i = 0; ep_i < module->getDefinedEntryPointCount(); ++ep_i)
    {
        Slang::ComPtr<slang::IEntryPoint> entry_point;
        if (SLANG_FAILED(module->getDefinedEntryPoint(ep_i, entry_point.writeRef())))
            return "Failed to get entry point";

        // CREATE COMPOSITE COMPONENT TYPE
        std::vector<slang::IComponentType*> components{entry_point};
        Slang::ComPtr<slang::IComponentType> program;
        if (SLANG_FAILED(
            session->createCompositeComponentType(components.data(), components.size(), program.writeRef())))
            return "Failed to create stage program";

        // LINK PROGRAM
        Slang::ComPtr<slang::IComponentType> linked_program;
        program->link(linked_program.writeRef(), diagnostics.writeRef());
        if (diagnostics)
            return static_cast<const char*>(diagnostics->getBufferPointer());

        // GET REFLECTION DATA
        slang::IMetadata* metadata;
        linked_program->getEntryPointMetadata(0, 0, &metadata, diagnostics.writeRef());
        if (diagnostics)
            return static_cast<const char*>(diagnostics->getBufferPointer());

        // ITERATE OVER PARAMETERS
        slang::ProgramLayout* shaderReflection = linked_program->getLayout();
        for (unsigned par_i = 0; par_i < shaderReflection->getParameterCount(); par_i++)
        {
            slang::VariableLayoutReflection* parameter = shaderReflection->getParameterByIndex(par_i);

            if (parameter->getCategory() == slang::PushConstantBuffer)
            {
                bool b_is_used = true;
                metadata->isParameterLocationUsed(
                    static_cast<SlangParameterCategory>(parameter->getCategory()),
                    0,
                    parameter->getBindingIndex(),
                    b_is_used);

                // In this example, all push constant are used so it should never be false
                if (!b_is_used)
                    return "Error !!!! Push constant parameter '" +
                        std::string(parameter->getName()) +
                        "' is not marked as used by the current entry point '" +
                        entry_point->getFunctionReflection()->getName() +
                        "' while it should be !";
            }
        }
    }

    return {};
}

int main(int argc, char** argv)
{
    if (argc != 2) {
        std::cerr << "expected slang shader path \n";
        return -1;
    }

    //std::filesystem::path file_path = std::filesystem::path(__FILE__).parent_path() / "test_shader.slang";
    std::filesystem::path file_path = argv[1];

    if (const auto error = test_is_parameter_location_used(file_path))
    {
        std::cerr << "ERROR : " << *error << "\n";
        return -1;
    }
    std::cout << "success !!!\n";
}