yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
13dd01489
master
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{ 46IDevice * device ; 47 slang::IModule * shaderModule ; 48 slang::ShaderReflection * slangReflection ; 49ComPtr < IShaderProgram > shaderProgram ; 50 51 slang::TypeReflection * perViewShaderType ; 52 slang::TypeReflection * perModelShaderType ; 53 54TestBase * pTestBase ; 55 56Result init (IDevice * inDevice ,TestBase * inTestBase ) 57 { 58device = inDevice ; 59ComPtr < ISlangBlob > diagnostic ; 60pTestBase = inTestBase ; 61 62Slang ::String path = resourceBase .resolveResource ("shaders.slang" ).getBuffer (); 63shaderModule = 64device -> getSlangSession ()-> loadModule (path .getBuffer (),diagnostic .writeRef ()); 65diagnoseIfNeeded (diagnostic ); 66if (!shaderModule ) 67return SLANG_FAIL ; 68 69// Compose the shader program for drawing models by combining the shader module 70// and entry points ("vertexMain" and "fragmentMain"). 71char const * vertexEntryPointName = "vertexMain" ; 72ComPtr < slang::IEntryPoint > vertexEntryPoint ; 73SLANG_RETURN_ON_FAIL ( 74shaderModule -> findEntryPointByName (vertexEntryPointName ,vertexEntryPoint .writeRef ())); 75 76char const * fragEntryPointName = "fragmentMain" ; 77ComPtr < slang::IEntryPoint > fragEntryPoint ; 78SLANG_RETURN_ON_FAIL ( 79shaderModule -> 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// 94Slang ::List < slang::IComponentType *> componentTypes ; 95componentTypes .add (shaderModule ); 96componentTypes .add (vertexEntryPoint ); 97componentTypes .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// 105ComPtr < slang::IComponentType > composedProgram ; 106ComPtr < ISlangBlob > diagnosticsBlob ; 107SlangResult result = device -> getSlangSession ()-> createCompositeComponentType ( 108componentTypes .getBuffer (), 109componentTypes .getCount (), 110composedProgram .writeRef (), 111diagnosticsBlob .writeRef ()); 112diagnoseIfNeeded (diagnosticsBlob ); 113SLANG_RETURN_ON_FAIL (result ); 114 115if (pTestBase && pTestBase -> isTestMode ()) 116 { 117pTestBase -> printEntrypointHashes (componentTypes .getCount ()- 1 ,1 ,composedProgram ); 118 } 119 120slangReflection = 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. 126ShaderProgramDesc programDesc = {}; 127programDesc .slangGlobalScope = composedProgram .get (); 128 129shaderProgram = device -> createShaderProgram (programDesc ); 130 131// Get other shader types that we will use for creating shader objects. 132perViewShaderType = slangReflection -> findTypeByName ("PerView" ); 133perModelShaderType = slangReflection -> findTypeByName ("PerModel" ); 134 135return 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. 154virtual IShaderObject * createShaderObject (RendererContext * context )= 0 ; 155 156// The shader object for a material will be stashed here 157// after it is created. 158ComPtr < 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 ; 169float specularity = 1.0f ; 170 171// Create a shader object that contains the type info and parameter values 172// that represent an instance of `SimpleMaterial`. 173IShaderObject * createShaderObject (RendererContext * context )override 174 { 175auto program = context -> slangReflection ; 176auto shaderType = program -> findTypeByName ("SimpleMaterial" ); 177shaderObject = context -> device -> createShaderObject (shaderType ); 178ShaderCursor cursor (shaderObject ); 179cursor ["diffuseColor" ].setData (& diffuseColor ,sizeof (diffuseColor )); 180cursor ["specularColor" ].setData (& specularColor ,sizeof (specularColor )); 181cursor ["specularity" ].setData (& specularity ,sizeof (specularity )); 182return 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{ 196RefPtr < Material > material ; 197int firstIndex ; 198int indexCount ; 199}; 200struct Model :RefObject 201{ 202typedef platform::ModelLoader ::Vertex Vertex ; 203 204ComPtr < IBuffer > vertexBuffer ; 205ComPtr < IBuffer > indexBuffer ; 206PrimitiveTopology primitiveTopology ; 207int vertexCount ; 208int 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 ( 218RendererContext * context , 219char const * inputPath , 220 platform::ModelLoader ::LoadFlags loadFlags = 0 , 221float 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// 227struct Callbacks : platform::ModelLoader ::ICallbacks 228 { 229RendererContext * 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 ; 235void * createMaterial (MaterialData const & data )override 236 { 237SimpleMaterial * material = new SimpleMaterial (); 238material -> diffuseColor = data .diffuseColor ; 239material -> specularColor = data .specularColor ; 240material -> specularity = data .specularity ; 241material -> createShaderObject (context ); 242materials .push_back (material ); 243return material ; 244 } 245 246void * createMesh (MeshData const & data )override 247 { 248Mesh * mesh = new Mesh (); 249mesh -> firstIndex = data .firstIndex ; 250mesh -> indexCount = data .indexCount ; 251mesh -> material = (Material * )data .material ; 252meshes .push_back (mesh ); 253return mesh ; 254 } 255 256void * createModel (ModelData const & data )override 257 { 258Model * model = new Model (); 259model -> vertexBuffer = data .vertexBuffer ; 260model -> indexBuffer = data .indexBuffer ; 261model -> primitiveTopology = data .primitiveTopology ; 262model -> vertexCount = data .vertexCount ; 263model -> indexCount = data .indexCount ; 264 265int meshCount = data .meshCount ; 266for (int ii = 0 ;ii < meshCount ;++ ii ) 267model -> meshes .push_back ((Mesh * )data .meshes [ii ]); 268 269return model ; 270 } 271 }; 272Callbacks callbacks ; 273callbacks .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 ; 279loader .device = context -> device ; 280loader .loadFlags = loadFlags ; 281loader .scale = scale ; 282loader .callbacks = & callbacks ; 283Model * model = nullptr ; 284if (SLANG_FAILED (loader .load (inputPath , (void ** )& model ))) 285 { 286log ("failed to load '%s'\n" ,inputPath ); 287return nullptr ; 288 } 289 290return 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// 303virtual void writeTo (ShaderCursor const & cursor )= 0 ; 304 305// Retrieves the shader type for this light object. 306virtual 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{ 317auto program = context -> slangReflection ; 318auto shaderType = program -> findTypeByName (T ::getTypeName ()); 319return 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 331static const char * getTypeName () {return "DirectionalLight" ; } 332 333virtual void writeTo (ShaderCursor const & cursor )override 334 { 335cursor ["direction" ].setData (& direction ,sizeof (direction )); 336cursor ["intensity" ].setData (& intensity ,sizeof (intensity )); 337 } 338 339virtual slang::TypeReflection * getShaderType (RendererContext * context )override 340 { 341return ::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 350static const char * getTypeName () {return "PointLight" ; } 351 352virtual void writeTo (ShaderCursor const & cursor )override 353 { 354cursor ["position" ].setData (& position ,sizeof (position )); 355cursor ["intensity" ].setData (& intensity ,sizeof (intensity )); 356 } 357 358virtual slang::TypeReflection * getShaderType (RendererContext * context )override 359 { 360return ::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// 384struct LightArrayLayout :RefObject 385 { 386SlangInt 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 393void addLightType ( 394RendererContext * context , 395 slang::TypeReflection * lightType , 396SlangInt maximumCount ) 397 { 398SlangInt arrayIndex = (SlangInt )lightArrayLayouts .size (); 399LightArrayLayout layout ; 400layout .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// 408if (maximumCount <=1 ) 409 { 410layout .typeName = lightType -> getName (); 411 } 412else 413 { 414auto program = context -> slangReflection ; 415 std::stringstream typeNameBuilder ; 416typeNameBuilder <<"LightArray<" <<lightType -> getName () <<"," <<maximumCount <<">" ; 417layout .typeName = typeNameBuilder .str (); 418 } 419 420lightArrayLayouts .push_back (layout ); 421mapLightTypeToArrayIndex .insert (std::make_pair (lightType ,arrayIndex )); 422 } 423 424template < typename T > 425void addLightType (RendererContext * context ,SlangInt maximumCount ) 426 { 427addLightType (context ,getShaderType < T > (context ),maximumCount ); 428 } 429 430SlangInt getArrayIndexForType (slang::TypeReflection * lightType ) 431 { 432auto iter = mapLightTypeToArrayIndex .find (lightType ); 433if (iter != mapLightTypeToArrayIndex .end ()) 434return iter -> second ; 435 436return -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// 461RefPtr < LightEnvLayout > layout ; 462RendererContext * context ; 463LightEnv (RefPtr < LightEnvLayout > layout ,RendererContext * inContext ) 464 :layout (layout ),context (inContext ) 465 { 466for (auto arrayLayout :layout -> lightArrayLayouts ) 467 { 468RefPtr < LightArray > lightArray = new LightArray (); 469lightArray -> layout = arrayLayout ; 470lightArrays .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// 477struct LightArray :RefObject 478 { 479LightEnvLayout ::LightArrayLayout layout ; 480 std::vector < RefPtr < Light >> lights ; 481 }; 482 std::vector < RefPtr < LightArray >> lightArrays ; 483 484RefPtr < LightArray > getArrayForType (slang::TypeReflection * type ) 485 { 486auto index = layout -> getArrayIndexForType (type ); 487return lightArrays [index ]; 488 } 489 490void add (RefPtr < Light > light ) 491 { 492auto array = getArrayForType (light -> getShaderType (context )); 493array -> 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 ; 511auto arrayCount = layout -> lightArrayLayouts .size (); 512for (size_t ii = arrayCount ;ii -- ;) 513 { 514auto arrayInfo = layout -> lightArrayLayouts [ii ]; 515 516if (!currentEnvTypeName .size ()) 517 { 518// The is the right-most entry, so it is the base case for our "fold". 519currentEnvTypeName = arrayInfo .typeName ; 520 } 521else 522 { 523// Fold one entry: `envLayout = LightPair<a, envLayout>` 524 std::stringstream typeBuilder ; 525typeBuilder <<"LightPair<" <<arrayInfo .typeName <<"," <<currentEnvTypeName 526 <<">" ; 527currentEnvTypeName = typeBuilder .str (); 528 } 529 } 530 531if (!currentEnvTypeName .size ()) 532 { 533// Handle the special case of *zero* light types. 534currentEnvTypeName = "EmptyLightEnv" ; 535 } 536return 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// 544ComPtr < IShaderObject > createShaderObject () 545 { 546auto specializedType = getShaderType (); 547 548auto shaderObject = context -> device -> createShaderObject (specializedType ); 549ShaderCursor 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// 563size_t lightTypeCount = lightArrays .size (); 564for (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. 568auto lightTypeCursor = cursor ; 569if (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// 579lightTypeCursor = cursor ["first" ]; 580cursor = cursor ["second" ]; 581 } 582 583auto & lightTypeArray = lightArrays [tt ]; 584size_t lightCount = lightTypeArray -> lights .size (); 585size_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// 592if (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 599if (lightCount > 0 ) 600 { 601lightTypeArray -> lights [0 ]-> writeTo (lightTypeCursor ); 602 } 603else 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 } 610else 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// 615int32_t lightCount = int32_t (lightTypeArray -> lights .size ()); 616lightTypeCursor ["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// 623auto arrayCursor = lightTypeCursor ["lights" ]; 624for (int32_t ii = 0 ;ii < lightCount ;++ ii ) 625 { 626lightTypeArray -> lights [ii ]-> writeTo (arrayCursor [ii ]); 627 } 628 } 629 } 630return 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{ 644RendererContext 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 ; 650RefPtr < LightEnv > lightEnv ; 651 652// The pipeline state object we will use to draw models. 653ComPtr < IPipeline > gPipelineState ; 654 655// During startup the application will load one or more models and 656// add them to the `gModels` list. 657// 658void loadAndAddModel ( 659char const * inputPath , 660 platform::ModelLoader ::LoadFlags loadFlags = 0 , 661float scale = 1.0f ) 662 { 663auto model = loadModel (& context ,inputPath ,loadFlags ,scale ); 664if (!model ) 665return ; 666gModels .push_back (model ); 667 } 668 669// Our "simulation" state consists of just a few values. 670// 671uint64_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 679float translationScale = 0.5f ; 680float rotationScale = 0.025f ; 681 682// In order to control camera movement, we will 683// use good old WASD 684bool wPressed = false; 685bool aPressed = false; 686bool sPressed = false; 687bool dPressed = false; 688 689bool isMouseDown = false; 690float lastMouseX = 0.0f ; 691float lastMouseY = 0.0f ; 692 693void setKeyState (platform::KeyCode key ,bool state ) 694 { 695switch (key ) 696 { 697default : 698break ; 699case platform::KeyCode ::W : 700wPressed = state ; 701break ; 702case platform::KeyCode ::A : 703aPressed = state ; 704break ; 705case platform::KeyCode ::S : 706sPressed = state ; 707break ; 708case platform::KeyCode ::D : 709dPressed = state ; 710break ; 711 } 712 } 713void onKeyDown (platform::KeyEventArgs args ) {setKeyState (args .key , true); } 714void onKeyUp (platform::KeyEventArgs args ) {setKeyState (args .key , false); } 715 716void onMouseDown (platform::MouseEventArgs args ) 717 { 718isMouseDown = true; 719lastMouseX = (float )args .x ; 720lastMouseY = (float )args .y ; 721 } 722 723void onMouseMove (platform::MouseEventArgs args ) 724 { 725if (isMouseDown ) 726 { 727float deltaX = args .x - lastMouseX ; 728float deltaY = args .y - lastMouseY ; 729 730cameraOrientation = 731 glm::rotate (cameraOrientation ,- deltaX * rotationScale , glm::vec3 (0 ,1 ,0 )); 732cameraOrientation = 733 glm::rotate (cameraOrientation ,- deltaY * rotationScale , glm::vec3 (1 ,0 ,0 )); 734 735cameraOrientation = normalize (cameraOrientation ); 736 737lastMouseX = (float )args .x ; 738lastMouseY = (float )args .y ; 739 } 740 } 741void 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// 749Result initialize () 750 { 751SLANG_RETURN_ON_FAIL (initializeBase ("Model Viewer" ,1024 ,768 ,getDeviceType ())); 752if (!isTestMode ()) 753 { 754gWindow -> events .mouseMove = [this ](const platform::MouseEventArgs & e ) 755 {onMouseMove (e ); }; 756gWindow -> events .mouseUp = [this ](const platform::MouseEventArgs & e ) {onMouseUp (e ); }; 757gWindow -> events .mouseDown = [this ](const platform::MouseEventArgs & e ) 758 {onMouseDown (e ); }; 759gWindow -> events .keyDown = [this ](const platform::KeyEventArgs & e ) {onKeyDown (e ); }; 760gWindow -> events .keyUp = [this ](const platform::KeyEventArgs & e ) {onKeyUp (e ); }; 761 } 762 763// Initialize `RendererContext`, which loads the shader module from file. 764SLANG_RETURN_ON_FAIL (context .init (gDevice ,this )); 765 766 767InputElementDesc 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 }; 772auto inputLayout = gDevice -> createInputLayout (sizeof (Model ::Vertex ),& inputElements [0 ],3 ); 773if (!inputLayout ) 774return SLANG_FAIL ; 775 776// Create the pipeline state object for drawing models. 777RenderPipelineDesc pipelineStateDesc = {}; 778pipelineStateDesc .program = context .shaderProgram ; 779pipelineStateDesc .inputLayout = inputLayout ; 780pipelineStateDesc .primitiveTopology = PrimitiveTopology ::TriangleList ; 781pipelineStateDesc .depthStencil .depthFunc = ComparisonFunc ::LessEqual ; 782pipelineStateDesc .depthStencil .depthTestEnable = true; 783// Set up color target 784ColorTargetDesc colorTarget = {}; 785colorTarget .format = Format ::RGBA8Unorm ; 786pipelineStateDesc .targetCount = 1 ; 787pipelineStateDesc .targets = & colorTarget ; 788gPipelineState = 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// 794RefPtr < LightEnvLayout > lightEnvLayout = new LightEnvLayout (); 795lightEnvLayout -> addLightType < PointLight > (& context ,10 ); 796lightEnvLayout -> addLightType < DirectionalLight > (& context ,2 ); 797 798lightEnv = new LightEnv (lightEnvLayout ,& context ); 799 800RefPtr < PointLight > pointLight = new PointLight (); 801pointLight -> position = glm::vec3 (5 ,3 ,1 ); 802pointLight -> intensity = glm::vec3 (10 ); 803lightEnv -> 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// 813Slang ::String path = resourceBase .resolveResource ("cube.obj" ).getBuffer (); 814loadAndAddModel (path .getBuffer ()); 815 816return 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// 823void 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// 828if (!lastTime ) 829lastTime = getCurrentTime (); 830uint64_t currentTime = getCurrentTime (); 831float deltaTime = float (double (currentTime - lastTime ) /double (getTimerFrequency ())); 832lastTime = 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 {}; 840if (isTestMode ()) 841 { 842clientRect .width = 1024 ; 843clientRect .height = 768 ; 844 } 845else 846 { 847clientRect = getWindow ()-> getClientRect (); 848 } 849if (clientRect .height == 0 ) 850return ; 851 glm::mat4x4 projection = glm::perspectiveRH_ZO ( 852 glm::radians (60.0f ), 853float (clientRect .width ) /float (clientRect .height ), 8540.1f , 8551000.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 ); 865if (wPressed ) 866movement += forward ; 867if (sPressed ) 868movement -= forward ; 869if (aPressed ) 870movement -= right ; 871if (dPressed ) 872movement += right ; 873 874cameraPosition += deltaTime * translationScale * movement ; 875 876 glm::mat4x4 view = identity ; 877view *= glm::mat4x4 (inverse (cameraOrientation )); 878view = glm::translate (view ,- cameraPosition ); 879 880 glm::mat4x4 viewProjection = projection * view ; 881auto deviceInfo = gDevice -> getInfo (); 882// Use identity matrix for correction 883static const float kIdentity []= {1 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,0 ,1 }; 884 glm::mat4x4 correctionMatrix ; 885memcpy (& correctionMatrix ,kIdentity ,sizeof (float )* 16 ); 886viewProjection = correctionMatrix * viewProjection ; 887// glm uses column-major layout, we need to translate it to row-major. 888viewProjection = glm::transpose (viewProjection ); 889 890auto drawCommandEncoder = gQueue -> createCommandEncoder (); 891 892ComPtr < ITextureView > textureView = gDevice -> createTextureView (texture , {}); 893RenderPassColorAttachment colorAttachment = {}; 894colorAttachment .view = textureView ; 895colorAttachment .loadOp = LoadOp ::Clear ; 896 897RenderPassDesc renderPass = {}; 898renderPass .colorAttachments = & colorAttachment ; 899renderPass .colorAttachmentCount = 1 ; 900 901auto renderEncoder = drawCommandEncoder -> beginRenderPass (renderPass ); 902 903RenderState renderState = {}; 904renderState .viewports [0 ]= 905Viewport ::fromSize ((float )clientRect .width , (float )clientRect .height ); 906renderState .viewportCount = 1 ; 907renderState .scissorRects [0 ]= 908ScissorRect ::fromSize ((float )clientRect .width , (float )clientRect .height ); 909renderState .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 915auto viewShaderObject = gDevice -> createShaderObject (context .perViewShaderType ); 916 { 917ShaderCursor cursor (viewShaderObject ); 918cursor ["viewProjection" ].setData (& viewProjection ,sizeof (viewProjection )); 919cursor ["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// 924for (auto & model :gModels ) 925 { 926renderState .vertexBuffers [0 ]= model -> vertexBuffer ; 927renderState .vertexBufferCount = 1 ; 928renderState .indexBuffer = model -> indexBuffer ; 929renderState .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 )); 936auto modelShaderObject = gDevice -> createShaderObject (context .perModelShaderType ); 937 { 938ShaderCursor cursor (modelShaderObject ); 939cursor ["modelTransform" ].setData (& modelTransform ,sizeof (modelTransform )); 940cursor ["inverseTransposeModelTransform" ].setData ( 941& inverseTransposeModelTransform , 942sizeof (inverseTransposeModelTransform )); 943 } 944 945auto 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// 954for (auto & mesh :model -> meshes ) 955 { 956// Set the pipeline and binding state for drawing each mesh. 957auto rootObject = renderEncoder -> bindPipeline ( 958static_cast < IRenderPipeline *> (gPipelineState .get ())); 959 960// Apply render state 961renderEncoder -> setRenderState (renderState ); 962 963ShaderCursor rootCursor (rootObject ); 964rootCursor ["gViewParams" ].setObject (viewShaderObject ); 965rootCursor ["gModelParams" ].setObject (modelShaderObject ); 966rootCursor ["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// 979rootCursor ["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. 983DrawArguments drawArgs = {}; 984// `drawArgs.vertexCount` is actually `indexCount` for the `DrawIndexed` Graphics 985// API 986drawArgs .vertexCount = mesh -> indexCount ; 987drawArgs .startIndexLocation = mesh -> firstIndex ; 988renderEncoder -> drawIndexed (drawArgs ); 989 } 990 } 991renderEncoder -> end (); 992gQueue -> submit (drawCommandEncoder -> finish ()); 993 994if (!isTestMode ()) 995 { 996gSurface -> present (); 997 } 998 } 999}; 1000 1001// This macro instantiates an appropriate main function to 1002// run the application defined above. 1003EXAMPLE_MAIN (innerMain < ModelViewer > );