Skip to content

Commit 88ab45d

Browse files
authored
test that link time extern struct layouts are visible for nested types (#6568)
closes #6556
1 parent fb48856 commit 88ab45d

File tree

1 file changed

+241
-0
lines changed

1 file changed

+241
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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+
slang::EntryPointLayout* entryPointLayout = slangReflection->findEntryPointByName("vertexMain");
123+
124+
SLANG_CHECK_MSG(entryPointLayout != nullptr, "Could not find vertexMain entry point");
125+
126+
// Get the parameter count for the entry point
127+
auto paramCount = entryPointLayout->getParameterCount();
128+
SLANG_CHECK_MSG(paramCount >= 1, "Entry point has no parameters");
129+
130+
// Get the first parameter, which should be of type Outer
131+
auto paramLayout = entryPointLayout->getParameterByIndex(0);
132+
SLANG_CHECK_MSG(paramLayout != nullptr, "Could not get first parameter layout");
133+
134+
// Get the type layout of the parameter
135+
auto outerTypeLayout = paramLayout->getTypeLayout();
136+
SLANG_CHECK_MSG(outerTypeLayout != nullptr, "Parameter has no type layout");
137+
138+
// Check if it's a struct type
139+
auto kind = outerTypeLayout->getKind();
140+
SLANG_CHECK_MSG(kind == slang::TypeReflection::Kind::Struct, "Parameter is not a struct type");
141+
142+
// Verify Outer has 3 fields: position, innerData, texCoord
143+
auto fieldCount = outerTypeLayout->getFieldCount();
144+
SLANG_CHECK_MSG(fieldCount == 3, "Outer struct does not have 3 fields");
145+
146+
// Find and check the innerData field
147+
slang::VariableLayoutReflection* innerDataField = nullptr;
148+
for (unsigned int i = 0; i < fieldCount; i++)
149+
{
150+
auto fieldLayout = outerTypeLayout->getFieldByIndex(i);
151+
const char* fieldName = fieldLayout->getName();
152+
153+
if (fieldName && strcmp(fieldName, "innerData") == 0)
154+
{
155+
innerDataField = fieldLayout;
156+
break;
157+
}
158+
}
159+
160+
SLANG_CHECK_MSG(innerDataField != nullptr, "Could not find innerData field in Outer struct");
161+
162+
// Get the type layout of the innerData field
163+
auto innerTypeLayout = innerDataField->getTypeLayout();
164+
SLANG_CHECK_MSG(innerTypeLayout != nullptr, "innerData field has no type layout");
165+
166+
// Verify Inner is a struct type
167+
kind = innerTypeLayout->getKind();
168+
SLANG_CHECK_MSG(kind == slang::TypeReflection::Kind::Struct, "Inner is not a struct type");
169+
170+
// Verify Inner has 1 field (data)
171+
fieldCount = innerTypeLayout->getFieldCount();
172+
SLANG_CHECK_MSG(fieldCount == 1, "Inner struct does not have 1 field");
173+
174+
// Find and check the data field in Inner
175+
bool foundDataField = false;
176+
for (unsigned int i = 0; i < fieldCount; i++)
177+
{
178+
auto fieldLayout = innerTypeLayout->getFieldByIndex(i);
179+
const char* fieldName = fieldLayout->getName();
180+
181+
if (fieldName && strcmp(fieldName, "data") == 0)
182+
{
183+
foundDataField = true;
184+
185+
// Check that it's a float4 type
186+
auto fieldTypeLayout = fieldLayout->getTypeLayout();
187+
auto fieldTypeKind = fieldTypeLayout->getKind();
188+
189+
SLANG_CHECK_MSG(
190+
fieldTypeKind == slang::TypeReflection::Kind::Vector,
191+
"Field 'data' is not a vector type");
192+
193+
auto elementCount = fieldTypeLayout->getElementCount();
194+
SLANG_CHECK_MSG(elementCount == 4, "Field 'data' is not a 4-element vector");
195+
196+
break;
197+
}
198+
}
199+
200+
SLANG_CHECK_MSG(foundDataField, "Could not find field 'data' in Inner struct");
201+
}
202+
203+
void linkTimeTypeLayoutNestedImpl(gfx::IDevice* device, UnitTestContext* context)
204+
{
205+
Slang::ComPtr<gfx::IShaderProgram> shaderProgram;
206+
slang::ProgramLayout* slangReflection = nullptr;
207+
208+
auto result = loadProgram(device, shaderProgram, slangReflection);
209+
SLANG_CHECK(SLANG_SUCCEEDED(result));
210+
211+
// Validate the nested struct layout
212+
validateNestedExternStructLayout(context, slangReflection);
213+
214+
// Create a graphics pipeline to verify everything works
215+
GraphicsPipelineStateDesc pipelineDesc = {};
216+
pipelineDesc.program = shaderProgram.get();
217+
pipelineDesc.primitiveType = PrimitiveType::Triangle;
218+
219+
ComPtr<gfx::IPipelineState> pipelineState;
220+
auto pipelineResult =
221+
device->createGraphicsPipelineState(pipelineDesc, pipelineState.writeRef());
222+
SLANG_CHECK(SLANG_SUCCEEDED(pipelineResult));
223+
}
224+
225+
//
226+
// This test verifies that type layout information correctly propagates through
227+
// the Slang compilation pipeline when a regular struct contains a field whose type
228+
// is an extern struct defined in another module.
229+
// Specifically, it tests that:
230+
//
231+
// 1. The Outer struct correctly includes the Inner extern struct as a field
232+
// 2. After linking, the Inner struct's layout is properly resolved with its field
233+
// 3. The complete type layout information is available in the reflection data
234+
//
235+
236+
SLANG_UNIT_TEST(linkTimeTypeLayoutNested)
237+
{
238+
runTestImpl(linkTimeTypeLayoutNestedImpl, unitTestContext, Slang::RenderApiFlag::Vulkan);
239+
}
240+
241+
} // namespace gfx_test

0 commit comments

Comments
 (0)