yum-mirror/slang

Making it easier to work with shaders

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

Matthew MoultonImprove documentation and example formatting consistency (#4299)78d34f3b3

master
12.6 KiB348 linesraw
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}