Repository: shader-slang/slang (C++)

Issue:
Stage inputs not marked as used using isParameterLocationUsed
Hi,
I want to detect if a stage input parameter (varying in) is used by a given stage.
I tried to use `slang::IMetadata::isParameterLocationUsed` but it always returns false.

I haven't found any other way of obtaining this information, so this case is maybe not handled yet.

Here is a full example code :

[Slang shader](https://shader-slang.com/slang-playground/?target=SPIRV&code=eJzjKi4pKk0uUQgL9swrKC3hquZSAIK0nPzEEmOFgvxiawTfSKG0zBpZOi-_KDcxB0WoJDEvPTWvBEUsKRMqimyYiUJyfk5-kTVXrTUXV3RxRmJKapGGUllqUUlqhZJmLBdUL0TANzEzTwPqQoVMEFmsCXVpUWpJaVEeVFAP7OBaADG-Q3Y)

```cpp
#include <filesystem>
#include <iostream>
#include <optional>
#include <string>

#include "slang-com-ptr.h"
#include "slang.h"

// recursively iterate into varying parameter struct members to know which one are used
static void try_register_variable(slang::VariableLayoutReflection* variable, slang::IMetadata* metadata)
{
	if (variable->getTypeLayout()->getKind() == slang::TypeReflection::Kind::Struct)
	{
		for (size_t fi = 0; fi < variable->getTypeLayout()->getFieldCount(); ++fi)
			try_register_variable(variable->getTypeLayout()->getFieldByIndex(fi), metadata);
	}
	else
	{
		if (variable->getTypeLayout()->getBindingRangeCount() == 1)
		{
			bool b_used = true;
			metadata->isParameterLocationUsed((SlangParameterCategory)variable->getCategory(),
			                                  variable->getBindingSpace(), variable->getBindingIndex(), b_used);
                       // Will always be false even if "Pos" is used in our example
			std::cout << "name=" << variable->getName() << " : used=" << b_used << "\n";
		}
	}
}

std::optional<std::string> test_which_varying_inputs_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
		for (uint32_t pi = 0; pi < entry_point->getLayout()->getEntryPointByIndex(0)->getParameterCount(); ++pi)
			try_register_variable(entry_point->getLayout()->getEntryPointByIndex(0)->getParameterByIndex(pi), metadata);
	}

	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_which_varying_inputs_used(file_path))
	{
		std::cerr << "ERROR : " << *error << "\n";
		return -1;
	}
	std::cout << "Done\n";
}
```




Texture in the second ParameterBlock is not reported as used in any stage in the new reflection-api demo
I just tested the new reflection-api demo using the following code, https://github.com/shader-slang/slang/pull/5839 
I see the perMaterial.texture is marked as used in Fragment shader but perMaterial2 is not while it's actually the opposite
```
struct Vertex
{
    float2 position;
    float2 uv;
    [format("rgba8")]
    float4 color;
}

struct Complicated
{
    float4 cFloat4;
}

struct PerMaterial
{
    float4 aFloat4;
    float4 bFloat4;
    Complicated cFloat4;
    Sampler2D texture;
}

struct PerMaterial2
{
    float4 aFloat4;
    float4 bFloat4;
    Complicated cFloat4;
    Sampler2D texture;
}

ParameterBlock<PerMaterial> perMaterial;
ParameterBlock<PerMaterial2> perMaterial2;

struct VOut
{
    float4 position : SV_Position;
    float4 color;
    float2 uv;
}

struct PushConstant
{
    float2 scale;
    float2 translate;
}
[vk::push_constant]
PushConstant p;

[shader("vertex")]
VOut VertexMain(Vertex input)
{
    VOut output;
    output.position = float4(input.position * p.scale + p.translate, 0, 1);
    output.color = input.color;

    return output;
}

struct In
{
    float4 color;
    float2 uv;
}

[shader("fragment")]
float4 FragmentMain(VOut input) : SV_Target
{
    let color = perMaterial2.texture.Sample(input.uv * perMaterial.aFloat4.xy * perMaterial.cFloat4.cFloat4.xy);
    return color;
}


```


Discussion: @PierreEVEN is this issue blocking you? Or are you OK if we target this for Q1 2025?
This is a personal project, so no worries about the deadlines 👌
(in the meantime I'll use spirv-reflect)

Yes, this is a known issue. Our `IMetadata` reporting interface is ignoring `descriptor set`/`space` value.



Fix (patch):
diff --git a/examples/reflection-api/README.md b/examples/reflection-api/README.md
index 9dc5f17458..f37ec4b691 100644
--- a/examples/reflection-api/README.md
+++ b/examples/reflection-api/README.md
@@ -4,3 +4,6 @@ Slang Reflection API Example
 This example shows basic usage of the Slang reflection API to
 traverse the parameters of a program, including type and
 layout information.
+
+This example is a companion Slang reflection API documentation:
+https://shader-slang.org/slang/user-guide/compiling.html
diff --git a/examples/reflection-api/main.cpp b/examples/reflection-api/main.cpp
index 9d773fffd9..5c157b7976 100644
--- a/examples/reflection-api/main.cpp
+++ b/examples/reflection-api/main.cpp
@@ -7,9 +7,8 @@
 // This example uses the Slang reflection API to travserse the structure
 // of the parameters of a Slang program and their types.
 //
-// This program is a companion to a document on the Slang reflection API.
-// Once that document is published, this paragraph will be updated to
-// properly link to that document.
+// This program is a companion Slang reflection API documentation:
+// https://shader-slang.org/slang/user-guide/compiling.html
 //
 // Boilerplate
 // -----------
diff --git a/source/compiler-core/slang-artifact-associated-impl.h b/source/compiler-core/slang-artifact-associated-impl.h
index 8a89f865b6..0418b2018c 100644
--- a/source/compiler-core/slang-artifact-associated-impl.h
+++ b/source/compiler-core/slang-artifact-associated-impl.h
@@ -163,6 +163,9 @@ struct ShaderBindingRange
         case slang::UnorderedAccess:
         case slang::SamplerState:
         case slang::DescriptorTableSlot:
+        case slang::VaryingInput:
+        case slang::VaryingOutput:
+        case slang::SpecializationConstant:
             return true;
         default:
             return false;
diff --git a/source/slang/slang-ir-metadata.cpp b/source/slang/slang-ir-metadata.cpp
index 7f0c824713..7348c06fd7 100644
--- a/source/slang/slang-ir-metadata.cpp
+++ b/source/slang/slang-ir-metadata.cpp
@@ -43,6 +43,41 @@ static void _insertBinding(
     ranges.add(newRange);
 }
 
+void collectMetadataFromInst(IRInst* param, ArtifactPostEmitMetadata& outMetadata)
+{
+    auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
+    if (!layoutDecoration)
+        return;
+
+    auto varLayout = as<IRVarLayout>(layoutDecoration->getLayout());
+    if (!varLayout)
+        return;
+
+    for (auto sizeAttr : varLayout->getTypeLayout()->getSizeAttrs())
+    {
+        auto kind = sizeAttr->getResourceKind();
+
+        // Only track resource types that we can reliably track, such as textures.
+        // Do not track individual uniforms, for example.
+        if (!ShaderBindingRange::isUsageTracked(kind))
+            continue;
+
+        if (auto offsetAttr = varLayout->findOffsetAttr(kind))
+        {
+            // Get the binding information from this attribute and insert it into the list
+            auto spaceIndex = offsetAttr->getSpace();
+            if (auto spaceAttr = varLayout->findOffsetAttr(LayoutResourceKind::RegisterSpace))
+            {
+                spaceIndex += spaceAttr->getOffset();
+            }
+            auto registerIndex = offsetAttr->getOffset();
+            auto size = sizeAttr->getSize();
+            auto count = size.isFinite() ? size.getFiniteValue() : 0;
+            _insertBinding(outMetadata.m_usedBindings, kind, spaceIndex, registerIndex, count);
+        }
+    }
+}
+
 // Collects the metadata from the provided IR module, saves it in outMetadata.
 void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMetadata)
 {
@@ -57,39 +92,18 @@ void collectMetadata(const IRModule* irModule, ArtifactPostEmitMetadata& outMeta
                 auto name = func->findDecoration<IRExportDecoration>()->getMangledName();
                 outMetadata.m_exportedFunctionMangledNames.add(name);
             }
+
+            // Collect metadata from entrypoint params.
+            for (auto param : func->getParams())
+            {
+                collectMetadataFromInst(param, outMetadata);
+            }
         }
 
         auto param = as<IRGlobalParam>(inst);
         if (!param)
             continue;
-
-        auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
-        if (!layoutDecoration)
-            continue;
-
-        auto varLayout = as<IRVarLayout>(layoutDecoration->getLayout());
-        if (!varLayout)
-            continue;
-
-        for (auto sizeAttr : varLayout->getTypeLayout()->getSizeAttrs())
-        {
-            auto kind = sizeAttr->getResourceKind();
-
-            // Only track resource types that we can reliably track, such as textures.
-            // Do not track individual uniforms, for example.
-            if (!ShaderBindingRange::isUsageTracked(kind))
-                continue;
-
-            if (auto offsetAttr = varLayout->findOffsetAttr(kind))
-            {
-                // Get the binding information from this attribute and insert it into the list
-                auto spaceIndex = offsetAttr->getSpace();
-                auto registerIndex = offsetAttr->getOffset();
-                auto size = sizeAttr->getSize();
-                auto count = size.isFinite() ? size.getFiniteValue() : 0;
-                _insertBinding(outMetadata.m_usedBindings, kind, spaceIndex, registerIndex, count);
-            }
-        }
+        collectMetadataFromInst(param, outMetadata);
     }
 }