yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongConvert gfx unit tests and examples to use slang-rhi (#7577)43d0c2100

master
8.6 KiB246 linesraw
1#include "core/slang-basic.h"
2#include "core/slang-blob.h"
3#include "gfx-test-util.h"
4#include "unit-test/slang-unit-test.h"
5
6#include <slang-rhi.h>
7#include <slang-rhi/shader-cursor.h>
8
9using namespace rhi;
10
11namespace gfx_test
12{
13static Slang::Result loadProgram(
14    IDevice* device,
15    Slang::ComPtr<IShaderProgram>& outShaderProgram,
16    const char* mainModuleName,
17    const char* libModuleName,
18    const char* entryPointName,
19    slang::ProgramLayout*& slangReflection)
20{
21    Slang::ComPtr<slang::ISession> slangSession;
22    SLANG_RETURN_ON_FAIL(device->getSlangSession(slangSession.writeRef()));
23    Slang::ComPtr<slang::IBlob> diagnosticsBlob;
24
25    // Load main module
26    slang::IModule* mainModule =
27        slangSession->loadModule(mainModuleName, diagnosticsBlob.writeRef());
28    diagnoseIfNeeded(diagnosticsBlob);
29    if (!mainModule)
30        return SLANG_FAIL;
31
32    // Load library module with constants
33    slang::IModule* libModule = slangSession->loadModule(libModuleName, diagnosticsBlob.writeRef());
34    diagnoseIfNeeded(diagnosticsBlob);
35    if (!libModule)
36        return SLANG_FAIL;
37
38    // Find entry point
39    ComPtr<slang::IEntryPoint> computeEntryPoint;
40    SLANG_RETURN_ON_FAIL(
41        mainModule->findEntryPointByName(entryPointName, computeEntryPoint.writeRef()));
42
43    // Compose program from modules
44    Slang::List<slang::IComponentType*> componentTypes;
45    componentTypes.add(mainModule);
46    componentTypes.add(libModule);
47    componentTypes.add(computeEntryPoint);
48
49    Slang::ComPtr<slang::IComponentType> composedProgram;
50    SlangResult result = slangSession->createCompositeComponentType(
51        componentTypes.getBuffer(),
52        componentTypes.getCount(),
53        composedProgram.writeRef(),
54        diagnosticsBlob.writeRef());
55    diagnoseIfNeeded(diagnosticsBlob);
56    SLANG_RETURN_ON_FAIL(result);
57
58    // Link program
59    ComPtr<slang::IComponentType> linkedProgram;
60    result = composedProgram->link(linkedProgram.writeRef(), diagnosticsBlob.writeRef());
61    diagnoseIfNeeded(diagnosticsBlob);
62    SLANG_RETURN_ON_FAIL(result);
63
64    composedProgram = linkedProgram;
65    slangReflection = composedProgram->getLayout();
66
67    // Create shader program
68    ShaderProgramDesc programDesc = {};
69    programDesc.slangGlobalScope = composedProgram.get();
70
71    auto shaderProgram = device->createShaderProgram(programDesc);
72
73    outShaderProgram = shaderProgram;
74    return SLANG_OK;
75}
76
77// Function to validate the array size in struct S
78static void validateArraySizeInStruct(
79    UnitTestContext* context,
80    slang::ProgramLayout* slangReflection,
81    int expectedSize)
82{
83    // Check reflection is available
84    SLANG_CHECK_ABORT(slangReflection != nullptr);
85
86    // Get the global scope layout
87    auto globalScope = slangReflection->getGlobalParamsVarLayout();
88    SLANG_CHECK_ABORT(globalScope != nullptr);
89
90    auto typeLayout = globalScope->getTypeLayout();
91    SLANG_CHECK_ABORT(typeLayout != nullptr);
92
93    // Check if the global scope is a struct type
94    auto kind = typeLayout->getKind();
95    SLANG_CHECK_ABORT(kind == slang::TypeReflection::Kind::Struct);
96
97    // Find the buffer resource 'b'
98    bool foundBuffer = false;
99    auto fieldCount = typeLayout->getFieldCount();
100
101    for (unsigned int i = 0; i < fieldCount; i++)
102    {
103        auto fieldLayout = typeLayout->getFieldByIndex(i);
104        const char* fieldName = fieldLayout->getName();
105
106        if (fieldName && strcmp(fieldName, "b") == 0)
107        {
108            foundBuffer = true;
109
110            // Get the type layout of the field
111            auto fieldTypeLayout = fieldLayout->getTypeLayout();
112            SLANG_CHECK_MSG(fieldTypeLayout != nullptr, "Field has no type layout");
113
114            // Get the element type of the structured buffer
115            auto elementTypeLayout = fieldTypeLayout->getElementTypeLayout();
116            SLANG_CHECK_MSG(
117                elementTypeLayout != nullptr,
118                "Structured buffer has no element type layout");
119            // Check if it's a struct type
120            auto elementKind = elementTypeLayout->getKind();
121            SLANG_CHECK_MSG(
122                elementKind == slang::TypeReflection::Kind::Struct,
123                "Buffer element is not a struct type");
124
125            // Get the field count of the struct
126            auto structFieldCount = elementTypeLayout->getFieldCount();
127            SLANG_CHECK_MSG(structFieldCount >= 1, "Struct has no fields");
128
129            // Check for the 'xs' field
130            bool foundXsField = false;
131            for (unsigned int j = 0; j < structFieldCount; j++)
132            {
133                auto structField = elementTypeLayout->getFieldByIndex(j);
134                const char* structFieldName = structField->getName();
135
136                if (structFieldName && strcmp(structFieldName, "xs") == 0)
137                {
138                    foundXsField = true;
139
140                    // Check that it's an array type
141                    auto structFieldTypeLayout = structField->getTypeLayout();
142                    auto structFieldTypeKind = structFieldTypeLayout->getKind();
143
144                    SLANG_CHECK_MSG(
145                        structFieldTypeKind == slang::TypeReflection::Kind::Array,
146                        "Field 'xs' is not an array type");
147
148                    // Check the array size
149                    auto arraySize = structFieldTypeLayout->getElementCount();
150                    // 0 becuase we haven't resolved the constant
151                    SLANG_CHECK_MSG(
152                        arraySize == 0,
153                        "Field 'xs' array size does not match expected size");
154
155                    // 4 because we're resolving it
156                    const auto resolvedArraySize =
157                        structFieldTypeLayout->getElementCount(slangReflection);
158                    SLANG_CHECK_MSG(
159                        resolvedArraySize == expectedSize,
160                        "Field 'xs' array size does not match expected size");
161
162                    break;
163                }
164            }
165
166            SLANG_CHECK_MSG(foundXsField, "Could not find field 'xs' in struct S");
167            break;
168        }
169    }
170
171    SLANG_CHECK_MSG(foundBuffer, "Could not find buffer 'b' in global scope");
172}
173
174
175void linkTimeConstantArraySizeTestImpl(IDevice* device, UnitTestContext* context)
176{
177    // Load and link program
178    ComPtr<IShaderProgram> shaderProgram;
179    slang::ProgramLayout* slangReflection;
180    GFX_CHECK_CALL_ABORT(loadProgram(
181        device,
182        shaderProgram,
183        "link-time-constant-array-size-main",
184        "link-time-constant-array-size-lib",
185        "computeMain",
186        slangReflection));
187
188    // Check array size through reflection
189    const int N = 4; // This should match the constant in lib.slang
190
191    validateArraySizeInStruct(context, slangReflection, N);
192
193    // Create compute pipeline
194    ComputePipelineDesc pipelineDesc = {};
195    pipelineDesc.program = shaderProgram.get();
196    ComPtr<IComputePipeline> pipelineState;
197    GFX_CHECK_CALL_ABORT(device->createComputePipeline(pipelineDesc, pipelineState.writeRef()));
198
199    // Create buffer for struct S with array of size N
200    int32_t initialData[] = {1, 2, 3, 4};
201    BufferDesc bufferDesc = {};
202    bufferDesc.size = N * sizeof(int32_t);
203    bufferDesc.format = Format::Undefined;
204    bufferDesc.elementSize = sizeof(int32_t);
205    bufferDesc.usage = BufferUsage::ShaderResource | BufferUsage::UnorderedAccess |
206                       BufferUsage::CopyDestination | BufferUsage::CopySource;
207    bufferDesc.defaultState = ResourceState::UnorderedAccess;
208    bufferDesc.memoryType = MemoryType::DeviceLocal;
209
210    ComPtr<IBuffer> numbersBuffer;
211    GFX_CHECK_CALL_ABORT(
212        device->createBuffer(bufferDesc, (void*)initialData, numbersBuffer.writeRef()));
213
214    // Record and execute command buffer
215    {
216        auto queue = device->getQueue(QueueType::Graphics);
217        auto commandEncoder = queue->createCommandEncoder();
218        auto encoder = commandEncoder->beginComputePass();
219
220        auto rootObject = encoder->bindPipeline(pipelineState);
221
222        ShaderCursor rootCursor(rootObject);
223        rootCursor.getPath("b").setBinding(Binding(numbersBuffer));
224
225        encoder->dispatchCompute(1, 1, 1);
226        encoder->end();
227        queue->submit(commandEncoder->finish());
228        queue->waitOnHost();
229    }
230
231    // Expected results: each element is input * N
232    // With N=4 and inputs [1,2,3,4], expected output is [4,8,12,16]
233    compareComputeResult(device, numbersBuffer, std::array{4, 8, 12, 16});
234}
235
236SLANG_UNIT_TEST(linkTimeConstantArraySizeD3D12)
237{
238    runTestImpl(linkTimeConstantArraySizeTestImpl, unitTestContext, DeviceType::D3D12);
239}
240
241SLANG_UNIT_TEST(linkTimeConstantArraySizeVulkan)
242{
243    runTestImpl(linkTimeConstantArraySizeTestImpl, unitTestContext, DeviceType::Vulkan);
244}
245
246} // namespace gfx_test