yum-mirror/slang

Making it easier to work with shaders

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

Gangzheng TongAdd RHI Device Caching and Test Prefix Exclusion (#8448)ba8132345

master
58.3 KiB1703 linesraw
1// render-test-main.cpp
2
3#define _CRT_SECURE_NO_WARNINGS 1
4
5#include "../../source/core/slang-test-tool-util.h"
6#include "../source/core/slang-io.h"
7#include "../source/core/slang-std-writers.h"
8#include "../source/core/slang-string-util.h"
9#include "core/slang-token-reader.h"
10#include "options.h"
11#include "png-serialize-util.h"
12#include "shader-input-layout.h"
13#include "shader-renderer-util.h"
14#include "slang-support.h"
15#include "slang-test-device-cache.h"
16#include "window.h"
17
18#if defined(_WIN32)
19#include <d3d12.h>
20#include <windows.h>
21#pragma comment(lib, "advapi32")
22#endif
23
24#include <slang-rhi.h>
25#include <slang-rhi/acceleration-structure-utils.h>
26#include <slang-rhi/shader-cursor.h>
27#include <stdio.h>
28#include <stdlib.h>
29#define ENABLE_RENDERDOC_INTEGRATION 0
30
31#if ENABLE_RENDERDOC_INTEGRATION
32#include "external/renderdoc_app.h"
33
34#include <windows.h>
35#endif
36
37#if defined(_WIN32)
38// Check if Windows Developer mode is enabled
39// Developer mode is required for D3D12 experimental features
40static bool isWindowsDeveloperModeEnabled()
41{
42    // Helper function to check a specific registry key with detailed logging
43    auto checkRegistryKey = [](HKEY rootKey,
44                               const char* rootName,
45                               const char* path,
46                               const char* valueName,
47                               bool try32BitView = false) -> bool
48    {
49        HKEY key;
50        DWORD accessFlags = KEY_READ | (try32BitView ? KEY_WOW64_32KEY : KEY_WOW64_64KEY);
51        LONG ret = RegOpenKeyExA(rootKey, path, 0, accessFlags, &key);
52
53        if (ret != ERROR_SUCCESS)
54        {
55            return false;
56        }
57
58        DWORD value = 0;
59        DWORD size = sizeof(DWORD);
60        DWORD type = REG_DWORD;
61
62        ret = RegQueryValueExA(key, valueName, nullptr, &type, (LPBYTE)&value, &size);
63
64        RegCloseKey(key);
65
66        if (ret != ERROR_SUCCESS || type != REG_DWORD)
67        {
68            return false;
69        }
70
71        return value == 1;
72    };
73
74    // Method 1: Check multiple registry locations with different views
75    struct RegistryLocation
76    {
77        HKEY rootKey;
78        const char* rootName;
79        const char* path;
80        const char* valueName;
81    };
82
83    RegistryLocation locations[] = {
84        // Windows 10+ main location
85        {HKEY_LOCAL_MACHINE,
86         "HKLM",
87         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
88         "AllowDevelopmentWithoutDevLicense"},
89
90        // Alternative user location
91        {HKEY_CURRENT_USER,
92         "HKCU",
93         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
94         "AllowDevelopmentWithoutDevLicense"},
95
96        // Windows 11 alternative
97        {HKEY_LOCAL_MACHINE,
98         "HKLM",
99         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
100         "AllowAllTrustedApps"},
101
102        // Policy location
103        {HKEY_LOCAL_MACHINE,
104         "HKLM",
105         "SOFTWARE\\Policies\\Microsoft\\Windows\\Appx",
106         "AllowDevelopmentWithoutDevLicense"},
107
108        // Additional alternative locations
109        {HKEY_CURRENT_USER,
110         "HKCU",
111         "SOFTWARE\\Policies\\Microsoft\\Windows\\Appx",
112         "AllowDevelopmentWithoutDevLicense"},
113    };
114
115    for (const auto& location : locations)
116    {
117        // Try 64-bit view first
118        if (checkRegistryKey(
119                location.rootKey,
120                location.rootName,
121                location.path,
122                location.valueName,
123                false))
124        {
125            return true;
126        }
127
128        // Try 32-bit view as fallback
129        if (checkRegistryKey(
130                location.rootKey,
131                location.rootName,
132                location.path,
133                location.valueName,
134                true))
135        {
136            return true;
137        }
138    }
139
140    printf("*** Developer Mode NOT DETECTED ***\n");
141    printf("To enable Developer Mode:\n");
142    printf("1. Open Windows Settings (Windows key + I)\n");
143    printf("2. Go to 'System' -> 'For developers'\n");
144    printf("3. Turn on 'Developer Mode'\n");
145    printf("4. Restart the application\n");
146    printf("==========================================\n");
147
148    return false;
149}
150#endif
151
152namespace renderer_test
153{
154
155using Slang::Result;
156
157int gWindowWidth = 1024;
158int gWindowHeight = 768;
159
160//
161// For the purposes of a small example, we will define the vertex data for a
162// single triangle directly in the source file. It should be easy to extend
163// this example to load data from an external source, if desired.
164//
165
166struct Vertex
167{
168    float position[3];
169    float color[3];
170    float uv[2];
171    float customData0[4];
172    float customData1[4];
173    float customData2[4];
174    float customData3[4];
175};
176
177static const Vertex kVertexData[] = {
178    {{0, 0, 0.5}, {1, 0, 0}, {0, 0}, {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}},
179    {{0, 1, 0.5}, {0, 0, 1}, {1, 0}, {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}},
180    {{1, 0, 0.5}, {0, 1, 0}, {1, 1}, {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}},
181};
182static const int kVertexCount = SLANG_COUNT_OF(kVertexData);
183
184using namespace Slang;
185
186static void _outputProfileTime(uint64_t startTicks, uint64_t endTicks)
187{
188    WriterHelper out = StdWriters::getOut();
189    double time = double(endTicks - startTicks) / Process::getClockFrequency();
190    out.print("profile-time=%g\n", time);
191}
192
193class ProgramVars;
194
195struct ShaderOutputPlan
196{
197    struct Item
198    {
199        ComPtr<IResource> resource;
200        slang::TypeLayoutReflection* typeLayout = nullptr;
201    };
202
203    List<Item> items;
204};
205
206// A context for hodling resources allocated for a test.
207struct TestResourceContext
208{
209    List<ComPtr<IResource>> resources;
210};
211
212class RenderTestApp
213{
214public:
215    Result update();
216
217    // At initialization time, we are going to load and compile our Slang shader
218    // code, and then create the API objects we need for rendering.
219    Result initialize(
220        SlangSession* session,
221        IDevice* device,
222        const Options& options,
223        const ShaderCompilerUtil::Input& input);
224    void finalize();
225
226    Result applyBinding(IShaderObject* rootObject);
227    void setProjectionMatrix(IShaderObject* rootObject);
228    Result writeBindingOutput(const String& fileName);
229
230    Result writeScreen(const String& filename);
231
232protected:
233    /// Called in initialize
234    Result _initializeShaders(
235        SlangSession* session,
236        IDevice* device,
237        Options::ShaderProgramType shaderType,
238        const ShaderCompilerUtil::Input& input);
239    void _initializeRenderPass();
240    void _initializeAccelerationStructure();
241
242    uint64_t m_startTicks;
243
244    // variables for state to be used for rendering...
245    uintptr_t m_constantBufferSize;
246
247    IDevice* m_device;
248    ComPtr<ICommandQueue> m_queue;
249    ComPtr<IInputLayout> m_inputLayout;
250    ComPtr<IBuffer> m_vertexBuffer;
251    ComPtr<IShaderProgram> m_shaderProgram;
252    ComPtr<IPipeline> m_pipeline;
253    ComPtr<IShaderTable> m_shaderTable;
254    ComPtr<ITexture> m_depthBuffer;
255    ComPtr<ITextureView> m_depthBufferView;
256    ComPtr<ITexture> m_colorBuffer;
257    ComPtr<ITextureView> m_colorBufferView;
258
259    ComPtr<IBuffer> m_blasBuffer;
260    ComPtr<IAccelerationStructure> m_bottomLevelAccelerationStructure;
261    ComPtr<IBuffer> m_tlasBuffer;
262    ComPtr<IAccelerationStructure> m_topLevelAccelerationStructure;
263
264    ShaderCompilerUtil::OutputAndLayout m_compilationOutput;
265
266    ShaderInputLayout m_shaderInputLayout; ///< The binding layout
267
268    Options m_options;
269
270    ShaderOutputPlan m_outputPlan;
271    TestResourceContext m_resourceContext;
272};
273
274struct AssignValsFromLayoutContext
275{
276    IDevice* device;
277    slang::IComponentType* slangComponent;
278    ShaderOutputPlan& outputPlan;
279    TestResourceContext& resourceContext;
280    IAccelerationStructure* accelerationStructure;
281
282    AssignValsFromLayoutContext(
283        IDevice* device,
284        slang::IComponentType* slangComponent,
285        ShaderOutputPlan& outputPlan,
286        TestResourceContext& resourceContext,
287        IAccelerationStructure* accelerationStructure)
288        : device(device)
289        , slangComponent(slangComponent)
290        , outputPlan(outputPlan)
291        , resourceContext(resourceContext)
292        , accelerationStructure(accelerationStructure)
293    {
294    }
295
296    slang::ProgramLayout* slangReflection() { return slangComponent->getLayout(); }
297    slang::ISession* slangSession() { return slangComponent->getSession(); }
298
299    void maybeAddOutput(
300        ShaderCursor const& dstCursor,
301        ShaderInputLayout::Val* srcVal,
302        IResource* resource)
303    {
304        if (srcVal->isOutput)
305        {
306            ShaderOutputPlan::Item item;
307            item.resource = resource;
308            item.typeLayout = dstCursor.getTypeLayout();
309            outputPlan.items.add(item);
310        }
311    }
312
313    SlangResult assignData(ShaderCursor const& dstCursor, ShaderInputLayout::DataVal* srcVal)
314    {
315        const size_t bufferSize = srcVal->bufferData.getCount() * sizeof(uint32_t);
316
317        ShaderCursor dataCursor = dstCursor;
318        switch (dataCursor.getTypeLayout()->getKind())
319        {
320        case slang::TypeReflection::Kind::ConstantBuffer:
321        case slang::TypeReflection::Kind::ParameterBlock:
322            dataCursor = dataCursor.getDereferenced();
323            break;
324
325        default:
326            break;
327        }
328
329        SLANG_RETURN_ON_FAIL(dataCursor.setData(srcVal->bufferData.getBuffer(), bufferSize));
330        return SLANG_OK;
331    }
332
333    SlangResult assignBuffer(ShaderCursor const& dstCursor, ShaderInputLayout::BufferVal* srcVal)
334    {
335        const InputBufferDesc& srcBuffer = srcVal->bufferDesc;
336        auto& bufferData = srcVal->bufferData;
337        const size_t bufferSize = Math::Max(
338            (size_t)bufferData.getCount() * sizeof(uint32_t),
339            (size_t)(srcBuffer.elementCount * srcBuffer.stride));
340        bufferData.reserve(bufferSize / sizeof(uint32_t));
341        for (size_t i = bufferData.getCount(); i < bufferSize / sizeof(uint32_t); i++)
342            bufferData.add(0);
343
344        ComPtr<IBuffer> bufferResource;
345
346        SLANG_RETURN_ON_FAIL(ShaderRendererUtil::createBuffer(
347            srcBuffer,
348            /*entry.isOutput,*/ bufferSize,
349            bufferData.getBuffer(),
350            device,
351            bufferResource));
352
353        if ((dstCursor.getTypeLayout()->getType()->getKind() ==
354                 slang::TypeReflection::Kind::Scalar &&
355             dstCursor.getTypeLayout()->getType()->getScalarType() ==
356                 slang::TypeReflection::ScalarType::UInt64) ||
357            dstCursor.getTypeLayout()->getType()->getKind() == slang::TypeReflection::Kind::Pointer)
358        {
359            // dstCursor is pointer to an ordinary uniform data field,
360            // we should write bufferResource as a pointer.
361            uint64_t addr = bufferResource->getDeviceAddress();
362            dstCursor.setData(&addr, sizeof(addr));
363            resourceContext.resources.add(ComPtr<IResource>(bufferResource.get()));
364            maybeAddOutput(dstCursor, srcVal, bufferResource);
365            return SLANG_OK;
366        }
367
368        ComPtr<IBuffer> counterResource;
369        const auto explicitCounterCursor = dstCursor.getExplicitCounter();
370        if (srcBuffer.counter != ~0u)
371        {
372            if (explicitCounterCursor.isValid())
373            {
374                // If this cursor has a full buffer object associated with the
375                // resource, then assign to that.
376                ShaderInputLayout::BufferVal counterVal;
377                counterVal.bufferData.add(srcBuffer.counter);
378                assignBuffer(explicitCounterCursor, &counterVal);
379            }
380            else
381            {
382                // Otherwise, this API (D3D) must be handling the buffer object
383                // specially, in which case create the buffer resource to pass
384                // into `createBufferView`
385                const InputBufferDesc& counterBufferDesc{
386                    InputBufferType::StorageBuffer,
387                    sizeof(uint32_t),
388                    1,
389                    Format::Undefined,
390                };
391                SLANG_RETURN_ON_FAIL(ShaderRendererUtil::createBuffer(
392                    counterBufferDesc,
393                    sizeof(srcBuffer.counter),
394                    &srcBuffer.counter,
395                    device,
396                    counterResource));
397            }
398        }
399        else if (explicitCounterCursor.isValid())
400        {
401            // If we know we require a counter for this resource but haven't
402            // been given one, error
403            return SLANG_E_INVALID_ARG;
404        }
405
406        if (counterResource)
407        {
408            dstCursor.setBinding(Binding(bufferResource, counterResource));
409        }
410        else
411        {
412            dstCursor.setBinding(bufferResource);
413        }
414        maybeAddOutput(dstCursor, srcVal, bufferResource);
415
416        return SLANG_OK;
417    }
418
419    SlangResult assignCombinedTextureSampler(
420        ShaderCursor const& dstCursor,
421        ShaderInputLayout::CombinedTextureSamplerVal* srcVal)
422    {
423        auto& textureEntry = srcVal->textureVal;
424        auto& samplerEntry = srcVal->samplerVal;
425
426        ComPtr<ITexture> texture;
427        SLANG_RETURN_ON_FAIL(ShaderRendererUtil::generateTexture(
428            textureEntry->textureDesc,
429            ResourceState::ShaderResource,
430            device,
431            texture));
432
433        auto sampler = _createSampler(device, samplerEntry->samplerDesc);
434
435        dstCursor.setBinding(Binding(texture, sampler));
436        maybeAddOutput(dstCursor, srcVal, texture);
437
438        return SLANG_OK;
439    }
440
441    SlangResult assignTexture(ShaderCursor const& dstCursor, ShaderInputLayout::TextureVal* srcVal)
442    {
443        ComPtr<ITexture> texture;
444        ResourceState defaultState = srcVal->textureDesc.isRWTexture
445                                         ? ResourceState::UnorderedAccess
446                                         : ResourceState::ShaderResource;
447
448        SLANG_RETURN_ON_FAIL(ShaderRendererUtil::generateTexture(
449            srcVal->textureDesc,
450            defaultState,
451            device,
452            texture));
453
454        dstCursor.setBinding(texture);
455        maybeAddOutput(dstCursor, srcVal, texture);
456        return SLANG_OK;
457    }
458
459    SlangResult assignSampler(ShaderCursor const& dstCursor, ShaderInputLayout::SamplerVal* srcVal)
460    {
461        auto sampler = _createSampler(device, srcVal->samplerDesc);
462
463        dstCursor.setBinding(sampler);
464        return SLANG_OK;
465    }
466
467    SlangResult assignAggregate(ShaderCursor const& dstCursor, ShaderInputLayout::AggVal* srcVal)
468    {
469        Index fieldCount = srcVal->fields.getCount();
470        for (Index fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex)
471        {
472            auto& field = srcVal->fields[fieldIndex];
473
474            if (field.name.getLength() == 0)
475            {
476                // If no name was given, assume by-indexing matching is requested
477                auto fieldCursor = dstCursor.getElement((uint32_t)fieldIndex);
478                if (!fieldCursor.isValid())
479                {
480                    StdWriters::getError().print(
481                        "error: could not find shader parameter at index %d\n",
482                        (int)fieldIndex);
483                    return SLANG_E_INVALID_ARG;
484                }
485                SLANG_RETURN_ON_FAIL(assign(fieldCursor, field.val));
486            }
487            else
488            {
489                auto fieldCursor = dstCursor.getPath(field.name.getBuffer());
490                if (!fieldCursor.isValid())
491                {
492                    StdWriters::getError().print(
493                        "error: could not find shader parameter matching '%s'\n",
494                        field.name.begin());
495                    return SLANG_E_INVALID_ARG;
496                }
497                SLANG_RETURN_ON_FAIL(assign(fieldCursor, field.val));
498            }
499        }
500        return SLANG_OK;
501    }
502
503    SlangResult assignObject(ShaderCursor const& dstCursor, ShaderInputLayout::ObjectVal* srcVal)
504    {
505        auto typeName = srcVal->typeName;
506        slang::TypeReflection* slangType = nullptr;
507        if (typeName.getLength() != 0)
508        {
509            // If the input line specified the name of the type
510            // to allocate, then we use it directly.
511            //
512            slangType = slangReflection()->findTypeByName(typeName.getBuffer());
513        }
514        else
515        {
516            // if the user did not specify what type to allocate,
517            // then we will infer the type from the type of the
518            // value pointed to by `entryCursor`.
519            //
520            auto slangTypeLayout = dstCursor.getTypeLayout();
521            switch (slangTypeLayout->getKind())
522            {
523            default:
524                break;
525
526            case slang::TypeReflection::Kind::ConstantBuffer:
527            case slang::TypeReflection::Kind::ParameterBlock:
528                // If the cursor is pointing at a constant buffer
529                // or parameter block, then we assume the user
530                // actually means to allocate an object based on
531                // the element type of the block.
532                //
533                slangTypeLayout = slangTypeLayout->getElementTypeLayout();
534                break;
535            }
536            slangType = slangTypeLayout->getType();
537        }
538
539        ComPtr<IShaderObject> shaderObject;
540        device->createShaderObject(
541            slangSession(),
542            slangType,
543            ShaderObjectContainerType::None,
544            shaderObject.writeRef());
545
546        SLANG_RETURN_ON_FAIL(assign(ShaderCursor(shaderObject), srcVal->contentVal));
547        shaderObject->finalize();
548        dstCursor.setObject(shaderObject);
549        return SLANG_OK;
550    }
551
552    SlangResult assignValWithSpecializationArg(
553        ShaderCursor const& dstCursor,
554        ShaderInputLayout::SpecializeVal* srcVal)
555    {
556        assign(dstCursor, srcVal->contentVal);
557        List<slang::SpecializationArg> args;
558        for (auto& typeName : srcVal->typeArgs)
559        {
560            auto slangType = slangReflection()->findTypeByName(typeName.getBuffer());
561            if (!slangType)
562            {
563                StdWriters::getError().print(
564                    "error: could not find shader type '%s'\n",
565                    typeName.getBuffer());
566                return SLANG_E_INVALID_ARG;
567            }
568            args.add(slang::SpecializationArg::fromType(slangType));
569        }
570        return dstCursor.setSpecializationArgs(args.getBuffer(), (uint32_t)args.getCount());
571    }
572
573    SlangResult assignArray(ShaderCursor const& dstCursor, ShaderInputLayout::ArrayVal* srcVal)
574    {
575        Index elementCounter = 0;
576        for (auto elementVal : srcVal->vals)
577        {
578            Index elementIndex = elementCounter++;
579            SLANG_RETURN_ON_FAIL(assign(dstCursor[elementIndex], elementVal));
580        }
581        return SLANG_OK;
582    }
583
584    SlangResult assignAccelerationStructure(
585        ShaderCursor const& dstCursor,
586        ShaderInputLayout::AccelerationStructureVal* srcVal)
587    {
588        dstCursor.setBinding(accelerationStructure);
589        return SLANG_OK;
590    }
591
592    SlangResult assign(ShaderCursor const& dstCursor, ShaderInputLayout::ValPtr const& srcVal)
593    {
594        auto& entryCursor = dstCursor;
595        switch (srcVal->kind)
596        {
597        case ShaderInputType::UniformData:
598            return assignData(dstCursor, (ShaderInputLayout::DataVal*)srcVal.Ptr());
599
600        case ShaderInputType::Buffer:
601            return assignBuffer(dstCursor, (ShaderInputLayout::BufferVal*)srcVal.Ptr());
602
603        case ShaderInputType::CombinedTextureSampler:
604            return assignCombinedTextureSampler(
605                dstCursor,
606                (ShaderInputLayout::CombinedTextureSamplerVal*)srcVal.Ptr());
607
608        case ShaderInputType::Texture:
609            return assignTexture(dstCursor, (ShaderInputLayout::TextureVal*)srcVal.Ptr());
610
611        case ShaderInputType::Sampler:
612            return assignSampler(dstCursor, (ShaderInputLayout::SamplerVal*)srcVal.Ptr());
613
614        case ShaderInputType::Object:
615            return assignObject(dstCursor, (ShaderInputLayout::ObjectVal*)srcVal.Ptr());
616
617        case ShaderInputType::Specialize:
618            return assignValWithSpecializationArg(
619                dstCursor,
620                (ShaderInputLayout::SpecializeVal*)srcVal.Ptr());
621
622        case ShaderInputType::Aggregate:
623            return assignAggregate(dstCursor, (ShaderInputLayout::AggVal*)srcVal.Ptr());
624
625        case ShaderInputType::Array:
626            return assignArray(dstCursor, (ShaderInputLayout::ArrayVal*)srcVal.Ptr());
627
628        case ShaderInputType::AccelerationStructure:
629            return assignAccelerationStructure(
630                dstCursor,
631                (ShaderInputLayout::AccelerationStructureVal*)srcVal.Ptr());
632        default:
633            assert(!"Unhandled type");
634            return SLANG_FAIL;
635        }
636    }
637};
638
639static SlangResult _assignVarsFromLayout(
640    IDevice* device,
641    slang::IComponentType* slangComponent,
642    IShaderObject* shaderObject,
643    ShaderInputLayout const& layout,
644    ShaderOutputPlan& ioOutputPlan,
645    TestResourceContext& ioResourceContext,
646    IAccelerationStructure* accelerationStructure)
647{
648    AssignValsFromLayoutContext
649        context(device, slangComponent, ioOutputPlan, ioResourceContext, accelerationStructure);
650    ShaderCursor rootCursor = ShaderCursor(shaderObject);
651    return context.assign(rootCursor, layout.rootVal);
652}
653
654Result RenderTestApp::applyBinding(IShaderObject* rootObject)
655{
656    return _assignVarsFromLayout(
657        m_device,
658        m_compilationOutput.output.slangProgram,
659        rootObject,
660        m_compilationOutput.layout,
661        m_outputPlan,
662        m_resourceContext,
663        m_topLevelAccelerationStructure);
664}
665
666SlangResult RenderTestApp::initialize(
667    SlangSession* session,
668    IDevice* device,
669    const Options& options,
670    const ShaderCompilerUtil::Input& input)
671{
672    m_options = options;
673
674    // We begin by compiling the shader file and entry points that specified via the options.
675    //
676    SLANG_RETURN_ON_FAIL(ShaderCompilerUtil::compileWithLayout(
677        device->getSlangSession()->getGlobalSession(),
678        options,
679        input,
680        m_compilationOutput));
681    m_shaderInputLayout = m_compilationOutput.layout;
682
683    // Once the shaders have been compiled we load them via the underlying API.
684    //
685    ComPtr<ISlangBlob> outDiagnostics;
686    auto result = device->createShaderProgram(
687        m_compilationOutput.output.desc,
688        m_shaderProgram.writeRef(),
689        outDiagnostics.writeRef());
690
691    // If there was a failure creating a program, we can't continue
692    // Special case SLANG_E_NOT_AVAILABLE error code to make it a failure,
693    // as it is also used to indicate an attempt setup something failed gracefully (because it
694    // couldn't be supported) but that's not this.
695    if (SLANG_FAILED(result))
696    {
697        result = (result == SLANG_E_NOT_AVAILABLE) ? SLANG_FAIL : result;
698        return result;
699    }
700
701    m_device = device;
702
703    _initializeRenderPass();
704    _initializeAccelerationStructure();
705
706    {
707        switch (m_options.shaderType)
708        {
709        default:
710            assert(!"unexpected test shader type");
711            return SLANG_FAIL;
712
713        case Options::ShaderProgramType::Compute:
714            {
715                ComputePipelineDesc desc;
716                desc.program = m_shaderProgram;
717
718                m_pipeline = device->createComputePipeline(desc);
719            }
720            break;
721
722        case Options::ShaderProgramType::Graphics:
723        case Options::ShaderProgramType::GraphicsCompute:
724            {
725                // TODO: We should conceivably be able to match up the "available" vertex
726                // attributes, as defined by the vertex stream(s) on the model being
727                // renderer, with the "required" vertex attributes as defiend on the
728                // shader.
729                //
730                // For now we just create a fixed input layout for all graphics tests
731                // since at present they all draw the same single triangle with a
732                // fixed/known set of attributes.
733                //
734                const InputElementDesc inputElements[] = {
735                    {"A", 0, Format::RGB32Float, offsetof(Vertex, position)},
736                    {"A", 1, Format::RGB32Float, offsetof(Vertex, color)},
737                    {"A", 2, Format::RG32Float, offsetof(Vertex, uv)},
738                    {"A", 3, Format::RGBA32Float, offsetof(Vertex, customData0)},
739                    {"A", 4, Format::RGBA32Float, offsetof(Vertex, customData1)},
740                    {"A", 5, Format::RGBA32Float, offsetof(Vertex, customData2)},
741                    {"A", 6, Format::RGBA32Float, offsetof(Vertex, customData3)},
742                };
743
744                ComPtr<IInputLayout> inputLayout;
745                SLANG_RETURN_ON_FAIL(device->createInputLayout(
746                    sizeof(Vertex),
747                    inputElements,
748                    SLANG_COUNT_OF(inputElements),
749                    inputLayout.writeRef()));
750
751                BufferDesc vertexBufferDesc;
752                vertexBufferDesc.size = kVertexCount * sizeof(Vertex);
753                vertexBufferDesc.memoryType = MemoryType::DeviceLocal;
754                vertexBufferDesc.usage = BufferUsage::VertexBuffer;
755                vertexBufferDesc.defaultState = ResourceState::VertexBuffer;
756
757                SLANG_RETURN_ON_FAIL(
758                    device->createBuffer(vertexBufferDesc, kVertexData, m_vertexBuffer.writeRef()));
759
760                ColorTargetDesc colorTarget;
761                colorTarget.format = Format::RGBA8Unorm;
762                RenderPipelineDesc desc;
763                desc.program = m_shaderProgram;
764                desc.inputLayout = inputLayout;
765                desc.targets = &colorTarget;
766                desc.targetCount = 1;
767                desc.depthStencil.format = Format::D32Float;
768                m_pipeline = device->createRenderPipeline(desc);
769            }
770            break;
771
772        case Options::ShaderProgramType::GraphicsMeshCompute:
773        case Options::ShaderProgramType::GraphicsTaskMeshCompute:
774            {
775                ColorTargetDesc colorTarget;
776                colorTarget.format = Format::RGBA8Unorm;
777                RenderPipelineDesc desc;
778                desc.program = m_shaderProgram;
779                desc.targets = &colorTarget;
780                desc.targetCount = 1;
781                desc.depthStencil.format = Format::D32Float;
782                m_pipeline = device->createRenderPipeline(desc);
783            }
784            break;
785
786        case Options::ShaderProgramType::RayTracing:
787            {
788                RayTracingPipelineDesc desc;
789                desc.program = m_shaderProgram;
790
791                m_pipeline = device->createRayTracingPipeline(desc);
792
793                const char* raygenNames[] = {"raygenMain"};
794
795                // We don't define a miss shader for this test. OptiX allows
796                // passing nullptr to indicate no miss shader, but something in
797                // slang-rhi assumes that the miss shader always has a name. To
798                // work around that, use a dummy name.
799                const char* missNames[] = {"missNull"};
800
801                ShaderTableDesc shaderTableDesc = {};
802                shaderTableDesc.program = m_shaderProgram;
803                shaderTableDesc.rayGenShaderCount = 1;
804                shaderTableDesc.rayGenShaderEntryPointNames = raygenNames;
805                shaderTableDesc.missShaderCount = 1;
806                shaderTableDesc.missShaderEntryPointNames = missNames;
807                SLANG_RETURN_ON_FAIL(
808                    device->createShaderTable(shaderTableDesc, m_shaderTable.writeRef()));
809            }
810            break;
811        }
812    }
813    // If success must have a pipeline state
814    return m_pipeline ? SLANG_OK : SLANG_FAIL;
815}
816
817Result RenderTestApp::_initializeShaders(
818    SlangSession* session,
819    IDevice* device,
820    Options::ShaderProgramType shaderType,
821    const ShaderCompilerUtil::Input& input)
822{
823    SLANG_RETURN_ON_FAIL(ShaderCompilerUtil::compileWithLayout(
824        device->getSlangSession()->getGlobalSession(),
825        m_options,
826        input,
827        m_compilationOutput));
828    m_shaderInputLayout = m_compilationOutput.layout;
829    m_shaderProgram = device->createShaderProgram(m_compilationOutput.output.desc);
830    return m_shaderProgram ? SLANG_OK : SLANG_FAIL;
831}
832
833void RenderTestApp::_initializeRenderPass()
834{
835    m_queue = m_device->getQueue(QueueType::Graphics);
836    SLANG_ASSERT(m_queue);
837
838    rhi::TextureDesc depthBufferDesc;
839    depthBufferDesc.type = TextureType::Texture2D;
840    depthBufferDesc.size.width = gWindowWidth;
841    depthBufferDesc.size.height = gWindowHeight;
842    depthBufferDesc.size.depth = 1;
843    depthBufferDesc.mipCount = 1;
844    depthBufferDesc.format = Format::D32Float;
845    depthBufferDesc.usage = TextureUsage::DepthStencil;
846    depthBufferDesc.defaultState = ResourceState::DepthWrite;
847    m_depthBuffer = m_device->createTexture(depthBufferDesc, nullptr);
848    SLANG_ASSERT(m_depthBuffer);
849    m_depthBufferView = m_device->createTextureView(m_depthBuffer, {});
850    SLANG_ASSERT(m_depthBufferView);
851
852    rhi::TextureDesc colorBufferDesc;
853    colorBufferDesc.type = TextureType::Texture2D;
854    colorBufferDesc.size.width = gWindowWidth;
855    colorBufferDesc.size.height = gWindowHeight;
856    colorBufferDesc.size.depth = 1;
857    colorBufferDesc.mipCount = 1;
858    colorBufferDesc.format = Format::RGBA8Unorm;
859    colorBufferDesc.usage = TextureUsage::RenderTarget | TextureUsage::CopySource;
860    colorBufferDesc.defaultState = ResourceState::RenderTarget;
861    m_colorBuffer = m_device->createTexture(colorBufferDesc, nullptr);
862    SLANG_ASSERT(m_colorBuffer);
863    m_colorBufferView = m_device->createTextureView(m_colorBuffer, {});
864    SLANG_ASSERT(m_colorBufferView);
865}
866
867void RenderTestApp::_initializeAccelerationStructure()
868{
869    if (!m_device->hasFeature("ray-tracing"))
870        return;
871    BufferDesc vertexBufferDesc = {};
872    vertexBufferDesc.size = kVertexCount * sizeof(Vertex);
873    vertexBufferDesc.usage = BufferUsage::AccelerationStructureBuildInput;
874    vertexBufferDesc.defaultState = ResourceState::AccelerationStructureBuildInput;
875    ComPtr<IBuffer> vertexBuffer = m_device->createBuffer(vertexBufferDesc, &kVertexData[0]);
876
877    BufferDesc transformBufferDesc = {};
878    transformBufferDesc.size = sizeof(float) * 12;
879    transformBufferDesc.usage = BufferUsage::AccelerationStructureBuildInput;
880    transformBufferDesc.defaultState = ResourceState::AccelerationStructureBuildInput;
881    float transformData[12] =
882        {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f};
883    ComPtr<IBuffer> transformBuffer = m_device->createBuffer(transformBufferDesc, &transformData);
884
885    // Build bottom level acceleration structure.
886    {
887        AccelerationStructureBuildInput buildInput = {};
888        buildInput.type = AccelerationStructureBuildInputType::Triangles;
889        buildInput.triangles.vertexBuffers[0] = vertexBuffer;
890        buildInput.triangles.vertexBufferCount = 1;
891        buildInput.triangles.vertexFormat = Format::RGB32Float;
892        buildInput.triangles.vertexCount = kVertexCount;
893        buildInput.triangles.vertexStride = sizeof(Vertex);
894        buildInput.triangles.preTransformBuffer = transformBuffer;
895        buildInput.triangles.flags = AccelerationStructureGeometryFlags::Opaque;
896        AccelerationStructureBuildDesc buildDesc = {};
897        buildDesc.inputs = &buildInput;
898        buildDesc.inputCount = 1;
899        buildDesc.flags = AccelerationStructureBuildFlags::AllowCompaction;
900
901        // Query buffer size for acceleration structure build.
902        AccelerationStructureSizes accelerationStructureSizes = {};
903        m_device->getAccelerationStructureSizes(buildDesc, &accelerationStructureSizes);
904
905        BufferDesc scratchBufferDesc = {};
906        scratchBufferDesc.usage = BufferUsage::UnorderedAccess;
907        scratchBufferDesc.defaultState = ResourceState::UnorderedAccess;
908        scratchBufferDesc.size = accelerationStructureSizes.scratchSize;
909        ComPtr<IBuffer> scratchBuffer = m_device->createBuffer(scratchBufferDesc);
910
911        ComPtr<IQueryPool> compactedSizeQuery;
912        QueryPoolDesc queryPoolDesc = {};
913        queryPoolDesc.count = 1;
914        queryPoolDesc.type = QueryType::AccelerationStructureCompactedSize;
915        m_device->createQueryPool(queryPoolDesc, compactedSizeQuery.writeRef());
916
917        // Build acceleration structure.
918        ComPtr<IAccelerationStructure> draftAS;
919        AccelerationStructureDesc draftDesc = {};
920        draftDesc.size = accelerationStructureSizes.accelerationStructureSize;
921        m_device->createAccelerationStructure(draftDesc, draftAS.writeRef());
922
923        compactedSizeQuery->reset();
924
925        auto encoder = m_queue->createCommandEncoder();
926        AccelerationStructureQueryDesc compactedSizeQueryDesc = {};
927        compactedSizeQueryDesc.queryPool = compactedSizeQuery;
928        compactedSizeQueryDesc.queryType = QueryType::AccelerationStructureCompactedSize;
929        encoder->buildAccelerationStructure(
930            buildDesc,
931            draftAS,
932            nullptr,
933            scratchBuffer,
934            1,
935            &compactedSizeQueryDesc);
936        m_queue->submit(encoder->finish());
937        m_queue->waitOnHost();
938
939        uint64_t compactedSize = 0;
940        compactedSizeQuery->getResult(0, 1, &compactedSize);
941        AccelerationStructureDesc finalDesc;
942        finalDesc.size = compactedSize;
943        m_device->createAccelerationStructure(
944            finalDesc,
945            m_bottomLevelAccelerationStructure.writeRef());
946
947        encoder = m_queue->createCommandEncoder();
948        encoder->copyAccelerationStructure(
949            m_bottomLevelAccelerationStructure,
950            draftAS,
951            AccelerationStructureCopyMode::Compact);
952        m_queue->submit(encoder->finish());
953        m_queue->waitOnHost();
954    }
955
956    // Build top level acceleration structure.
957    {
958        AccelerationStructureInstanceDescType nativeInstanceDescType =
959            getAccelerationStructureInstanceDescType(m_device);
960        rhi::Size nativeInstanceDescSize =
961            getAccelerationStructureInstanceDescSize(nativeInstanceDescType);
962
963        List<AccelerationStructureInstanceDescGeneric> genericInstanceDescs;
964        genericInstanceDescs.setCount(1);
965        genericInstanceDescs[0].accelerationStructure =
966            m_bottomLevelAccelerationStructure->getHandle();
967        genericInstanceDescs[0].flags =
968            AccelerationStructureInstanceFlags::TriangleFacingCullDisable;
969        genericInstanceDescs[0].instanceContributionToHitGroupIndex = 0;
970        genericInstanceDescs[0].instanceID = 0;
971        genericInstanceDescs[0].instanceMask = 0xFF;
972        float transformMatrix[] =
973            {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f};
974        memcpy(&genericInstanceDescs[0].transform[0][0], transformMatrix, sizeof(float) * 12);
975
976        List<unsigned char> nativeInstanceDescs;
977        nativeInstanceDescs.setCount(genericInstanceDescs.getCount() * nativeInstanceDescSize);
978        convertAccelerationStructureInstanceDescs(
979            genericInstanceDescs.getCount(),
980            nativeInstanceDescType,
981            nativeInstanceDescs.getBuffer(),
982            nativeInstanceDescSize,
983            genericInstanceDescs.getBuffer(),
984            sizeof(AccelerationStructureInstanceDescGeneric));
985
986        BufferDesc instanceBufferDesc = {};
987        instanceBufferDesc.size = nativeInstanceDescs.getCount();
988        instanceBufferDesc.usage = BufferUsage::AccelerationStructureBuildInput;
989        instanceBufferDesc.defaultState = ResourceState::AccelerationStructureBuildInput;
990        ComPtr<IBuffer> instanceBuffer =
991            m_device->createBuffer(instanceBufferDesc, nativeInstanceDescs.getBuffer());
992
993        AccelerationStructureBuildInput buildInput = {};
994        buildInput.type = AccelerationStructureBuildInputType::Instances;
995        buildInput.instances.instanceBuffer = instanceBuffer;
996        buildInput.instances.instanceCount = 1;
997        buildInput.instances.instanceStride = nativeInstanceDescSize;
998        AccelerationStructureBuildDesc buildDesc = {};
999        buildDesc.inputs = &buildInput;
1000        buildDesc.inputCount = 1;
1001
1002        // Query buffer size for acceleration structure build.
1003        AccelerationStructureSizes accelerationStructureSizes = {};
1004        m_device->getAccelerationStructureSizes(buildDesc, &accelerationStructureSizes);
1005
1006        BufferDesc scratchBufferDesc = {};
1007        scratchBufferDesc.usage = BufferUsage::UnorderedAccess;
1008        scratchBufferDesc.defaultState = ResourceState::UnorderedAccess;
1009        scratchBufferDesc.size = (size_t)accelerationStructureSizes.scratchSize;
1010        ComPtr<IBuffer> scratchBuffer = m_device->createBuffer(scratchBufferDesc);
1011
1012        AccelerationStructureDesc createDesc = {};
1013        createDesc.size = accelerationStructureSizes.accelerationStructureSize;
1014        m_device->createAccelerationStructure(
1015            createDesc,
1016            m_topLevelAccelerationStructure.writeRef());
1017
1018        auto encoder = m_queue->createCommandEncoder();
1019        encoder->buildAccelerationStructure(
1020            buildDesc,
1021            m_topLevelAccelerationStructure,
1022            nullptr,
1023            scratchBuffer,
1024            0,
1025            nullptr);
1026        m_queue->submit(encoder->finish());
1027        m_queue->waitOnHost();
1028    }
1029}
1030
1031void RenderTestApp::setProjectionMatrix(IShaderObject* rootObject)
1032{
1033    float kIdentity[16] =
1034        {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f};
1035    auto info = m_device->getInfo();
1036    ShaderCursor(rootObject)
1037        .getField("Uniforms")
1038        .getDereferenced()
1039        .setData(kIdentity, sizeof(kIdentity));
1040}
1041
1042void RenderTestApp::finalize()
1043{
1044    m_compilationOutput.output.reset();
1045}
1046
1047Result RenderTestApp::writeBindingOutput(const String& fileName)
1048{
1049    // Wait until everything is complete
1050    m_queue->waitOnHost();
1051
1052    FILE* f = fopen(fileName.getBuffer(), "wb");
1053    if (!f)
1054    {
1055        return SLANG_FAIL;
1056    }
1057    FileWriter writer(f, WriterFlags(0));
1058
1059    for (auto outputItem : m_outputPlan.items)
1060    {
1061        auto resource = outputItem.resource;
1062        IBuffer* buffer = nullptr;
1063        resource->queryInterface(IBuffer::getTypeGuid(), (void**)&buffer);
1064        if (buffer)
1065        {
1066            const BufferDesc& bufferDesc = buffer->getDesc();
1067            const size_t bufferSize = bufferDesc.size;
1068
1069            ComPtr<ISlangBlob> blob;
1070            m_device->readBuffer(buffer, 0, bufferSize, blob.writeRef());
1071            buffer->release();
1072
1073            if (!blob)
1074            {
1075                return SLANG_FAIL;
1076            }
1077            const SlangResult res = ShaderInputLayout::writeBinding(
1078                m_options.outputUsingType ? outputItem.typeLayout
1079                                          : nullptr, // TODO: always output using type
1080                blob->getBufferPointer(),
1081                bufferSize,
1082                &writer);
1083            SLANG_RETURN_ON_FAIL(res);
1084        }
1085        else
1086        {
1087            auto typeName = outputItem.typeLayout->getName();
1088            printf("invalid output type '%s'.\n", typeName ? typeName : "UNKNOWN");
1089        }
1090    }
1091    return SLANG_OK;
1092}
1093
1094Result RenderTestApp::writeScreen(const String& filename)
1095{
1096    rhi::SubresourceLayout layout;
1097    ComPtr<ISlangBlob> blob;
1098    SLANG_RETURN_ON_FAIL(m_device->readTexture(m_colorBuffer, 0, 0, blob.writeRef(), &layout));
1099    return PngSerializeUtil::write(
1100        filename.getBuffer(),
1101        blob,
1102        layout.size.width,
1103        layout.size.height,
1104        layout.rowPitch);
1105}
1106
1107Result RenderTestApp::update()
1108{
1109    auto encoder = m_queue->createCommandEncoder();
1110    if (m_options.shaderType == Options::ShaderProgramType::Compute)
1111    {
1112        auto passEncoder = encoder->beginComputePass();
1113        auto rootObject =
1114            passEncoder->bindPipeline(static_cast<IComputePipeline*>(m_pipeline.get()));
1115        applyBinding(rootObject);
1116        passEncoder->dispatchCompute(
1117            m_options.computeDispatchSize[0],
1118            m_options.computeDispatchSize[1],
1119            m_options.computeDispatchSize[2]);
1120        passEncoder->end();
1121    }
1122    else if (m_options.shaderType == Options::ShaderProgramType::RayTracing)
1123    {
1124        auto passEncoder = encoder->beginRayTracingPass();
1125        auto rootObject = passEncoder->bindPipeline(
1126            static_cast<IRayTracingPipeline*>(m_pipeline.get()),
1127            m_shaderTable);
1128        applyBinding(rootObject);
1129        passEncoder->dispatchRays(
1130            0,
1131            m_options.computeDispatchSize[0],
1132            m_options.computeDispatchSize[1],
1133            m_options.computeDispatchSize[2]);
1134        passEncoder->end();
1135    }
1136    else
1137    {
1138        RenderPassColorAttachment colorAttachment = {};
1139        colorAttachment.view = m_colorBufferView;
1140        colorAttachment.loadOp = LoadOp::Clear;
1141        colorAttachment.storeOp = StoreOp::Store;
1142        RenderPassDepthStencilAttachment depthStencilAttachment = {};
1143        depthStencilAttachment.view = m_depthBufferView;
1144        depthStencilAttachment.depthLoadOp = LoadOp::Clear;
1145        depthStencilAttachment.depthStoreOp = StoreOp::Store;
1146        RenderPassDesc renderPass = {};
1147        renderPass.colorAttachments = &colorAttachment;
1148        renderPass.colorAttachmentCount = 1;
1149        renderPass.depthStencilAttachment = &depthStencilAttachment;
1150
1151        auto passEncoder = encoder->beginRenderPass(renderPass);
1152        auto rootObject =
1153            passEncoder->bindPipeline(static_cast<IRenderPipeline*>(m_pipeline.get()));
1154        applyBinding(rootObject);
1155        setProjectionMatrix(rootObject);
1156
1157        RenderState state;
1158        state.viewports[0] = Viewport::fromSize(gWindowWidth, gWindowHeight);
1159        state.viewportCount = 1;
1160        state.scissorRects[0] = ScissorRect::fromSize(gWindowWidth, gWindowHeight);
1161        state.scissorRectCount = 1;
1162
1163        if (m_options.shaderType == Options::ShaderProgramType::GraphicsMeshCompute ||
1164            m_options.shaderType == Options::ShaderProgramType::GraphicsTaskMeshCompute)
1165        {
1166            passEncoder->setRenderState(state);
1167            passEncoder->drawMeshTasks(
1168                m_options.computeDispatchSize[0],
1169                m_options.computeDispatchSize[1],
1170                m_options.computeDispatchSize[2]);
1171        }
1172        else
1173        {
1174            state.vertexBuffers[0] = m_vertexBuffer;
1175            state.vertexBufferCount = 1;
1176            passEncoder->setRenderState(state);
1177            DrawArguments args;
1178            args.vertexCount = 3;
1179            passEncoder->draw(args);
1180        }
1181        passEncoder->end();
1182    }
1183    m_startTicks = Process::getClockTick();
1184    m_queue->submit(encoder->finish());
1185    m_queue->waitOnHost();
1186
1187    // If we are in a mode where output is requested, we need to snapshot the back buffer here
1188    if (m_options.outputPath.getLength() || m_options.performanceProfile)
1189    {
1190        // Wait until everything is complete
1191
1192        if (m_options.performanceProfile)
1193        {
1194#if 0
1195            // It might not be enough on some APIs to 'waitForGpu' to mean the computation has completed. Let's lock an output
1196            // buffer to be sure
1197            if (m_bindingState->outputBindings.getCount() > 0)
1198            {
1199                const auto& binding = m_bindingState->outputBindings[0];
1200                auto i = binding.entryIndex;
1201                const auto& layoutBinding = m_shaderInputLayout.entries[i];
1202
1203                assert(layoutBinding.isOutput);
1204                
1205                if (binding.resource && binding.resource->isBuffer())
1206                {
1207                    BufferResource* bufferResource = static_cast<BufferResource*>(binding.resource.Ptr());
1208                    const size_t bufferSize = bufferResource->getDesc().size;
1209                    unsigned int* ptr = (unsigned int*)m_renderer->map(bufferResource, MapFlavor::HostRead);
1210                    if (!ptr)
1211                    {                            
1212                        return SLANG_FAIL;
1213                    }
1214                    m_renderer->unmap(bufferResource);
1215                }
1216            }
1217#endif
1218
1219            // Note we don't do the same with screen rendering -> as that will do a lot of work,
1220            // which may swamp any computation so can only really profile compute shaders at the
1221            // moment
1222
1223            const uint64_t endTicks = Process::getClockTick();
1224
1225            _outputProfileTime(m_startTicks, endTicks);
1226        }
1227
1228        if (m_options.outputPath.getLength())
1229        {
1230            if (m_options.shaderType == Options::ShaderProgramType::Compute ||
1231                m_options.shaderType == Options::ShaderProgramType::GraphicsCompute ||
1232                m_options.shaderType == Options::ShaderProgramType::GraphicsMeshCompute ||
1233                m_options.shaderType == Options::ShaderProgramType::GraphicsTaskMeshCompute ||
1234                m_options.shaderType == Options::ShaderProgramType::RayTracing)
1235            {
1236                SLANG_RETURN_ON_FAIL(writeBindingOutput(m_options.outputPath));
1237            }
1238            else
1239            {
1240                SlangResult res = writeScreen(m_options.outputPath);
1241                if (SLANG_FAILED(res))
1242                {
1243                    fprintf(stderr, "ERROR: failed to write screen capture to file\n");
1244                    return res;
1245                }
1246            }
1247        }
1248        return SLANG_OK;
1249    }
1250    return SLANG_OK;
1251}
1252
1253
1254static SlangResult _setSessionPrelude(
1255    const Options& options,
1256    const char* exePath,
1257    SlangSession* session)
1258{
1259    // Let's see if we need to set up special prelude for HLSL
1260    if (options.nvapiExtnSlot.getLength())
1261    {
1262#if !SLANG_WINDOWS_FAMILY
1263        // NVAPI is currently only available on Windows
1264        return SLANG_E_NOT_AVAILABLE;
1265#else
1266        // We want to set the path to NVAPI
1267        String rootPath;
1268        SLANG_RETURN_ON_FAIL(TestToolUtil::getRootPath(exePath, rootPath));
1269        String includePath;
1270        SLANG_RETURN_ON_FAIL(
1271            TestToolUtil::getIncludePath(rootPath, "external/nvapi/nvHLSLExtns.h", includePath))
1272
1273        StringBuilder buf;
1274        // We have to choose a slot that NVAPI will use.
1275        buf << "#define NV_SHADER_EXTN_SLOT " << options.nvapiExtnSlot << "\n";
1276
1277        // Include the NVAPI header
1278        buf << "#include ";
1279        StringEscapeUtil::appendQuoted(
1280            StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp),
1281            includePath.getUnownedSlice(),
1282            buf);
1283        buf << "\n\n";
1284
1285        session->setLanguagePrelude(SLANG_SOURCE_LANGUAGE_HLSL, buf.getBuffer());
1286#endif
1287    }
1288    else
1289    {
1290        session->setLanguagePrelude(SLANG_SOURCE_LANGUAGE_HLSL, "");
1291    }
1292
1293    return SLANG_OK;
1294}
1295
1296} //  namespace renderer_test
1297
1298#if ENABLE_RENDERDOC_INTEGRATION
1299static RENDERDOC_API_1_1_2* rdoc_api = NULL;
1300static void initializeRenderDoc()
1301{
1302    if (HMODULE mod = GetModuleHandleA("renderdoc.dll"))
1303    {
1304        pRENDERDOC_GetAPI RENDERDOC_GetAPI =
1305            (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI");
1306        int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&rdoc_api);
1307        assert(ret == 1);
1308    }
1309}
1310static void renderDocBeginFrame()
1311{
1312    if (rdoc_api)
1313        rdoc_api->StartFrameCapture(nullptr, nullptr);
1314}
1315static void renderDocEndFrame()
1316{
1317    if (rdoc_api)
1318        rdoc_api->EndFrameCapture(nullptr, nullptr);
1319    _fgetchar();
1320}
1321#else
1322static void initializeRenderDoc() {}
1323static void renderDocBeginFrame() {}
1324static void renderDocEndFrame() {}
1325#endif
1326
1327static SlangResult _innerMain(
1328    Slang::StdWriters* stdWriters,
1329    SlangSession* session,
1330    int argcIn,
1331    const char* const* argvIn)
1332{
1333    using namespace renderer_test;
1334    using namespace Slang;
1335
1336    initializeRenderDoc();
1337
1338    StdWriters::setSingleton(stdWriters);
1339
1340    Options options;
1341
1342    // Parse command-line options
1343    SLANG_RETURN_ON_FAIL(Options::parse(argcIn, argvIn, StdWriters::getError(), options));
1344    if (options.deviceType == DeviceType::Default)
1345    {
1346        return SLANG_OK;
1347    }
1348
1349    ShaderCompilerUtil::Input input;
1350
1351    input.profile = "";
1352    input.target = SLANG_TARGET_NONE;
1353
1354    SlangSourceLanguage nativeLanguage = SLANG_SOURCE_LANGUAGE_UNKNOWN;
1355    SlangPassThrough slangPassThrough = SLANG_PASS_THROUGH_NONE;
1356    char const* profileName = "";
1357    switch (options.deviceType)
1358    {
1359    case DeviceType::D3D11:
1360        input.target = SLANG_DXBC;
1361        input.profile = "sm_5_0";
1362        nativeLanguage = SLANG_SOURCE_LANGUAGE_HLSL;
1363        slangPassThrough = SLANG_PASS_THROUGH_FXC;
1364
1365        break;
1366
1367    case DeviceType::D3D12:
1368        input.target = SLANG_DXIL;
1369        input.profile = "sm_6_5";
1370        nativeLanguage = SLANG_SOURCE_LANGUAGE_HLSL;
1371        slangPassThrough = SLANG_PASS_THROUGH_DXC;
1372
1373        if (options.useDXBC)
1374        {
1375            input.target = SLANG_DXBC;
1376            input.profile = "sm_5_0";
1377            slangPassThrough = SLANG_PASS_THROUGH_FXC;
1378        }
1379        break;
1380
1381    case DeviceType::Vulkan:
1382        input.target = SLANG_SPIRV;
1383        input.profile = "";
1384        nativeLanguage = SLANG_SOURCE_LANGUAGE_GLSL;
1385        slangPassThrough = SLANG_PASS_THROUGH_GLSLANG;
1386        break;
1387    case DeviceType::Metal:
1388        input.target = SLANG_METAL_LIB;
1389        input.profile = "";
1390        nativeLanguage = SLANG_SOURCE_LANGUAGE_METAL;
1391        slangPassThrough = SLANG_PASS_THROUGH_METAL;
1392        break;
1393    case DeviceType::CPU:
1394        input.target = SLANG_SHADER_HOST_CALLABLE;
1395        input.profile = "";
1396        nativeLanguage = SLANG_SOURCE_LANGUAGE_CPP;
1397        slangPassThrough = SLANG_PASS_THROUGH_GENERIC_C_CPP;
1398        break;
1399    case DeviceType::CUDA:
1400        input.target = SLANG_PTX;
1401        input.profile = "";
1402        nativeLanguage = SLANG_SOURCE_LANGUAGE_CUDA;
1403        slangPassThrough = SLANG_PASS_THROUGH_NVRTC;
1404        break;
1405    case DeviceType::WGPU:
1406        input.target = SLANG_WGSL;
1407        input.profile = "";
1408        nativeLanguage = SLANG_SOURCE_LANGUAGE_WGSL;
1409        slangPassThrough = SLANG_PASS_THROUGH_NONE;
1410        break;
1411
1412    default:
1413        fprintf(stderr, "error: unexpected\n");
1414        return SLANG_FAIL;
1415    }
1416
1417    switch (options.inputLanguageID)
1418    {
1419    case Options::InputLanguageID::Slang:
1420        input.sourceLanguage = SLANG_SOURCE_LANGUAGE_SLANG;
1421        input.passThrough = SLANG_PASS_THROUGH_NONE;
1422        break;
1423
1424    case Options::InputLanguageID::Native:
1425        input.sourceLanguage = nativeLanguage;
1426        input.passThrough = slangPassThrough;
1427        break;
1428
1429    default:
1430        break;
1431    }
1432
1433    if (options.sourceLanguage != SLANG_SOURCE_LANGUAGE_UNKNOWN)
1434    {
1435        input.sourceLanguage = options.sourceLanguage;
1436
1437        if (input.sourceLanguage == SLANG_SOURCE_LANGUAGE_C ||
1438            input.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP)
1439        {
1440            input.passThrough = SLANG_PASS_THROUGH_GENERIC_C_CPP;
1441        }
1442    }
1443
1444    static renderer_test::CoreToRHIDebugBridge debugCallback;
1445    debugCallback.setCoreCallback(stdWriters->getDebugCallback());
1446
1447    // Use the profile name set on options if set
1448    input.profile = options.profileName.getLength() ? options.profileName : input.profile;
1449
1450    StringBuilder rendererName;
1451    auto info = rendererName << "[" << getRHI()->getDeviceTypeName(options.deviceType) << "] ";
1452
1453    if (options.onlyStartup)
1454    {
1455        switch (options.deviceType)
1456        {
1457        case DeviceType::CUDA:
1458            {
1459#if RENDER_TEST_CUDA
1460                if (SLANG_FAILED(
1461                        spSessionCheckPassThroughSupport(session, SLANG_PASS_THROUGH_NVRTC)))
1462                    return SLANG_FAIL;
1463#else
1464                return SLANG_FAIL;
1465#endif
1466            }
1467        case DeviceType::CPU:
1468            {
1469                // As long as we have CPU, then this should work
1470                return spSessionCheckPassThroughSupport(session, SLANG_PASS_THROUGH_GENERIC_C_CPP);
1471            }
1472        default:
1473            break;
1474        }
1475    }
1476
1477    Index nvapiExtnSlot = -1;
1478
1479    // Let's see if we need to set up special prelude for HLSL
1480    if (options.nvapiExtnSlot.getLength() && options.nvapiExtnSlot[0] == 'u')
1481    {
1482        //
1483        Slang::Int value;
1484        UnownedStringSlice slice = options.nvapiExtnSlot.getUnownedSlice();
1485        UnownedStringSlice indexText(slice.begin() + 1, slice.end());
1486        if (SLANG_SUCCEEDED(StringUtil::parseInt(indexText, value)))
1487        {
1488            nvapiExtnSlot = Index(value);
1489        }
1490    }
1491
1492    // If can't set up a necessary prelude make not available (which will lead to the test being
1493    // ignored)
1494    if (SLANG_FAILED(_setSessionPrelude(options, argvIn[0], session)))
1495    {
1496        return SLANG_E_NOT_AVAILABLE;
1497    }
1498
1499    CachedDeviceWrapper deviceWrapper;
1500    {
1501        DeviceDesc desc = {};
1502        desc.deviceType = options.deviceType;
1503
1504        desc.enableValidation = options.enableDebugLayers;
1505        desc.debugCallback = &debugCallback;
1506
1507        desc.slang.lineDirectiveMode = SLANG_LINE_DIRECTIVE_MODE_NONE;
1508        if (options.generateSPIRVDirectly)
1509            desc.slang.targetFlags = SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY;
1510        else
1511            desc.slang.targetFlags = 0;
1512
1513        List<const char*> requiredFeatureList;
1514        for (auto& name : options.renderFeatures)
1515            requiredFeatureList.add(name.getBuffer());
1516
1517        desc.requiredFeatures = requiredFeatureList.getBuffer();
1518        desc.requiredFeatureCount = (int)requiredFeatureList.getCount();
1519
1520#if defined(_WIN32)
1521        // When the experimental feature is enabled, things become unstable.
1522        // It is enabled only when requested.
1523        D3D12ExperimentalFeaturesDesc experimentalFD = {};
1524        UUID features[1] = {D3D12ExperimentalShaderModels};
1525        experimentalFD.featureCount = 1;
1526        experimentalFD.featureIIDs = features;
1527        experimentalFD.configurationStructs = nullptr;
1528        experimentalFD.configurationStructSizes = nullptr;
1529
1530        if (options.dx12Experimental)
1531        {
1532            // Check if Windows Developer mode is enabled
1533            if (!isWindowsDeveloperModeEnabled())
1534            {
1535                return SLANG_E_NOT_AVAILABLE;
1536            }
1537            desc.next = &experimentalFD;
1538        }
1539#endif
1540
1541        // Look for args going to slang
1542        {
1543            const auto& args = options.downstreamArgs.getArgsByName("slang");
1544            for (const auto& arg : args)
1545            {
1546                if (arg.value == "-matrix-layout-column-major")
1547                {
1548                    desc.slang.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR;
1549                    break;
1550                }
1551            }
1552        }
1553
1554        desc.nvapiExtUavSlot = uint32_t(nvapiExtnSlot);
1555        desc.slang.slangGlobalSession = session;
1556        desc.slang.targetProfile = options.profileName.getBuffer();
1557        {
1558            if (options.enableDebugLayers)
1559            {
1560                getRHI()->enableDebugLayers();
1561            }
1562            Slang::ComPtr<rhi::IDevice> rhiDevice;
1563            SlangResult res;
1564            if (options.cacheRhiDevice)
1565            {
1566                res = DeviceCache::acquireDevice(desc, rhiDevice.writeRef());
1567                if (SLANG_FAILED(res))
1568                {
1569                    rhiDevice = nullptr;
1570                }
1571            }
1572            else
1573            {
1574                res = rhi::getRHI()->createDevice(desc, rhiDevice.writeRef());
1575                if (SLANG_FAILED(res))
1576                {
1577                    rhiDevice = nullptr;
1578                }
1579            }
1580
1581            // Check result for both cached and non-cached paths
1582            if (SLANG_FAILED(res) || !rhiDevice)
1583            {
1584                // We need to be careful here about SLANG_E_NOT_AVAILABLE. This return value means
1585                // that the renderer couldn't be created because it required *features* that were
1586                // *not available*. It does not mean the renderer in general couldn't be
1587                // constructed.
1588                //
1589                // Returning SLANG_E_NOT_AVAILABLE will lead to the test infrastructure ignoring
1590                // this test.
1591                //
1592                // We also don't want to output the 'Unable to create renderer' error, as this isn't
1593                // an error.
1594                if (res == SLANG_E_NOT_AVAILABLE)
1595                {
1596                    return res;
1597                }
1598                if (!options.onlyStartup)
1599                {
1600                    fprintf(stderr, "Unable to create renderer %s\n", rendererName.getBuffer());
1601                }
1602                return res;
1603            }
1604            SLANG_ASSERT(rhiDevice);
1605            deviceWrapper = CachedDeviceWrapper(rhiDevice);
1606        }
1607
1608        for (const auto& feature : requiredFeatureList)
1609        {
1610            // If doesn't have required feature... we have to give up
1611            if (!deviceWrapper->hasFeature(feature))
1612            {
1613                return SLANG_E_NOT_AVAILABLE;
1614            }
1615        }
1616    }
1617
1618    // Print adapter info after device creation but before any other operations
1619    if (options.showAdapterInfo)
1620    {
1621        auto info = deviceWrapper->getInfo();
1622        auto out = stdWriters->getOut();
1623        out.print("Using graphics adapter: %s\n", info.adapterName);
1624    }
1625
1626    // If the only test is we can startup, then we are done
1627    if (options.onlyStartup)
1628    {
1629        return SLANG_OK;
1630    }
1631
1632    {
1633        RenderTestApp app;
1634        renderDocBeginFrame();
1635        SLANG_RETURN_ON_FAIL(app.initialize(session, deviceWrapper.get(), options, input));
1636        app.update();
1637        renderDocEndFrame();
1638        app.finalize();
1639    }
1640
1641    return SLANG_OK;
1642}
1643
1644SLANG_TEST_TOOL_API void cleanDeviceCache()
1645{
1646    DeviceCache::cleanCache();
1647}
1648
1649SLANG_TEST_TOOL_API SlangResult innerMain(
1650    Slang::StdWriters* stdWriters,
1651    SlangSession* sharedSession,
1652    int inArgc,
1653    const char* const* inArgv)
1654{
1655    using namespace Slang;
1656
1657    // Assume we will used the shared session
1658    ComPtr<slang::IGlobalSession> session(sharedSession);
1659
1660    // The sharedSession always has a pre-loaded core module.
1661    // This differed test checks if the command line has an option to setup the core module.
1662    // If so we *don't* use the sharedSession, and create a new session without the core module just
1663    // for this compilation.
1664    if (TestToolUtil::hasDeferredCoreModule(Index(inArgc - 1), inArgv + 1))
1665    {
1666        SLANG_RETURN_ON_FAIL(
1667            slang_createGlobalSessionWithoutCoreModule(SLANG_API_VERSION, session.writeRef()));
1668    }
1669
1670    SlangResult res = SLANG_FAIL;
1671    try
1672    {
1673        res = _innerMain(stdWriters, session, inArgc, inArgv);
1674    }
1675    catch (const Slang::Exception& exception)
1676    {
1677        stdWriters->getOut().put(exception.Message.getUnownedSlice());
1678        return SLANG_FAIL;
1679    }
1680    catch (...)
1681    {
1682        stdWriters->getOut().put(UnownedStringSlice::fromLiteral("Unhandled exception"));
1683        return SLANG_FAIL;
1684    }
1685
1686    return res;
1687}
1688
1689int main(int argc, char** argv)
1690{
1691    using namespace Slang;
1692    SlangSession* session = spCreateSession(nullptr);
1693
1694    TestToolUtil::setSessionDefaultPreludeFromExePath(argv[0], session);
1695
1696    auto stdWriters = StdWriters::initDefaultSingleton();
1697
1698    SlangResult res = innerMain(stdWriters, session, argc, argv);
1699    spDestroySession(session);
1700
1701    slang::shutdown();
1702    return (int)TestToolUtil::getReturnCode(res);
1703}