yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
78d34f3b3
master
1// shader-toy.slang 2 3// This file implements the core of a system for executing 4// code from shadertoy.com in the context of Slang. 5// 6// The big idea here is to define an interface so that 7// different shader toy effects can be defined as 8// separately compiled modules, and then "plug in" to 9// execution environment for running those effects, wheter 10// via vertex/fragment shaders, compute, or on CPU. 11// 12// An important goal is that we should be able to run effects 13// defined on shadertoy.com with as little modification as 14// possible. This goal isn't 100% achievable because shader 15// toy effects are authored in GLSL, which differs from Slang 16// in several ways, so this is an aspirational goal rather 17// than a requirement. 18// 19// There are a few different kinds of effects supported 20// by shader toy, which are enumerated on the [How To](https://www.shadertoy.com/howto) 21// page of the project. This module focuses only on 22// "image shaders," which are by far the most common. 23// 24// We will start with the interface that all image shaders 25// are expected to implement. 26// 27interface IShaderToyImageShader 28{ 29 // The shader toy "How To" page says: 30 // 31 // > Image shaders implement the `mainImage()` function in order 32 // > to generate procedural images by computing a color for 33 // > each pixel. ... 34 // 35 // The GLSL signature given is: 36 // 37 // > void mainImage( out vec4 fragColor, in vec2 fragCoord ); 38 // 39 // We can translate that signature almost verbatim into Slang: 40 // 41 void mainImage( out float4 fragColor, in float2 fragCoord ); 42 43 // An image shader effect will thus be a Slang `struct` type 44 // that implements the `IShaderToyImageShader` interface. 45 // In most cases, effects will be created by pasting the 46 // GLSL content of an effect into boilerplate `struct` 47 // definition. 48 // 49 // As a result of scoping the GLSL effect code in a Slang 50 // `struct`, any functions declared in the effect will become 51 // methods of the struct, and any global-scope variables declared 52 // in the effect will become members of the `struct` type. 53 // 54 // Note: One caveat that arises here is that any effect that 55 // makes use of mutable global variables in its GLSL code will 56 // fail to compile with our approach. By default methods in 57 // Slang have can only read from `this` and its members, and 58 // need to be marked a `[mutating]` to have read-write access. 59 // Most shader toy effects only use globals to declare constants, 60 // so this limitation may not be a big problem in practice. 61 // 62 // One thing that *does* matter is that global variables/constants in 63 // an effect may have initializers, and the behavior of the 64 // effect is likely to depend on them being initialized correctly. 65 // 66 // In practice, the need to have effect-specific initialization 67 // is another requirement of the image shader interface that doesn't 68 // need to be explicitly explained on shadertoy.com, but does matter 69 // when writing an explicit interface in Slang. 70 // 71 // We express the requirement using a `static` method that 72 // returns the `This` type. Much like `this` refers to the 73 // "current object" with whatever type it might have at runtime, 74 // the `This` type refers to the "current type" that is implementing 75 // this interface. Static methods that return `This` allow 76 // Slang to express required "factory" functions in an interface. 77 // 78 static This getDefault(); 79}; 80 81// Now that we have defined the interface that all image 82// shader effects are expected to implement, we can define 83// a vertex and fragment shader that can be used to evaluate 84// any effect that conforms to the interface. 85// 86// The vertex shader is just going to implement a full-screen 87// triangle, so it is almost trivial: 88// 89[shader("vertex")] 90float4 vertexMain(float2 position : POSITION) 91 : SV_Position 92{ 93 // TODO: We could even turn this into a shader that 94 // takes no inputs, and directly computes the XY 95 // location of each vertex based on `SV_VertexID` 96 97 return float4(position, 0.5, 1.0); 98} 99// 100// The body of the effect will run in the fragment shader, 101// so that is where things get more interesting. 102// 103// We will define our fragment shader entry point as a 104// Slang *generic function*, with a generic type parameter 105// `T` that is constrained to implement the `IShaderToyImageShader` 106// interface. 107// 108[shader("fragment")] 109float4 fragmentMain<T : IShaderToyImageShader>( 110 float4 sv_position : SV_Position) 111 : SV_Target 112{ 113 // Because the Slang compiler knows the interface that `T` 114 // is expected to implement, any code in the function 115 // body will be checked to make sure that it does not 116 // use operations that `T` would not be guaranteed to 117 // support. Unlike with traditional C++ templates or 118 // preprocessor-based shader specialization, it is possible 119 // to copmile and type-check this entry point once, 120 // and use it with multiple different types for `T`. 121 122 // We start by creating an instance of the effect 123 // type `T`, initialized to whatever its default 124 // values are. 125 // 126 // Note: initializing the effect here preserves the 127 // semantics of the original shader toy code. An 128 // alternative approach (that would change the behavior 129 // from the original) would be to pass a value of 130 // type `T` in as a shader parameter of the entry point. 131 // 132 T toy = T.getDefault(); 133 134 // Next, we invoke the user-defined effect by calling 135 // its `mainImage` function. 136 // 137 // Recall that the `fragColor` parameter to `mainImage` 138 // was defined as an `out` parameter, so this call 139 // will set a value into our local `fragColor` variable. 140 // 141 float2 fragCoord = sv_position.xy; 142 float4 fragColor = 0; 143 toy.mainImage(fragColor, fragCoord); 144 145 // The output value from our shader is simply the result 146 // from the user-defined effect. 147 // 148 return float4(fragColor.xyz, 1); 149} 150 151// By defining an interface for image shader effects, we 152// have been able to decouple the code for the effects 153// themselves from the code for their execution contexts. 154// A key benefit of that decoupling is that we can introduce 155// both new effects and new execution contexts in a modular 156// fashion. 157// 158// For example, we can easily define a compute shader for 159// executing an image shader effect: 160// 161[shader("compute")] 162void computeMain<T : IShaderToyImageShader>( 163 uint3 sv_dispatchThreadID : SV_DispatchThreadID, 164 uniform RWTexture2D<float4> image) 165{ 166 // The operations required to set up and execute 167 // the user-defined effect are similar to what 168 // they were for the fragment shader. 169 // 170 T toy = T.getDefault(); 171 172 float2 fragCoord = float2(sv_dispatchThreadID.xy); 173 float4 fragColor = 0; 174 toy.mainImage(fragColor, fragCoord); 175 176 // The main difference is that we now write the 177 // output color explicitly to an image pixel 178 // instead of relying on the rasterization pipeline. 179 // 180 image[sv_dispatchThreadID.xy] = fragColor; 181} 182 183// At this point we've described how our module will 184// execute shader toy effects that implement the 185// required interface, but we also need to set up 186// the services that those effects are able to use. 187// 188// The shader toy "How To" file describes a large number of uniform 189// shader parameters that are implicitly visible to every effect. 190// 191// If we were able to design an interface from scratch, we might 192// prefer to make the `mainImage` function take some kind of 193// explicit context parameter that provides access to these 194// values, but because our goal is to be compatible with existing 195// effects with their established `mainImage` signature, we will 196// instead define these parameters using old-fashioned global-scope 197// shader parameters. 198// 199cbuffer ShaderToyUniforms 200{ 201 // Note: We do not currently define all of the parameters 202 // exposed by Shader Toy, but rather just the most commonly 203 // used ones. 204 // 205 // TODO: We can and should fill in the rest over time. 206 // 207 float4 iMouse; 208 float2 iResolution; 209 float iTime; 210}; 211 212// In addition to the above parameters that use ordinary data types, 213// shader toy also exposes the `iChannel*` parameters (`iChannel0` 214// through `iChannel4`). These parameters represent sampled image 215// inputs that can be bound to selected images as part of an effect. 216// 217// Traditional GLSL "sampler" types include both the texture image 218// and sampler state, while Slang (like D3D, Vulkan, etc.) has 219// distinct texture and sampler types. In order to define the 220// channel variables in a way that is compatible with shader toy, 221// we will define a `struct` type for a pair of a texture and 222// a sampler: 223// 224struct TextureSamplerPair 225{ 226 Texture2D t; 227 SamplerState s; 228}; 229 230// With our texture-sampler pair type defined, we can introduce 231// the variables for the texture channels easily. 232// 233TextureSamplerPair iChannel0; 234TextureSamplerPair iChannel1; 235TextureSamplerPair iChannel2; 236TextureSamplerPair iChannel3; 237 238 239// TODO: Shader toy supports more than just 2D textures, so a good 240// avenue for extension of the module would be to define an interface 241// for the texture channels, and have implementations using various 242// forms of textures. 243// 244// A really ambitious idea would be include one example of the 245// texture-channel interface that uses an existing ShaderToy as a 246// procedural texture. 247 248// Shader toy effects access the contents of the `iChannel*` variables 249// using the `texture()` function, so we need to provide a definition 250// that is suitable: 251// 252float4 texture(TextureSamplerPair p, float3 uvw) 253{ 254 // TODO: The right implementation to use here (at least 255 // in the context of fragment shaders) is: 256 // 257 // return p.t.Sample(p.s, uvw.xy); 258 // 259 // However, the current implementation of the main 260 // application code doesn't include texture image 261 // loading, so we will instead just fill in a 262 // placeholder result for "texture" lookup: 263 // 264 return 0.5; 265} 266 267// The last major issue we need to address in this module is the way that 268// shader toy effects are authored in GLSL, which has several differences 269// from Slang that could cause problems. 270// 271// Some of these differences can be surmounted relatively easily. For 272// example, GLSL uses different names for its built-in vector types, but 273// for the most part they are compatible with those defined by HLSL/Slang. 274// We can paper over this difference by defining a few helpful type 275// aliases. 276// 277typealias vec2 = float2; 278typealias vec3 = float3; 279typealias vec4 = float4; 280 281// Matrix types in GLSL are a more subtle issue, because they have different 282// semantics from their HLSL/Slang equivalents in a few key ways: 283// 284// * The infix `*` operator always performs component-wise multiplication 285// in HLSL/Slang, but in GLSL it sometimes performs linear-algebraic 286// products (whenever we have matrix*matrix, vector*matrix, or matrix*vector). 287// HLSL/Slang require a distinct `mul()` function for those cases. 288// 289// * Because of differences in terminology and conventions, a linear-algebraic 290// product like `M*v` in GLSL is equivalent to `mul(v, M)` in HLSL/Slang 291// (note the reversed order of operands). 292// 293// * Constructing a matrix or vector from a single scalar consistently 294// replicates that scalar across all components/elements in HLSL/Slang, 295// but in GLSL it instead produces a diagonal matrix. 296// 297// These differences are not something we can surmount by defining the 298// GLSL matrix types as aliases of the standard Slang ones, so instead we 299// must define the GLSL matrix types as wrappers around the Slang ones. 300// 301struct mat2 302{ 303 float2x2 data; 304 305 __init(float e00, float e01, float e10, float e11) 306 { 307 data = float2x2(e00, e01, e10, e11); 308 } 309 310 // TODO: We need to fill in the other intializers and members 311 // available on matrices here. 312}; 313 314// TODO: fill in `mat3` and `mat4`. 315 316// TODO: Ideally we would want to define overloaded operation functions 317// to allow `*`, `*=`, etc. to apply to our user-space matrix types. 318// 319// Unfortunately, implementation bugs in the Slang compiler mean that 320// user-defined operator overloads aren't working right now. 321// 322// vec2 operator*(vec2 left, mat2 right) 323// { 324// return mul(right.data, left); 325// } 326// 327// void operator*=(inout vec2 left, mat2 right) 328// { 329// left = mul(right.data, left); 330// } 331// 332// Instead, we will define an ordinary function for the one case that 333// we've run into in a test effect so far: 334// 335void mulAssign(inout vec2 left, mat2 right) 336{ 337 left = mul(right.data, left); 338} 339 340float fract(float value) 341{ 342 return frac(value); 343} 344 345float mix(float a, float b, float t) 346{ 347 return lerp(a, b, t); 348}