yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
13dd01489
master
1// main.cpp 2 3// This file provides the application code for the `shader-toy` example. 4// 5// Much of the logic here is identical to the simpler `hello-world` example, 6// so we will not spend time commenting those parts that are identical or 7// nearly identical. Readers who want detailed comments on a simpler example 8// using Slang should look there. 9 10// This example uses the Slang C/C++ API, alonmg with its optional type 11// for managing COM-style reference-counted pointers. 12// 13#include "slang-com-ptr.h" 14#include "slang.h" 15using Slang ::ComPtr ; 16 17// This example uses a graphics API abstraction layer that is implemented inside 18// the Slang codebase for use in our sample programs and test cases. Use of 19// this layer is *not* required or assumed when using the Slang language, 20// compiler, and API. 21// 22#include "core/slang-basic.h" 23#include "examples/example-base/example-base.h" 24#include "platform/performance-counter.h" 25#include "platform/window.h" 26#include "slang-rhi.h" 27#include "slang-rhi/shader-cursor.h" 28 29#include <chrono> 30 31static const ExampleResources resourceBase ("shader-toy" ); 32 33using namespace rhi ; 34 35// In order to display a shader toy effect using rasterization-based shader 36// execution we need to render a full-screen triangle. We will define a 37// small helper type that defines the data for such a triangle. 38// 39struct FullScreenTriangle 40{ 41struct Vertex 42 { 43float position [2 ]; 44 }; 45 46enum 47 { 48kVertexCount = 3 49 }; 50 51static const Vertex kVertices [kVertexCount ]; 52}; 53const FullScreenTriangle ::Vertex FullScreenTriangle ::kVertices [FullScreenTriangle ::kVertexCount ]= { 54 {{-1 ,-1 }}, 55 {{-1 ,3 }}, 56 {{3 ,-1 }}, 57}; 58 59// The application itself will be encapsulated in a C++ `struct` type 60// so that it can easily scope its state without use of global variables. 61// 62struct ShaderToyApp :public WindowedAppBase 63{ 64 65// The uniform data used by the shader is defined here as a simple 66// POD ("plain old data") type. 67// 68// Note: This type must match the declaration of `ShaderToyUniforms` 69// in the file `shader-toy.slang`. 70// 71// An application could instead use a shared header file to define 72// this type, or use Slang's reflection capabilities to allocate 73// and set parameters at runtime. For this simple example we did 74// the expedient thing of having distinct Slang and C++ declarations. 75// 76struct Uniforms 77 { 78float iMouse [4 ]; 79float iResolution [2 ]; 80float iTime ; 81 }; 82 83// The main interesting part of the host application code is where we 84// load, compile, inspect, and compose the Slang shader code. 85// 86Result loadShaderProgram (IDevice * device ,ComPtr < IShaderProgram >& outShaderProgram ) 87 { 88// We need to obatin a compilation session (`slang::ISession`) that will provide 89// a scope to all the compilation and loading of code we do. 90// 91// Our example application uses the `slang-rhi` graphics API abstraction layer, which 92// already creates a Slang compilation session for us, so we just grab and use it here. 93ComPtr < slang::ISession > slangSession ; 94slangSession = device -> getSlangSession (); 95 96// Once the session has been obtained, we can start loading code into it. 97// 98// The simplest way to load code is by calling `loadModule` with the name of a Slang 99// module. A call to `loadModule("MyStuff")` will behave more or less as if you 100// wrote: 101// 102// import MyStuff; 103// 104// In a Slang shader file. The compiler will use its search paths to try to locate 105// `MyModule.slang`, then compile and load that file. If a matching module had 106// already been loaded previously, that would be used directly. 107// 108// Note: The only interesting wrinkle here is that our file is named `shader-toy` with 109// a hyphen in it, so the name is not directly usable as an identifier in Slang code. 110// Instead, when trying to import this module in the context of Slang code, a user 111// needs to replace the hyphens with underscores: 112// 113// import shader_toy; 114// 115ComPtr < slang::IBlob > diagnosticsBlob ; 116Slang ::String shaderToyPath = resourceBase .resolveResource ("shader-toy.slang" ); 117 slang::IModule * module = 118slangSession -> loadModule (shaderToyPath .getBuffer (),diagnosticsBlob .writeRef ()); 119diagnoseIfNeeded (diagnosticsBlob ); 120if (!module ) 121return SLANG_FAIL ; 122 123// Loading the `shader-toy` module will compile and check all the shader code in it, 124// including the shader entry points we want to use. Now that the module is loaded 125// we can look up those entry points by name. 126// 127// Note: If you are using this `loadModule` approach to load your shader code it is 128// important to tag your entry point functions with the `[shader("...")]` attribute 129// (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there 130// is no umambiguous way for the compiler to know which functions represent entry 131// points when it parses your code via `loadModule()`. 132// 133char const * vertexEntryPointName = "vertexMain" ; 134char const * fragmentEntryPointName = "fragmentMain" ; 135// 136ComPtr < slang::IEntryPoint > vertexEntryPoint ; 137SLANG_RETURN_ON_FAIL ( 138module -> findEntryPointByName (vertexEntryPointName ,vertexEntryPoint .writeRef ())); 139// 140ComPtr < slang::IEntryPoint > fragmentEntryPoint ; 141SLANG_RETURN_ON_FAIL ( 142module -> findEntryPointByName (fragmentEntryPointName ,fragmentEntryPoint .writeRef ())); 143 144// At this point we have a few different Slang API objects that represent 145// pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. 146// 147// A single Slang module could contain many different entry points (e.g., 148// four vertex entry points, three fragment entry points, and two compute 149// shaders), and before we try to generate output code for our target API 150// we need to identify which entry points we plan to use together. 151// 152// Modules and entry points are both examples of *component types* in the 153// Slang API. The API also provides a way to build a *composite* out of 154// other pieces, and that is what we are going to do with our module 155// and entry points. 156// 157Slang ::List < slang::IComponentType *> componentTypes ; 158componentTypes .add (module ); 159 160// Later on when we go to extract compiled kernel code for our vertex 161// and fragment shaders, we will need to make use of their order within 162// the composition, so we will record the relative ordering of the entry 163// points here as we add them. 164int entryPointCount = 0 ; 165int vertexEntryPointIndex = entryPointCount ++ ; 166componentTypes .add (vertexEntryPoint ); 167 168int fragmentEntryPointIndex = entryPointCount ++ ; 169componentTypes .add (fragmentEntryPoint ); 170 171// Actually creating the composite component type is a single operation 172// on the Slang session, but the operation could potentially fail if 173// something about the composite was invalid (e.g., you are trying to 174// combine multiple copies of the same module), so we need to deal 175// with the possibility of diagnostic output. 176// 177ComPtr < slang::IComponentType > composedProgram ; 178SlangResult result = slangSession -> createCompositeComponentType ( 179componentTypes .getBuffer (), 180componentTypes .getCount (), 181composedProgram .writeRef (), 182diagnosticsBlob .writeRef ()); 183diagnoseIfNeeded (diagnosticsBlob ); 184SLANG_RETURN_ON_FAIL (result ); 185 186// At this point, `composedProgram` represents the shader program 187// we want to run, and the vertex and fragment shader there have 188// been checked. 189// 190// We could use the Slang reflection API on `composedProgram` at this 191// point to query things like the locations and offsets of the 192// various uniform parameters, textures, etc. 193// 194// What *cannot* be done yet at this point is actually generating 195// kernel code, because `composedProgram` includes a generic type 196// parameter as part of the `fragmentMain` entry point: 197// 198// void fragmentMain<T : IShaderToyImageShader>(...) 199// 200// Our next task is to load code for a type we'd like to plug in 201// for `T` there. 202// 203// Because Slang supports modular programming, there is no requirement 204// that a type we want to plug in for `T` has to come from the 205// same module, and to demonstrate that we will load a different 206// module to provide the effect type we will plug in. 207// 208const char * effectTypeName = "ExampleEffect" ; 209Slang ::String effectModulePath = resourceBase .resolveResource ("example-effect.slang" ); 210 slang::IModule * effectModule = 211slangSession -> loadModule (effectModulePath .getBuffer (),diagnosticsBlob .writeRef ()); 212diagnoseIfNeeded (diagnosticsBlob ); 213if (!module ) 214return SLANG_FAIL ; 215 216// Once we've loaded the code module that defines out effect type, 217// we can look it up by name using the reflection information on 218// the module. 219// 220// Note: A future version of the Slang API will support enumerating 221// the types declared in a module so that we do not have to hard-code 222// the name here. 223// 224auto effectType = effectModule -> getLayout ()-> findTypeByName (effectTypeName ); 225 226// Now that we have the `effectType` we want to plug in to our generic 227// shader, we need to specialize the shader to that type. 228// 229// Because a shader program could have zero or more specialization parameters, 230// we need to build up an array of specialization arguments. 231// 232Slang ::List < slang::SpecializationArg > specializationArgs ; 233 234 { 235// In our case, we only have a single specialization argument we plan 236// to use, and it is a type argument. 237// 238 slang::SpecializationArg effectTypeArg ; 239effectTypeArg .kind = slang::SpecializationArg ::Kind ::Type ; 240effectTypeArg .type = effectType ; 241specializationArgs .add (effectTypeArg ); 242 } 243 244// Specialization of a component type is a single Slang API call, but 245// we need to deal with the possibility of diagnostic output on failure. 246// For example, if we tried to specialize the shader program to a 247// type like `int` that doesn't support the `IShaderToyImageShader` interface, 248// this is the step where we'd get an error message saying so. 249// 250ComPtr < slang::IComponentType > specializedProgram ; 251result = composedProgram -> specialize ( 252specializationArgs .getBuffer (), 253specializationArgs .getCount (), 254specializedProgram .writeRef (), 255diagnosticsBlob .writeRef ()); 256diagnoseIfNeeded (diagnosticsBlob ); 257SLANG_RETURN_ON_FAIL (result ); 258 259// At this point we have a specialized shader program that represents our 260// intention to run the `vertexMain` and `fragmentMain` entry points, 261// specialized to the `ExampleEffect` type we loaded. 262// 263// We can now *link* the program, which ensures that all of the code that 264// it transitively depends on has been pulled together into a single 265// component type. 266// 267ComPtr < slang::IComponentType > linkedProgram ; 268result = specializedProgram -> link (linkedProgram .writeRef (),diagnosticsBlob .writeRef ()); 269diagnoseIfNeeded (diagnosticsBlob ); 270SLANG_RETURN_ON_FAIL (result ); 271 272ShaderProgramDesc programDesc = {}; 273programDesc .slangGlobalScope = linkedProgram .get (); 274auto shaderProgram = device -> createShaderProgram (programDesc ); 275outShaderProgram = shaderProgram ; 276return SLANG_OK ; 277 } 278 279ComPtr < IShaderProgram > gShaderProgram ; 280ComPtr < IPipeline > gPipeline ; 281ComPtr < IBuffer > gVertexBuffer ; 282const Format format = Format ::RG32Float ; 283 284Result initialize () 285 { 286SLANG_RETURN_ON_FAIL (initializeBase ("Shader Toy" ,1024 ,768 ,getDeviceType ())); 287 288// We may not have a window if we're running in test mode 289SLANG_ASSERT (isTestMode ()|| gWindow ); 290if (gWindow ) 291 { 292gWindow -> events .mouseMove = [this ](const platform::MouseEventArgs & e ) 293 {handleEvent (e ); }; 294gWindow -> events .mouseUp = [this ](const platform::MouseEventArgs & e ) {handleEvent (e ); }; 295gWindow -> events .mouseDown = [this ](const platform::MouseEventArgs & e ) 296 {handleEvent (e ); }; 297 } 298 299InputElementDesc inputElements []= { 300 {"POSITION" ,0 ,format , offsetof(FullScreenTriangle ::Vertex ,position )}, 301 }; 302auto inputLayout = gDevice -> createInputLayout ( 303sizeof (FullScreenTriangle ::Vertex ), 304& inputElements [0 ], 305SLANG_COUNT_OF (inputElements )); 306if (!inputLayout ) 307return SLANG_FAIL ; 308 309BufferDesc vertexBufferDesc ; 310vertexBufferDesc .size = 311FullScreenTriangle ::kVertexCount * sizeof (FullScreenTriangle ::Vertex ); 312vertexBufferDesc .elementSize = sizeof (FullScreenTriangle ::Vertex ); 313vertexBufferDesc .usage = BufferUsage ::VertexBuffer ; 314gVertexBuffer = gDevice -> createBuffer (vertexBufferDesc ,& FullScreenTriangle ::kVertices [0 ]); 315if (!gVertexBuffer ) 316return SLANG_FAIL ; 317 318SLANG_RETURN_ON_FAIL (loadShaderProgram (gDevice ,gShaderProgram )); 319 320// Create pipeline. 321ColorTargetDesc colorTarget ; 322colorTarget .format = Format ::RGBA8Unorm ; 323RenderPipelineDesc desc ; 324desc .inputLayout = inputLayout ; 325desc .program = gShaderProgram ; 326desc .targetCount = 1 ; 327desc .targets = & colorTarget ; 328desc .depthStencil .depthTestEnable = false; 329desc .depthStencil .depthWriteEnable = false; 330desc .primitiveTopology = PrimitiveTopology ::TriangleList ; 331gPipeline = gDevice -> createRenderPipeline (desc ); 332if (!gPipeline ) 333return SLANG_FAIL ; 334 335return SLANG_OK ; 336 } 337 338bool wasMouseDown = false; 339bool isMouseDown = false; 340float lastMouseX = 0.0f ; 341float lastMouseY = 0.0f ; 342float clickMouseX = 0.0f ; 343float clickMouseY = 0.0f ; 344 345bool firstTime = true; 346 platform::TimePoint startTime ; 347 348virtual void renderFrame (ITexture * texture )override 349 { 350auto commandEncoder = gQueue -> createCommandEncoder (); 351if (firstTime ) 352 { 353startTime = platform::PerformanceCounter ::now (); 354firstTime = false; 355 } 356 357// Update uniform buffer. 358 359Uniforms uniforms = {}; 360 { 361bool isMouseClick = isMouseDown && !wasMouseDown ; 362wasMouseDown = isMouseDown ; 363 364if (isMouseClick ) 365 { 366clickMouseX = lastMouseX ; 367clickMouseY = lastMouseY ; 368 } 369 370uniforms .iMouse [0 ]= lastMouseX ; 371uniforms .iMouse [1 ]= lastMouseY ; 372uniforms .iMouse [2 ]= isMouseDown ?clickMouseX :- clickMouseX ; 373uniforms .iMouse [3 ]= isMouseClick ?clickMouseY :- clickMouseY ; 374uniforms .iTime = platform::PerformanceCounter ::getElapsedTimeInSeconds (startTime ); 375uniforms .iResolution [0 ]= float (windowWidth ); 376uniforms .iResolution [1 ]= float (windowHeight ); 377 } 378 379// Encode render commands. 380ComPtr < ITextureView > textureView = gDevice -> createTextureView (texture , {}); 381RenderPassColorAttachment colorAttachment = {}; 382colorAttachment .view = textureView ; 383colorAttachment .loadOp = LoadOp ::Clear ; 384 385RenderPassDesc renderPass = {}; 386renderPass .colorAttachments = & colorAttachment ; 387renderPass .colorAttachmentCount = 1 ; 388 389auto encoder = commandEncoder -> beginRenderPass (renderPass ); 390 391RenderState renderState = {}; 392renderState .viewports [0 ]= Viewport ::fromSize (windowWidth ,windowHeight ); 393renderState .viewportCount = 1 ; 394renderState .scissorRects [0 ]= ScissorRect ::fromSize (windowWidth ,windowHeight ); 395renderState .scissorRectCount = 1 ; 396 397auto rootObject = encoder -> bindPipeline (static_cast < IRenderPipeline *> (gPipeline .get ())); 398auto constantBuffer = rootObject -> getObject (ShaderOffset ()); 399constantBuffer -> setData (ShaderOffset (),& uniforms ,sizeof (uniforms )); 400 401renderState .vertexBuffers [0 ]= gVertexBuffer ; 402renderState .vertexBufferCount = 1 ; 403encoder -> setRenderState (renderState ); 404 405DrawArguments drawArgs = {}; 406drawArgs .vertexCount = 3 ; 407encoder -> draw (drawArgs ); 408 409encoder -> end (); 410 411gQueue -> submit (commandEncoder -> finish ()); 412 413if (!isTestMode ()) 414 { 415gSurface -> present (); 416 } 417 } 418 419void handleEvent (const platform::MouseEventArgs & event ) 420 { 421isMouseDown = ((int )event .buttons & (int )platform::ButtonState ::Enum ::LeftButton )!= 0 ; 422lastMouseX = (float )event .x ; 423lastMouseY = (float )event .y ; 424 } 425}; 426 427// This macro instantiates an appropriate main function to 428// run the application defined above. 429EXAMPLE_MAIN (innerMain < ShaderToyApp > );