yum-mirror/slang

Making it easier to work with shaders

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

Jay KwakAdd command-line arguments to examples (#7835)13dd01489

master
37.6 KiB1003 linesraw
1// This example is out of date and currently disabled from build.
2// The `gfx` layer has been refactored with a new shader-object model
3// that will greatly simplify shader binding and specialization.
4// This example should be updated to use the shader-object API in `gfx`.
5
6// main.cpp
7
8//
9// This example is much more involved than the `hello-world` example,
10// so readers are encouraged to work through the simpler code first
11// before diving into this application. We will gloss over parts of
12// the code that are similar to the code in `hello-world`, and
13// instead focus on the new code that is required to use Slang in
14// more advanced ways.
15//
16
17// We still need to include the Slang header to use the Slang API
18//
19#include "slang-com-helper.h"
20#include "slang.h"
21
22// We will again make use of a graphics API abstraction
23// layer that implements the shader-object idiom based on Slang's
24// `ParameterBlock` and `interface` features to simplify shader specialization
25// and parameter binding.
26//
27#include "examples/example-base/example-base.h"
28#include "platform/gui.h"
29#include "platform/model.h"
30#include "platform/vector-math.h"
31#include "platform/window.h"
32#include "slang-rhi.h"
33
34#include <map>
35#include <slang-rhi/shader-cursor.h>
36#include <sstream>
37
38using namespace rhi;
39using Slang::RefObject;
40using Slang::RefPtr;
41
42static const ExampleResources resourceBase("model-viewer");
43
44struct RendererContext
45{
46    IDevice* device;
47    slang::IModule* shaderModule;
48    slang::ShaderReflection* slangReflection;
49    ComPtr<IShaderProgram> shaderProgram;
50
51    slang::TypeReflection* perViewShaderType;
52    slang::TypeReflection* perModelShaderType;
53
54    TestBase* pTestBase;
55
56    Result init(IDevice* inDevice, TestBase* inTestBase)
57    {
58        device = inDevice;
59        ComPtr<ISlangBlob> diagnostic;
60        pTestBase = inTestBase;
61
62        Slang::String path = resourceBase.resolveResource("shaders.slang").getBuffer();
63        shaderModule =
64            device->getSlangSession()->loadModule(path.getBuffer(), diagnostic.writeRef());
65        diagnoseIfNeeded(diagnostic);
66        if (!shaderModule)
67            return SLANG_FAIL;
68
69        // Compose the shader program for drawing models by combining the shader module
70        // and entry points ("vertexMain" and "fragmentMain").
71        char const* vertexEntryPointName = "vertexMain";
72        ComPtr<slang::IEntryPoint> vertexEntryPoint;
73        SLANG_RETURN_ON_FAIL(
74            shaderModule->findEntryPointByName(vertexEntryPointName, vertexEntryPoint.writeRef()));
75
76        char const* fragEntryPointName = "fragmentMain";
77        ComPtr<slang::IEntryPoint> fragEntryPoint;
78        SLANG_RETURN_ON_FAIL(
79            shaderModule->findEntryPointByName(fragEntryPointName, fragEntryPoint.writeRef()));
80
81        // At this point we have a few different Slang API objects that represent
82        // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`.
83        //
84        // A single Slang module could contain many different entry points (e.g.,
85        // four vertex entry points, three fragment entry points, and two compute
86        // shaders), and before we try to generate output code for our target API
87        // we need to identify which entry points we plan to use together.
88        //
89        // Modules and entry points are both examples of *component types* in the
90        // Slang API. The API also provides a way to build a *composite* out of
91        // other pieces, and that is what we are going to do with our module
92        // and entry points.
93        //
94        Slang::List<slang::IComponentType*> componentTypes;
95        componentTypes.add(shaderModule);
96        componentTypes.add(vertexEntryPoint);
97        componentTypes.add(fragEntryPoint);
98
99        // Actually creating the composite component type is a single operation
100        // on the Slang session, but the operation could potentially fail if
101        // something about the composite was invalid (e.g., you are trying to
102        // combine multiple copies of the same module), so we need to deal
103        // with the possibility of diagnostic output.
104        //
105        ComPtr<slang::IComponentType> composedProgram;
106        ComPtr<ISlangBlob> diagnosticsBlob;
107        SlangResult result = device->getSlangSession()->createCompositeComponentType(
108            componentTypes.getBuffer(),
109            componentTypes.getCount(),
110            composedProgram.writeRef(),
111            diagnosticsBlob.writeRef());
112        diagnoseIfNeeded(diagnosticsBlob);
113        SLANG_RETURN_ON_FAIL(result);
114
115        if (pTestBase && pTestBase->isTestMode())
116        {
117            pTestBase->printEntrypointHashes(componentTypes.getCount() - 1, 1, composedProgram);
118        }
119
120        slangReflection = composedProgram->getLayout();
121
122        // At this point, `composedProgram` represents the shader program
123        // we want to run, and the compute shader there have been checked.
124        // We can create a `IShaderProgram` object from `composedProgram`
125        // so it may be used by the graphics layer.
126        ShaderProgramDesc programDesc = {};
127        programDesc.slangGlobalScope = composedProgram.get();
128
129        shaderProgram = device->createShaderProgram(programDesc);
130
131        // Get other shader types that we will use for creating shader objects.
132        perViewShaderType = slangReflection->findTypeByName("PerView");
133        perModelShaderType = slangReflection->findTypeByName("PerModel");
134
135        return SLANG_OK;
136    }
137};
138
139// Our application code has a rudimentary material system,
140// to match the `IMaterial` abstraction used in the shade code.
141//
142struct Material : RefObject
143{
144    // The key feature of a matrial in our application is that
145    // it can provide a shader object that describes it and
146    // its parameters. The contents of the shader object will
147    // be any colors, textures, etc. that the material needs,
148    // while the Slang type that was used to allocate the
149    // block will be an implementation of `IMaterial` that
150    // provides the evaluation logic for the material.
151
152    // Each subclass of `Material` will provide a routine to
153    // create a shader object that stores its shader parameters.
154    virtual IShaderObject* createShaderObject(RendererContext* context) = 0;
155
156    // The shader object for a material will be stashed here
157    // after it is created.
158    ComPtr<IShaderObject> shaderObject;
159};
160
161// For now we have only a single implementation of `Material`,
162// which corresponds to the `SimpleMaterial` type in our shader
163// code.
164//
165struct SimpleMaterial : Material
166{
167    glm::vec3 diffuseColor;
168    glm::vec3 specularColor;
169    float specularity = 1.0f;
170
171    // Create a shader object that contains the type info and parameter values
172    // that represent an instance of `SimpleMaterial`.
173    IShaderObject* createShaderObject(RendererContext* context) override
174    {
175        auto program = context->slangReflection;
176        auto shaderType = program->findTypeByName("SimpleMaterial");
177        shaderObject = context->device->createShaderObject(shaderType);
178        ShaderCursor cursor(shaderObject);
179        cursor["diffuseColor"].setData(&diffuseColor, sizeof(diffuseColor));
180        cursor["specularColor"].setData(&specularColor, sizeof(specularColor));
181        cursor["specularity"].setData(&specularity, sizeof(specularity));
182        return shaderObject.get();
183    }
184};
185
186// With the `Material` abstraction defined, we can go on to define
187// the representation for loaded models that we will use.
188//
189// A `Model` will own vertex/index buffers, along with a list of meshes,
190// while each `Mesh` will own a material and a range of indices.
191// For this example we will be loading models from `.obj` files, but
192// that is just a simple lowest-common-denominator choice.
193//
194struct Mesh : RefObject
195{
196    RefPtr<Material> material;
197    int firstIndex;
198    int indexCount;
199};
200struct Model : RefObject
201{
202    typedef platform::ModelLoader::Vertex Vertex;
203
204    ComPtr<IBuffer> vertexBuffer;
205    ComPtr<IBuffer> indexBuffer;
206    PrimitiveTopology primitiveTopology;
207    int vertexCount;
208    int indexCount;
209    std::vector<RefPtr<Mesh>> meshes;
210};
211//
212// Loading a model from disk is done with the help of some utility
213// code for parsing the `.obj` file format, so that the application
214// mostly just registers some callbacks to allocate the objects
215// used for its representation.
216//
217RefPtr<Model> loadModel(
218    RendererContext* context,
219    char const* inputPath,
220    platform::ModelLoader::LoadFlags loadFlags = 0,
221    float scale = 1.0f)
222{
223    // The model loading interface using a C++ interface of
224    // callback functions to handle creating the application-specific
225    // representation of meshes, materials, etc.
226    //
227    struct Callbacks : platform::ModelLoader::ICallbacks
228    {
229        RendererContext* context;
230        // Hold a reference to all material and mesh objects
231        // created during loading so that they can be properly
232        // freed.
233        std::vector<RefPtr<Material>> materials;
234        std::vector<RefPtr<Mesh>> meshes;
235        void* createMaterial(MaterialData const& data) override
236        {
237            SimpleMaterial* material = new SimpleMaterial();
238            material->diffuseColor = data.diffuseColor;
239            material->specularColor = data.specularColor;
240            material->specularity = data.specularity;
241            material->createShaderObject(context);
242            materials.push_back(material);
243            return material;
244        }
245
246        void* createMesh(MeshData const& data) override
247        {
248            Mesh* mesh = new Mesh();
249            mesh->firstIndex = data.firstIndex;
250            mesh->indexCount = data.indexCount;
251            mesh->material = (Material*)data.material;
252            meshes.push_back(mesh);
253            return mesh;
254        }
255
256        void* createModel(ModelData const& data) override
257        {
258            Model* model = new Model();
259            model->vertexBuffer = data.vertexBuffer;
260            model->indexBuffer = data.indexBuffer;
261            model->primitiveTopology = data.primitiveTopology;
262            model->vertexCount = data.vertexCount;
263            model->indexCount = data.indexCount;
264
265            int meshCount = data.meshCount;
266            for (int ii = 0; ii < meshCount; ++ii)
267                model->meshes.push_back((Mesh*)data.meshes[ii]);
268
269            return model;
270        }
271    };
272    Callbacks callbacks;
273    callbacks.context = context;
274
275    // We instantiate a model loader object and then use it to
276    // try and load a model from the chosen path.
277    //
278    platform::ModelLoader loader;
279    loader.device = context->device;
280    loader.loadFlags = loadFlags;
281    loader.scale = scale;
282    loader.callbacks = &callbacks;
283    Model* model = nullptr;
284    if (SLANG_FAILED(loader.load(inputPath, (void**)&model)))
285    {
286        log("failed to load '%s'\n", inputPath);
287        return nullptr;
288    }
289
290    return model;
291}
292
293// Along with materials, our application needs to be able to represent
294// multiple light sources in the scene. For this task we will use a C++
295// inheritance hierarchy rooted at `Light` to match the `ILight`
296// interface in Slang.
297
298struct Light : RefObject
299{
300    // A light must be able to write its state into a shader parameters
301    // of the matching Slang type.
302    //
303    virtual void writeTo(ShaderCursor const& cursor) = 0;
304
305    // Retrieves the shader type for this light object.
306    virtual slang::TypeReflection* getShaderType(RendererContext* context) = 0;
307
308    // The shader object for a light will be stashed here
309    // after it is created.
310    //    ComPtr<IShaderObject> shaderObject;
311};
312
313// Helper function to retrieve the underlying shader type of `T`.
314template<typename T>
315slang::TypeReflection* getShaderType(RendererContext* context)
316{
317    auto program = context->slangReflection;
318    auto shaderType = program->findTypeByName(T::getTypeName());
319    return shaderType;
320}
321
322// We will provide two nearly trivial implementations of `Light` for now,
323// to show the kind of application code needed to line up with the corresponding
324// types defined in the Slang shader code for this application.
325
326struct DirectionalLight : Light
327{
328    glm::vec3 direction = normalize(glm::vec3(1));
329    glm::vec3 intensity = glm::vec3(1);
330
331    static const char* getTypeName() { return "DirectionalLight"; }
332
333    virtual void writeTo(ShaderCursor const& cursor) override
334    {
335        cursor["direction"].setData(&direction, sizeof(direction));
336        cursor["intensity"].setData(&intensity, sizeof(intensity));
337    }
338
339    virtual slang::TypeReflection* getShaderType(RendererContext* context) override
340    {
341        return ::getShaderType<DirectionalLight>(context);
342    }
343};
344
345struct PointLight : Light
346{
347    glm::vec3 position = glm::vec3(0);
348    glm::vec3 intensity = glm::vec3(1);
349
350    static const char* getTypeName() { return "PointLight"; }
351
352    virtual void writeTo(ShaderCursor const& cursor) override
353    {
354        cursor["position"].setData(&position, sizeof(position));
355        cursor["intensity"].setData(&intensity, sizeof(intensity));
356    }
357
358    virtual slang::TypeReflection* getShaderType(RendererContext* context) override
359    {
360        return ::getShaderType<PointLight>(context);
361    }
362};
363
364// Rendering is usually done with collections of lights rather than single
365// lights. This application will use a concept of "light environments" to
366// group together lights for rendering.
367//
368// We want to be *able* to specialize our shader code based on the particular
369// types of lights in a scene, but we also do not want to over-specialize
370// and, e.g., use differnt specialized shaders for a scene with 99 point
371// lights vs. 100.
372//
373// This particular application will use a notion of a "layout" for a lighting
374// environment, which specifies the allowed types of lights, and the maximum
375// number of lights of each type. Different lighting environment layouts
376// will yield different specialized code.
377
378struct LightEnvLayout : public RefObject
379{
380    // Our lighting environment layout will track layout
381    // information for several different arrays: one
382    // for each supported light type.
383    //
384    struct LightArrayLayout : RefObject
385    {
386        SlangInt maximumCount = 0;
387        std::string typeName;
388    };
389    std::vector<LightArrayLayout> lightArrayLayouts;
390    std::map<slang::TypeReflection*, SlangInt> mapLightTypeToArrayIndex;
391    slang::TypeReflection* shaderType = nullptr;
392
393    void addLightType(
394        RendererContext* context,
395        slang::TypeReflection* lightType,
396        SlangInt maximumCount)
397    {
398        SlangInt arrayIndex = (SlangInt)lightArrayLayouts.size();
399        LightArrayLayout layout;
400        layout.maximumCount = maximumCount;
401
402        // When the user adds a light type `X` to a light-env layout,
403        // we need to compute the corresponding Slang type and
404        // layout information to use. If only a single light is
405        // supported, this will just be the type `X`, while for
406        // any other count this will be a `LightArray<X, maximumCount>`
407        //
408        if (maximumCount <= 1)
409        {
410            layout.typeName = lightType->getName();
411        }
412        else
413        {
414            auto program = context->slangReflection;
415            std::stringstream typeNameBuilder;
416            typeNameBuilder << "LightArray<" << lightType->getName() << "," << maximumCount << ">";
417            layout.typeName = typeNameBuilder.str();
418        }
419
420        lightArrayLayouts.push_back(layout);
421        mapLightTypeToArrayIndex.insert(std::make_pair(lightType, arrayIndex));
422    }
423
424    template<typename T>
425    void addLightType(RendererContext* context, SlangInt maximumCount)
426    {
427        addLightType(context, getShaderType<T>(context), maximumCount);
428    }
429
430    SlangInt getArrayIndexForType(slang::TypeReflection* lightType)
431    {
432        auto iter = mapLightTypeToArrayIndex.find(lightType);
433        if (iter != mapLightTypeToArrayIndex.end())
434            return iter->second;
435
436        return -1;
437    }
438};
439
440// A `LightEnv` follows the structure of a `LightEnvLayout`,
441// and provides storage for zero or more lights of various
442// different types (up to the limits imposed by the layout).
443//
444struct LightEnv : public RefObject
445{
446    // A light environment is always created from a fixed layout
447    // in this application, so the constructor allocates an array
448    // for the per-light-type data.
449    //
450    // A more complex example might dynamically determine the
451    // layout based on the number of lights of each type active
452    // in the scene, with some quantization applied to avoid
453    // generating too many shader specializations.
454    //
455    // Note: the kind of specialization going on here would also
456    // be applicable to a deferred or "forward+" renderer, insofar
457    // as it sets the bounds on the total set of lights for
458    // a scene/frame, while per-tile/-cluster light lists would
459    // probably just be indices into the global structure.
460    //
461    RefPtr<LightEnvLayout> layout;
462    RendererContext* context;
463    LightEnv(RefPtr<LightEnvLayout> layout, RendererContext* inContext)
464        : layout(layout), context(inContext)
465    {
466        for (auto arrayLayout : layout->lightArrayLayouts)
467        {
468            RefPtr<LightArray> lightArray = new LightArray();
469            lightArray->layout = arrayLayout;
470            lightArrays.push_back(lightArray);
471        }
472    }
473
474    // For each light type, we track the layout information,
475    // plus the list of active lights of that type.
476    //
477    struct LightArray : RefObject
478    {
479        LightEnvLayout::LightArrayLayout layout;
480        std::vector<RefPtr<Light>> lights;
481    };
482    std::vector<RefPtr<LightArray>> lightArrays;
483
484    RefPtr<LightArray> getArrayForType(slang::TypeReflection* type)
485    {
486        auto index = layout->getArrayIndexForType(type);
487        return lightArrays[index];
488    }
489
490    void add(RefPtr<Light> light)
491    {
492        auto array = getArrayForType(light->getShaderType(context));
493        array->lights.push_back(light);
494    }
495
496    // Get the proper shader type that represents this lighting environment.
497    slang::TypeReflection* getShaderType()
498    {
499        // Given a lighting environment with N light types:
500        //
501        // L0, L1, ... LN
502        //
503        // We want to compute the Slang type:
504        //
505        // LightPair<L0, LightPair<L1, ... LightPair<LN-1, LN>>>
506        //
507        // This is most easily accomplished by doing a "fold" while
508        // walking the array in reverse order.
509
510        std::string currentEnvTypeName;
511        auto arrayCount = layout->lightArrayLayouts.size();
512        for (size_t ii = arrayCount; ii--;)
513        {
514            auto arrayInfo = layout->lightArrayLayouts[ii];
515
516            if (!currentEnvTypeName.size())
517            {
518                // The is the right-most entry, so it is the base case for our "fold".
519                currentEnvTypeName = arrayInfo.typeName;
520            }
521            else
522            {
523                // Fold one entry: `envLayout = LightPair<a, envLayout>`
524                std::stringstream typeBuilder;
525                typeBuilder << "LightPair<" << arrayInfo.typeName << "," << currentEnvTypeName
526                            << ">";
527                currentEnvTypeName = typeBuilder.str();
528            }
529        }
530
531        if (!currentEnvTypeName.size())
532        {
533            // Handle the special case of *zero* light types.
534            currentEnvTypeName = "EmptyLightEnv";
535        }
536        return context->slangReflection->findTypeByName(currentEnvTypeName.c_str());
537    }
538
539    // Because the lighting environment will often change between frames,
540    // we will not try to optimize for the case where it doesn't change,
541    // and will instead create a "transient" shader object from
542    // scratch every frame.
543    //
544    ComPtr<IShaderObject> createShaderObject()
545    {
546        auto specializedType = getShaderType();
547
548        auto shaderObject = context->device->createShaderObject(specializedType);
549        ShaderCursor cursor(shaderObject);
550        // When filling in the shader object for a lighting
551        // environment, we mostly follow the structure of
552        // the type that was computed by the `LightEnv::getShaderType`:
553        //
554        //      LightPair<A, LightPair<B, ... LightPair<Y, Z>>>
555        //
556        // we will keep `encoder` pointed at the "spine" of this
557        // structure (so at an element that represents a `LightPair`,
558        // except for the special case of the last item like `Z` above).
559        //
560        // For each light type, we will then encode the data as
561        // needed for the light type (`A` then `B` then ...)
562        //
563        size_t lightTypeCount = lightArrays.size();
564        for (size_t tt = 0; tt < lightTypeCount; ++tt)
565        {
566            // The encoder for the very last item will
567            // just be the one on the "spine" of the list.
568            auto lightTypeCursor = cursor;
569            if (tt != lightTypeCount - 1)
570            {
571                // In the common case `encoder` is set up
572                // for writing to a `LightPair<X, Y>` so
573                // we ant to set up the `lightTypeEncoder`
574                // for writing to an `X` (which is the first
575                // field of `LightPair`, and then have
576                // `encoder` move on to the `Y` (the rest
577                // of the list of light types).
578                //
579                lightTypeCursor = cursor["first"];
580                cursor = cursor["second"];
581            }
582
583            auto& lightTypeArray = lightArrays[tt];
584            size_t lightCount = lightTypeArray->lights.size();
585            size_t maxLightCount = lightTypeArray->layout.maximumCount;
586
587            // Recall that we are representing the data for a single
588            // light type `L` as either an instance of type `L` (if
589            // only a single light is supported), or as an instance
590            // of the type `LightArray<L,N>`.
591            //
592            if (maxLightCount == 1)
593            {
594                // This is the case where the maximu number of lights of
595                // the given type was set as one, so we just have a value
596                // of type `L`, and can tell the first light in our application-side
597                // array to encode itself into that location.
598
599                if (lightCount > 0)
600                {
601                    lightTypeArray->lights[0]->writeTo(lightTypeCursor);
602                }
603                else
604                {
605                    // We really ought to zero out the entry in this case
606                    // (under the assumption that all zeros will represent
607                    // an inactive light).
608                }
609            }
610            else
611            {
612                // The more interesting case is when we have a `LightArray<L,N>`,
613                // in which case we need to fill in the first field (the light count)...
614                //
615                int32_t lightCount = int32_t(lightTypeArray->lights.size());
616                lightTypeCursor["count"].setData(&lightCount, sizeof(lightCount));
617                //
618                // ... followed by an array of values of type `L` in the second field.
619                // We will only write to the first `lightCount` entries, which may be
620                // less than `N`. We will rely on dynamic looping in the shader to
621                // not access the entries past that point.
622                //
623                auto arrayCursor = lightTypeCursor["lights"];
624                for (int32_t ii = 0; ii < lightCount; ++ii)
625                {
626                    lightTypeArray->lights[ii]->writeTo(arrayCursor[ii]);
627                }
628            }
629        }
630        return shaderObject;
631    }
632};
633
634// Now that we've written all the required infrastructure code for
635// the application's renderer and shader library, we can move on
636// to the main logic.
637//
638// We will again structure our example application as a C++ `struct`,
639// so that we can scope its allocations for easy cleanup, rather than
640// use global variables.
641//
642struct ModelViewer : WindowedAppBase
643{
644    RendererContext context;
645
646    // Most of the application state is stored in the list of loaded models,
647    // as well as the active light source (a single light for now).
648    //
649    std::vector<RefPtr<Model>> gModels;
650    RefPtr<LightEnv> lightEnv;
651
652    // The pipeline state object we will use to draw models.
653    ComPtr<IPipeline> gPipelineState;
654
655    // During startup the application will load one or more models and
656    // add them to the `gModels` list.
657    //
658    void loadAndAddModel(
659        char const* inputPath,
660        platform::ModelLoader::LoadFlags loadFlags = 0,
661        float scale = 1.0f)
662    {
663        auto model = loadModel(&context, inputPath, loadFlags, scale);
664        if (!model)
665            return;
666        gModels.push_back(model);
667    }
668
669    // Our "simulation" state consists of just a few values.
670    //
671    uint64_t lastTime = 0;
672
673    // glm::vec3 lightDir = normalize(glm::vec3(10, 10, 10));
674    // glm::vec3 lightColor = glm::vec3(1, 1, 1);
675
676    glm::vec3 cameraPosition = glm::vec3(1.75, 1.25, 5);
677    glm::quat cameraOrientation = glm::quat(1, glm::vec3(0));
678
679    float translationScale = 0.5f;
680    float rotationScale = 0.025f;
681
682    // In order to control camera movement, we will
683    // use good old WASD
684    bool wPressed = false;
685    bool aPressed = false;
686    bool sPressed = false;
687    bool dPressed = false;
688
689    bool isMouseDown = false;
690    float lastMouseX = 0.0f;
691    float lastMouseY = 0.0f;
692
693    void setKeyState(platform::KeyCode key, bool state)
694    {
695        switch (key)
696        {
697        default:
698            break;
699        case platform::KeyCode::W:
700            wPressed = state;
701            break;
702        case platform::KeyCode::A:
703            aPressed = state;
704            break;
705        case platform::KeyCode::S:
706            sPressed = state;
707            break;
708        case platform::KeyCode::D:
709            dPressed = state;
710            break;
711        }
712    }
713    void onKeyDown(platform::KeyEventArgs args) { setKeyState(args.key, true); }
714    void onKeyUp(platform::KeyEventArgs args) { setKeyState(args.key, false); }
715
716    void onMouseDown(platform::MouseEventArgs args)
717    {
718        isMouseDown = true;
719        lastMouseX = (float)args.x;
720        lastMouseY = (float)args.y;
721    }
722
723    void onMouseMove(platform::MouseEventArgs args)
724    {
725        if (isMouseDown)
726        {
727            float deltaX = args.x - lastMouseX;
728            float deltaY = args.y - lastMouseY;
729
730            cameraOrientation =
731                glm::rotate(cameraOrientation, -deltaX * rotationScale, glm::vec3(0, 1, 0));
732            cameraOrientation =
733                glm::rotate(cameraOrientation, -deltaY * rotationScale, glm::vec3(1, 0, 0));
734
735            cameraOrientation = normalize(cameraOrientation);
736
737            lastMouseX = (float)args.x;
738            lastMouseY = (float)args.y;
739        }
740    }
741    void onMouseUp(platform::MouseEventArgs args) { isMouseDown = false; }
742
743    // The overall initialization logic is quite similar to
744    // the earlier example. The biggest difference is that we
745    // create instances of our application-specific parameter
746    // block layout and effect types instead of just creating
747    // raw graphics API objects.
748    //
749    Result initialize()
750    {
751        SLANG_RETURN_ON_FAIL(initializeBase("Model Viewer", 1024, 768, getDeviceType()));
752        if (!isTestMode())
753        {
754            gWindow->events.mouseMove = [this](const platform::MouseEventArgs& e)
755            { onMouseMove(e); };
756            gWindow->events.mouseUp = [this](const platform::MouseEventArgs& e) { onMouseUp(e); };
757            gWindow->events.mouseDown = [this](const platform::MouseEventArgs& e)
758            { onMouseDown(e); };
759            gWindow->events.keyDown = [this](const platform::KeyEventArgs& e) { onKeyDown(e); };
760            gWindow->events.keyUp = [this](const platform::KeyEventArgs& e) { onKeyUp(e); };
761        }
762
763        // Initialize `RendererContext`, which loads the shader module from file.
764        SLANG_RETURN_ON_FAIL(context.init(gDevice, this));
765
766
767        InputElementDesc inputElements[] = {
768            {"POSITION", 0, Format::RGB32Float, offsetof(Model::Vertex, position)},
769            {"NORMAL", 0, Format::RGB32Float, offsetof(Model::Vertex, normal)},
770            {"UV", 0, Format::RG32Float, offsetof(Model::Vertex, uv)},
771        };
772        auto inputLayout = gDevice->createInputLayout(sizeof(Model::Vertex), &inputElements[0], 3);
773        if (!inputLayout)
774            return SLANG_FAIL;
775
776        // Create the pipeline state object for drawing models.
777        RenderPipelineDesc pipelineStateDesc = {};
778        pipelineStateDesc.program = context.shaderProgram;
779        pipelineStateDesc.inputLayout = inputLayout;
780        pipelineStateDesc.primitiveTopology = PrimitiveTopology::TriangleList;
781        pipelineStateDesc.depthStencil.depthFunc = ComparisonFunc::LessEqual;
782        pipelineStateDesc.depthStencil.depthTestEnable = true;
783        // Set up color target
784        ColorTargetDesc colorTarget = {};
785        colorTarget.format = Format::RGBA8Unorm;
786        pipelineStateDesc.targetCount = 1;
787        pipelineStateDesc.targets = &colorTarget;
788        gPipelineState = gDevice->createRenderPipeline(pipelineStateDesc);
789
790        // We will create a lighting environment layout that can hold a few point
791        // and directional lights, and then initialize a lighting environment
792        // with just a single point light.
793        //
794        RefPtr<LightEnvLayout> lightEnvLayout = new LightEnvLayout();
795        lightEnvLayout->addLightType<PointLight>(&context, 10);
796        lightEnvLayout->addLightType<DirectionalLight>(&context, 2);
797
798        lightEnv = new LightEnv(lightEnvLayout, &context);
799
800        RefPtr<PointLight> pointLight = new PointLight();
801        pointLight->position = glm::vec3(5, 3, 1);
802        pointLight->intensity = glm::vec3(10);
803        lightEnv->add(pointLight);
804
805        // Once we have created all our graphcis API and application resources,
806        // we can start to load models. For now we are keeping things extremely
807        // simple by using a trivial `.obj` file that can be checked into source
808        // control.
809        //
810        // Support for loading more interesting/complex models will be added
811        // to this example over time (although model loading is *not* the focus).
812        //
813        Slang::String path = resourceBase.resolveResource("cube.obj").getBuffer();
814        loadAndAddModel(path.getBuffer());
815
816        return SLANG_OK;
817    }
818
819    // With the setup work done, we can look at the per-frame rendering
820    // logic to see how the application will drive the `RenderContext`
821    // type to perform both shader parameter binding and code specialization.
822    //
823    void renderFrame(ITexture* texture) override
824    {
825        // In order to see that things are rendering properly we need some
826        // kind of animation, so we will compute a crude delta-time value here.
827        //
828        if (!lastTime)
829            lastTime = getCurrentTime();
830        uint64_t currentTime = getCurrentTime();
831        float deltaTime = float(double(currentTime - lastTime) / double(getTimerFrequency()));
832        lastTime = currentTime;
833
834        // We will use the GLM library to do the matrix math required
835        // to set up our various transformation matrices.
836        //
837        glm::mat4x4 identity = glm::mat4x4(1.0f);
838
839        platform::Rect clientRect{};
840        if (isTestMode())
841        {
842            clientRect.width = 1024;
843            clientRect.height = 768;
844        }
845        else
846        {
847            clientRect = getWindow()->getClientRect();
848        }
849        if (clientRect.height == 0)
850            return;
851        glm::mat4x4 projection = glm::perspectiveRH_ZO(
852            glm::radians(60.0f),
853            float(clientRect.width) / float(clientRect.height),
854            0.1f,
855            1000.0f);
856
857        // We are implementing a *very* basic 6DOF first-person
858        // camera movement model.
859        //
860        glm::mat3x3 cameraOrientationMat(cameraOrientation);
861        glm::vec3 forward = -cameraOrientationMat[2];
862        glm::vec3 right = cameraOrientationMat[0];
863
864        glm::vec3 movement = glm::vec3(0);
865        if (wPressed)
866            movement += forward;
867        if (sPressed)
868            movement -= forward;
869        if (aPressed)
870            movement -= right;
871        if (dPressed)
872            movement += right;
873
874        cameraPosition += deltaTime * translationScale * movement;
875
876        glm::mat4x4 view = identity;
877        view *= glm::mat4x4(inverse(cameraOrientation));
878        view = glm::translate(view, -cameraPosition);
879
880        glm::mat4x4 viewProjection = projection * view;
881        auto deviceInfo = gDevice->getInfo();
882        // Use identity matrix for correction
883        static const float kIdentity[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
884        glm::mat4x4 correctionMatrix;
885        memcpy(&correctionMatrix, kIdentity, sizeof(float) * 16);
886        viewProjection = correctionMatrix * viewProjection;
887        // glm uses column-major layout, we need to translate it to row-major.
888        viewProjection = glm::transpose(viewProjection);
889
890        auto drawCommandEncoder = gQueue->createCommandEncoder();
891
892        ComPtr<ITextureView> textureView = gDevice->createTextureView(texture, {});
893        RenderPassColorAttachment colorAttachment = {};
894        colorAttachment.view = textureView;
895        colorAttachment.loadOp = LoadOp::Clear;
896
897        RenderPassDesc renderPass = {};
898        renderPass.colorAttachments = &colorAttachment;
899        renderPass.colorAttachmentCount = 1;
900
901        auto renderEncoder = drawCommandEncoder->beginRenderPass(renderPass);
902
903        RenderState renderState = {};
904        renderState.viewports[0] =
905            Viewport::fromSize((float)clientRect.width, (float)clientRect.height);
906        renderState.viewportCount = 1;
907        renderState.scissorRects[0] =
908            ScissorRect::fromSize((float)clientRect.width, (float)clientRect.height);
909        renderState.scissorRectCount = 1;
910
911        // We are only rendering one view, so we can fill in a per-view
912        // shader object once and use it across all draw calls.
913        //
914
915        auto viewShaderObject = gDevice->createShaderObject(context.perViewShaderType);
916        {
917            ShaderCursor cursor(viewShaderObject);
918            cursor["viewProjection"].setData(&viewProjection, sizeof(viewProjection));
919            cursor["eyePosition"].setData(&cameraPosition, sizeof(cameraPosition));
920        }
921        // The majority of our rendering logic is handled as a loop
922        // over the models in the scene, and their meshes.
923        //
924        for (auto& model : gModels)
925        {
926            renderState.vertexBuffers[0] = model->vertexBuffer;
927            renderState.vertexBufferCount = 1;
928            renderState.indexBuffer = model->indexBuffer;
929            renderState.indexFormat = IndexFormat::Uint32;
930            // For each model we provide a parameter
931            // block that holds the per-model transformation
932            // parameters, corresponding to the `PerModel` type
933            // in the shader code.
934            glm::mat4x4 modelTransform = identity;
935            glm::mat4x4 inverseTransposeModelTransform = inverse(transpose(modelTransform));
936            auto modelShaderObject = gDevice->createShaderObject(context.perModelShaderType);
937            {
938                ShaderCursor cursor(modelShaderObject);
939                cursor["modelTransform"].setData(&modelTransform, sizeof(modelTransform));
940                cursor["inverseTransposeModelTransform"].setData(
941                    &inverseTransposeModelTransform,
942                    sizeof(inverseTransposeModelTransform));
943            }
944
945            auto lightShaderObject = lightEnv->createShaderObject();
946
947            // Now we loop over the meshes in the model.
948            //
949            // A more advanced rendering loop would sort things by material
950            // rather than by model, to avoid overly frequent state changes.
951            // We are just doing something simple for the purposes of an
952            // exmple program.
953            //
954            for (auto& mesh : model->meshes)
955            {
956                // Set the pipeline and binding state for drawing each mesh.
957                auto rootObject = renderEncoder->bindPipeline(
958                    static_cast<IRenderPipeline*>(gPipelineState.get()));
959
960                // Apply render state
961                renderEncoder->setRenderState(renderState);
962
963                ShaderCursor rootCursor(rootObject);
964                rootCursor["gViewParams"].setObject(viewShaderObject);
965                rootCursor["gModelParams"].setObject(modelShaderObject);
966                rootCursor["gLightEnv"].setObject(lightShaderObject);
967
968                // Each mesh has a material, and each material has its own
969                // parameter block that was created at load time, so we
970                // can just re-use the persistent parameter block for the
971                // chosen material.
972                //
973                // Note that binding the material parameter block here is
974                // both selecting the values to use for various material
975                // parameters as well as the *code* to use for material
976                // evaluation (based on the concrete shader type that
977                // is implementing the `IMaterial` interface).
978                //
979                rootCursor["gMaterial"].setObject(mesh->material->shaderObject);
980
981                // All the shader parameters and pipeline states have been set up,
982                // we can now issue a draw call for the mesh.
983                DrawArguments drawArgs = {};
984                // `drawArgs.vertexCount` is actually `indexCount` for the `DrawIndexed` Graphics
985                // API
986                drawArgs.vertexCount = mesh->indexCount;
987                drawArgs.startIndexLocation = mesh->firstIndex;
988                renderEncoder->drawIndexed(drawArgs);
989            }
990        }
991        renderEncoder->end();
992        gQueue->submit(drawCommandEncoder->finish());
993
994        if (!isTestMode())
995        {
996            gSurface->present();
997        }
998    }
999};
1000
1001// This macro instantiates an appropriate main function to
1002// run the application defined above.
1003EXAMPLE_MAIN(innerMain<ModelViewer>);