Skip to content

Commit 9aef9aa

Browse files
committed
test that link time extern struct layouts are visible for nested types
closes shader-slang#6556
1 parent 3058a58 commit 9aef9aa

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
#include "core/slang-blob.h"
2+
#include "gfx-test-util.h"
3+
#include "slang-gfx.h"
4+
#include "unit-test/slang-unit-test.h"
5+
6+
using namespace gfx;
7+
8+
namespace gfx_test
9+
{
10+
11+
static void diagnoseIfNeeded(Slang::ComPtr<slang::IBlob>& diagnosticsBlob)
12+
{
13+
if (diagnosticsBlob && diagnosticsBlob->getBufferSize() > 0)
14+
{
15+
fprintf(stderr, "%s\n", (const char*)diagnosticsBlob->getBufferPointer());
16+
}
17+
}
18+
19+
static Slang::Result loadProgram(
20+
gfx::IDevice* device,
21+
Slang::ComPtr<gfx::IShaderProgram>& outShaderProgram,
22+
slang::ProgramLayout*& slangReflection)
23+
{
24+
// main.slang: declares the interface, extern struct Inner, and Outer struct with Inner field
25+
const char* mainSrc = R"(
26+
// Define an interface
27+
public interface IFoo
28+
{
29+
public float4 getFoo();
30+
};
31+
32+
// Define an extern struct that implements the interface
33+
public extern struct Inner : IFoo;
34+
35+
// Define a regular struct that contains an Inner field
36+
public struct Outer
37+
{
38+
float2 position;
39+
Inner innerData;
40+
float2 texCoord;
41+
};
42+
43+
// Vertex shader entry point that takes an Outer parameter
44+
[shader("vertex")]
45+
float4 vertexMain(Outer params) : SV_Position
46+
{
47+
return float4(params.position, 0.0f, 1.0f) + params.innerData.getFoo();
48+
}
49+
)";
50+
51+
// inner.slang: defines Inner with its field layout and its implementation of getFoo()
52+
const char* innerSrc = R"(
53+
import main;
54+
55+
// Define the implementation of Inner with its field layout
56+
export public struct Inner : IFoo
57+
{
58+
public float4 getFoo() { return this.data; }
59+
float4 data;
60+
}
61+
)";
62+
63+
Slang::ComPtr<slang::ISession> slangSession;
64+
SLANG_RETURN_ON_FAIL(device->getSlangSession(slangSession.writeRef()));
65+
Slang::ComPtr<slang::IBlob> diagnosticsBlob;
66+
67+
// Create blobs for the two modules
68+
auto mainBlob = Slang::UnownedRawBlob::create(mainSrc, strlen(mainSrc));
69+
auto innerBlob = Slang::UnownedRawBlob::create(innerSrc, strlen(innerSrc));
70+
71+
// Load modules from source
72+
slang::IModule* mainModule = slangSession->loadModuleFromSource("main", "main.slang", mainBlob);
73+
slang::IModule* innerModule =
74+
slangSession->loadModuleFromSource("inner", "inner.slang", innerBlob);
75+
76+
// Find the entry point from main.slang
77+
Slang::ComPtr<slang::IEntryPoint> vsEntryPoint;
78+
SLANG_RETURN_ON_FAIL(mainModule->findEntryPointByName("vertexMain", vsEntryPoint.writeRef()));
79+
80+
// Compose the program from both modules and the entry point
81+
Slang::List<slang::IComponentType*> componentTypes;
82+
componentTypes.add(mainModule);
83+
componentTypes.add(innerModule);
84+
componentTypes.add(vsEntryPoint);
85+
86+
Slang::ComPtr<slang::IComponentType> composedProgram;
87+
SLANG_RETURN_ON_FAIL(slangSession->createCompositeComponentType(
88+
componentTypes.getBuffer(),
89+
componentTypes.getCount(),
90+
composedProgram.writeRef(),
91+
diagnosticsBlob.writeRef()));
92+
diagnoseIfNeeded(diagnosticsBlob);
93+
94+
// Link the composite program
95+
Slang::ComPtr<slang::IComponentType> linkedProgram;
96+
SLANG_RETURN_ON_FAIL(
97+
composedProgram->link(linkedProgram.writeRef(), diagnosticsBlob.writeRef()));
98+
diagnoseIfNeeded(diagnosticsBlob);
99+
100+
// Retrieve the reflection information
101+
composedProgram = linkedProgram;
102+
slangReflection = composedProgram->getLayout();
103+
104+
// Create a shader program
105+
gfx::IShaderProgram::Desc programDesc = {};
106+
programDesc.slangGlobalScope = composedProgram.get();
107+
auto shaderProgram = device->createProgram(programDesc);
108+
outShaderProgram = shaderProgram;
109+
110+
return SLANG_OK;
111+
}
112+
113+
// Function to validate the type layout of Outer struct with nested Inner struct
114+
static void validateNestedExternStructLayout(
115+
UnitTestContext* context,
116+
slang::ProgramLayout* slangReflection)
117+
{
118+
// Check reflection is available
119+
SLANG_CHECK(slangReflection != nullptr);
120+
121+
// Get the entry point layout for vertexMain
122+
auto entryPointCount = slangReflection->getEntryPointCount();
123+
slang::EntryPointLayout* entryPointLayout = nullptr;
124+
125+
for (unsigned int i = 0; i < entryPointCount; i++)
126+
{
127+
auto currentEntryPoint = slangReflection->getEntryPointByIndex(i);
128+
const char* name = currentEntryPoint->getName();
129+
130+
if (strcmp(name, "vertexMain") == 0)
131+
{
132+
entryPointLayout = currentEntryPoint;
133+
break;
134+
}
135+
}
136+
137+
SLANG_CHECK_MSG(entryPointLayout != nullptr, "Could not find vertexMain entry point");
138+
139+
// Get the parameter count for the entry point
140+
auto paramCount = entryPointLayout->getParameterCount();
141+
SLANG_CHECK_MSG(paramCount >= 1, "Entry point has no parameters");
142+
143+
// Get the first parameter, which should be of type Outer
144+
auto paramLayout = entryPointLayout->getParameterByIndex(0);
145+
SLANG_CHECK_MSG(paramLayout != nullptr, "Could not get first parameter layout");
146+
147+
// Get the type layout of the parameter
148+
auto outerTypeLayout = paramLayout->getTypeLayout();
149+
SLANG_CHECK_MSG(outerTypeLayout != nullptr, "Parameter has no type layout");
150+
151+
// Check if it's a struct type
152+
auto kind = outerTypeLayout->getKind();
153+
SLANG_CHECK_MSG(kind == slang::TypeReflection::Kind::Struct, "Parameter is not a struct type");
154+
155+
// Verify Outer has 3 fields: position, innerData, texCoord
156+
auto fieldCount = outerTypeLayout->getFieldCount();
157+
SLANG_CHECK_MSG(fieldCount == 3, "Outer struct does not have 3 fields");
158+
159+
// Find and check the innerData field
160+
slang::VariableLayoutReflection* innerDataField = nullptr;
161+
for (unsigned int i = 0; i < fieldCount; i++)
162+
{
163+
auto fieldLayout = outerTypeLayout->getFieldByIndex(i);
164+
const char* fieldName = fieldLayout->getName();
165+
166+
if (fieldName && strcmp(fieldName, "innerData") == 0)
167+
{
168+
innerDataField = fieldLayout;
169+
break;
170+
}
171+
}
172+
173+
SLANG_CHECK_MSG(innerDataField != nullptr, "Could not find innerData field in Outer struct");
174+
175+
// Get the type layout of the innerData field
176+
auto innerTypeLayout = innerDataField->getTypeLayout();
177+
SLANG_CHECK_MSG(innerTypeLayout != nullptr, "innerData field has no type layout");
178+
179+
// Verify Inner is a struct type
180+
kind = innerTypeLayout->getKind();
181+
SLANG_CHECK_MSG(kind == slang::TypeReflection::Kind::Struct, "Inner is not a struct type");
182+
183+
// Verify Inner has 1 field (data)
184+
fieldCount = innerTypeLayout->getFieldCount();
185+
SLANG_CHECK_MSG(fieldCount == 1, "Inner struct does not have 1 field");
186+
187+
// Find and check the data field in Inner
188+
bool foundDataField = false;
189+
for (unsigned int i = 0; i < fieldCount; i++)
190+
{
191+
auto fieldLayout = innerTypeLayout->getFieldByIndex(i);
192+
const char* fieldName = fieldLayout->getName();
193+
194+
if (fieldName && strcmp(fieldName, "data") == 0)
195+
{
196+
foundDataField = true;
197+
198+
// Check that it's a float4 type
199+
auto fieldTypeLayout = fieldLayout->getTypeLayout();
200+
auto fieldTypeKind = fieldTypeLayout->getKind();
201+
202+
SLANG_CHECK_MSG(
203+
fieldTypeKind == slang::TypeReflection::Kind::Vector,
204+
"Field 'data' is not a vector type");
205+
206+
auto elementCount = fieldTypeLayout->getElementCount();
207+
SLANG_CHECK_MSG(elementCount == 4, "Field 'data' is not a 4-element vector");
208+
209+
break;
210+
}
211+
}
212+
213+
SLANG_CHECK_MSG(foundDataField, "Could not find field 'data' in Inner struct");
214+
}
215+
216+
void linkTimeTypeLayoutNestedImpl(gfx::IDevice* device, UnitTestContext* context)
217+
{
218+
Slang::ComPtr<gfx::IShaderProgram> shaderProgram;
219+
slang::ProgramLayout* slangReflection = nullptr;
220+
221+
auto result = loadProgram(device, shaderProgram, slangReflection);
222+
SLANG_CHECK(SLANG_SUCCEEDED(result));
223+
224+
// Validate the nested struct layout
225+
validateNestedExternStructLayout(context, slangReflection);
226+
227+
// Create a graphics pipeline to verify everything works
228+
GraphicsPipelineStateDesc pipelineDesc = {};
229+
pipelineDesc.program = shaderProgram.get();
230+
pipelineDesc.primitiveType = PrimitiveType::Triangle;
231+
232+
ComPtr<gfx::IPipelineState> pipelineState;
233+
auto pipelineResult =
234+
device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef());
235+
SLANG_CHECK(SLANG_SUCCEEDED(pipelineResult));
236+
}
237+
238+
//
239+
// This test verifies that type layout information correctly propagates through
240+
// the Slang compilation pipeline when a regular struct contains a field whose type
241+
// is an extern struct defined in another module.
242+
// Specifically, it tests that:
243+
//
244+
// 1. The Outer struct correctly includes the Inner extern struct as a field
245+
// 2. After linking, the Inner struct's layout is properly resolved with its field
246+
// 3. The complete type layout information is available in the reflection data
247+
//
248+
249+
SLANG_UNIT_TEST(linkTimeTypeLayoutNested)
250+
{
251+
runTestImpl(linkTimeTypeLayoutNestedImpl, unitTestContext, Slang::RenderApiFlag::Vulkan);
252+
}
253+
254+
} // namespace gfx_test
255+

0 commit comments

Comments
 (0)