summaryrefslogtreecommitdiffstats
path: root/source/slang/ir-entry-point-uniforms.cpp
blob: 7bbf58810ec54a03d878d966b57abfab1cd01e67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
// ir-entry-point-uniforms.cpp
#include "ir-entry-point-uniforms.h"

#include "ir.h"
#include "ir-insts.h"

#include "mangle.h"

namespace Slang
{


// The transformation in this file will solve the problem of taking
// code like the following:
//
//      float4 fragmentMain(
//          uniform Texture2D    t,
//          uniform SamplerState s;
//          uniform float4       c,
//                  float2       uv : UV) : SV_Target
//      {
//          return t.Sample(s, uv) + c;
//      }
//
// and transforming into code like this:
//
//      struct Params
//      {
//          Texture2D    t;
//          SamplerState s;
//          float4       c;
//      }
//      ConstantBuffer<Params> params;
//
//      float4 fragmentMain(
//          float2 uv : UV) : SV_Target
//      {
//          return params.t.Sample(params.s, uv) + params.c;
//      }
//
// As can be seen in this example, the `uniform` parameters
// declared as entry point parameters have been moved into
// a `struct` declaration that we then use to declare a global
// shader parameter that is a `ConstantBuffer`. We then
// rewrite references to those parameters to refer to the
// contents of the new constant buffer instead.
//
// We perform this transformation after the target-specific
// linking step, because that will have attached layout information
// to the entry point and its parameters. We need that layout
// information so that we can:
//
// * Identify which parameters are uniform vs. varying.
// * Have an appropriate layout to attached to the synthesized
//   global shader parameter `params`.
//
// One additional wrinkle this pass has to deal with is that
// in the case where the shader doesn't have any "ordinary"
// uniform parameters like `c` (e.g., it only has resource/object
// parameters), we do *not* wrap the parameter `struct` in
// a `ConstantBuffer`. For example, suppose we have:
//
//      float4 fragmentMain(
//          uniform Texture2D    t,
//          uniform SamplerState s;
//                  float2       uv : UV) : SV_Target
//      {
//          return t.Sample(s, uv);
//      }
//
// In this case the output of the transformation shold be:
//
//      struct Params
//      {
//          Texture2D    t;
//          SamplerState s;
//      }
//      Params params;
//
//      float4 fragmentMain(
//          float2 uv : UV) : SV_Target
//      {
//          return params.t.Sample(params.s, uv) + params.c;
//      }
//
// Note that this pass should always come before type legalization,
// which will take responsibility for turning a variable like
// `params` above into individual variables for the `t` and
// `s` fields.

// The overall structure here is similar to many other IR passes.
// We define a "context" structure to encapsulate the pass.
//
struct MoveEntryPointUniformParametersToGlobalScope
{
    // We'll hang on to the module we are processing,
    // so that we can refer to it when setting up `IRBuilder`s.
    //
    IRModule* module;

    // We will process a whole module by visiting all
    // its global functions, looking for entry points.
    //
    void processModule()
    {
        // Note that we are only looking at true global-scope
        // functions and not functions nested inside of
        // IR generics. When using generic entry points, this
        // pass should be run after the entry point(s) have
        // been specialized to their generic type parameters.

        for( auto inst : module->getGlobalInsts() )
        {
            // We are only interested in entry points.
            //
            // Every entry point must be a function.
            //
            auto func = as<IRFunc>(inst);
            if( !func )
                continue;

            // Entry points will always have the `[entryPoint]`
            // decoration to differentiate them from ordinary
            // functions.
            //
            // TODO: we could make `IREntryPoint` a subclass of
            // `IRFunc` if desired, to avoid having to attach
            // an explicit decoration to identify them.
            //
            if( !func->findDecorationImpl(kIROp_EntryPointDecoration) )
                continue;

            // If we fine a candidate entry point, then we
            // will process it.
            //
            processEntryPoint(func);
        }
    }

    void processEntryPoint(IRFunc* func)
    {
        // We expect all entry points to have explicit layout information attached.
        //
        // We will assert that we have the information we need, but try to be
        // defensive and bail out in the failure case in release builds.
        //
        auto funcLayoutDecoration = func->findDecoration<IRLayoutDecoration>();
        SLANG_ASSERT(funcLayoutDecoration);
        if(!funcLayoutDecoration)
            return;

        auto entryPointLayout = as<EntryPointLayout>(funcLayoutDecoration->getLayout());
        SLANG_ASSERT(entryPointLayout);
        if(!entryPointLayout)
            return;

        // The parameter layout for an entry point will either be a structure
        // type layout, or a constant buffer (a case of parameter group)
        // wrapped around such a structure.
        //
        // If we are in the latter case we will need to make sure to allocate
        // an explicit IR constant buffer for that wrapper, 
        //
        auto entryPointParamsLayout = entryPointLayout->parametersLayout;
        bool needConstantBuffer = entryPointParamsLayout->typeLayout.is<ParameterGroupTypeLayout>(); 

        // We will set up an IR builder so that we are ready to generate code.
        //
        SharedIRBuilder sharedBuilderStorage;
        auto sharedBuilder = &sharedBuilderStorage;
        sharedBuilder->module = module;
        sharedBuilder->session = module->getSession();

        IRBuilder builderStorage;
        auto builder = &builderStorage;
        builder->sharedBuilder = sharedBuilder;

        // *If* the entry point has any uniform parameter then we want to create a
        // structure type to house them, and a global shader parameter (either
        // an instance of that type or a constant buffer).
        //
        // We only want to create these if actually needed, so we will declare
        // them here and then initialize them on-demand.
        //
        IRStructType* paramStructType = nullptr;
        IRGlobalParam* globalParam = nullptr;

        // We will be removing any uniform parameters we run into, so we
        // need to iterate the parameter list carefully to deal with
        // us modifying it along the way.
        //
        IRParam* nextParam = nullptr;
        for( IRParam* param = func->getFirstParam(); param; param = nextParam )
        {
            nextParam = param->getNextParam();

            // We expect all entry-point parameters to have layout information,
            // but we will be defensive and skip parameters without the required
            // information when we are in a release build.
            //
            auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
            SLANG_ASSERT(layoutDecoration);
            if(!layoutDecoration)
                continue;
            auto paramLayout = as<VarLayout>(layoutDecoration->getLayout());
            SLANG_ASSERT(paramLayout);
            if(!paramLayout)
                continue;

            // A parameter that has varying input/output behavior should be left alone,
            // since this pass is only supposed to apply to uniform (non-varying)
            // parameters.
            //
            if(isVaryingParameter(paramLayout))
                continue;

            // At this point we know that `param` is not a varying shader parameter,
            // so that we want to turn it into an equivalent global shader parameter.
            //
            // If this is the first parameter we are running into, then we need
            // to deal with creating the structure type and global shader
            // parameter that our transformed entry point will use.
            //
            if( !paramStructType )
            {
                // First we create the structure to hold the parameters.
                //
                builder->setInsertBefore(func);
                paramStructType = builder->createStructType();

                if( needConstantBuffer )
                {
                    // If we need a constant buffer, then the global
                    // shader parameter will be a `ConstantBuffer<paramStructType>`
                    //
                    auto constantBufferType = builder->getConstantBufferType(paramStructType);
                    globalParam = builder->createGlobalParam(constantBufferType);
                }
                else
                {
                    // Otherwise, the global shader parameter is just
                    // an instance of `paramStructType`.
                    //
                    globalParam = builder->createGlobalParam(paramStructType);
                }

                // No matter what, the global shader parameter should have the layout
                // information from the entry point attached to it, so that the
                // contained parameters will end up in the right place(s).
                //
                builder->addLayoutDecoration(globalParam, entryPointParamsLayout);
            }

            // Now that we've ensured the global `struct` type and shader paramter
            // exist, we need to add a field to the `struct` to represent the
            // current parameter.
            //

            auto paramType = param->getFullType();

            builder->setInsertBefore(paramStructType);
            auto paramFieldKey = builder->createStructKey();
            auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType);
            SLANG_UNUSED(paramField);

            // We will transfer all decorations on the parameter over to the key
            // so that they can affect downstream emit logic.
            //
            // TODO: We should double-check whether any of the decorations should
            // be moved to the *field* instead.
            //
            param->transferDecorationsTo(paramFieldKey);

            // There is a bit of a hacky issue, where downstream passes (notably
            // type legalization) require the field keys for `struct` types to
            // have mangled names, because those mangled names will be used to
            // lookup field layout information inside of the layout information
            // for the `struct` type.
            //
            // TODO: We should fix that design choice in how layout information
            // is stored, to avoid the reliance on name strings.
            //
            builder->addExportDecoration(paramFieldKey, getMangledName(paramLayout->varDecl).getUnownedSlice());

            // At this point we want to eliminate the original entry point
            // parameter, in favor of the `struct` field we declared.
            // That required replacing any uses of the parameter with
            // appropriate code to pull out the field.
            //
            // We *could* extract the field at the start of the shader
            // and then do a `replaceAllUsesWith` to propragate it
            // down, but in practice we expect that it is better for
            // performance to "rematerialize" the value of a shader
            // parameter as close to where it is used as possible.
            //
            // We are therefore going to replace the uses one at a time.
            //
            while(auto use = param->firstUse )
            {
                // Given a `use` of the paramter, we will insert
                // the replacement code right before the instruction
                // that is doing the using.
                //
                builder->setInsertBefore(use->getUser());

                // The way to extract the field that corresponds
                // to the parameter depends on whether or not
                // we generated a constant buffer.
                //
                IRInst* fieldVal = nullptr;
                if( needConstantBuffer )
                {
                    // A constant buffer behaves like a pointer
                    // at the IR level, so we first do a pointer
                    // offset operation to compute what amounts
                    // to `&cb->field`, and then load from that address.
                    //
                    auto fieldAddress = builder->emitFieldAddress(
                        builder->getPtrType(paramType),
                        globalParam,
                        paramFieldKey);
                    fieldVal = builder->emitLoad(fieldAddress);
                }
                else
                {
                    // In the ordinary struct case, the parameter
                    // has an ordinary `struct` type (not a pointer),
                    // so we just extract the field directly.
                    //
                    fieldVal = builder->emitFieldExtract(
                        paramType,
                        globalParam,
                        paramFieldKey);
                }

                // We replace the value used at this use site, which
                // will have a side effect of making `use` no longer
                // be on the list of uses for `param`, so that when
                // we get back to the top of the loop the list of
                // uses will be shorter.
                //
                use->set(fieldVal);
            }

            // Once we've replaced all the uses of `param`, we
            // can go ahead and remove it completely.
            //
            param->removeAndDeallocate();
        }
    }

    // We need to be able to determine if a parameter is logically
    // a "varying" parameter based on its layout.
    //
    bool isVaryingParameter(VarLayout* layout)
    {
        // If *any* of the resources consumed by the parameter
        // is a varying resource kind (e.g., varying input) then
        // we consider the whole parameter to be varying.
        //
        // This is reasonable because there is no way to declare
        // a parameter that mixes varying and non-varying fields.
        //
        for( auto resInfo : layout->resourceInfos )
        {
            if(isVaryingResourceKind(resInfo.kind))
                return true;
        }

        // Varying parameters with "system value" semantics currently show up as
        // consuming no resources, so we need to special-case that here.
        //
        // Note: an empty `struct` parameter would also show up the same way, but
        // we should eliminate any such parameters later on during type legalization.
        //
        if(layout->resourceInfos.Count() == 0)
            return true;

        // if none of the above tests determined that the
        // parameter was varying, then we can safely consider
        // it to be non-varying (uniform):
        return false;
    }

    // In order to determine whether a parameter is varying based on its
    // layout, we need to know which resource kinds represent varying
    // shader parameters.
    //
    bool isVaryingResourceKind(LayoutResourceKind kind)
    {
        switch( kind )
        {
        default:
            return false;

            // Note: The set of cases that are considered
            // varying here would need to be extended if we
            // add more fine-grained resource kinds (e.g.,
            // if we ever add an explicit resource kind
            // for geometry shader output streams).
            //
            // Ordinary varying input/output:
        case LayoutResourceKind::VaryingInput:
        case LayoutResourceKind::VaryingOutput:
            //
            // Ray-tracing shader input/output:
        case LayoutResourceKind::CallablePayload:
        case LayoutResourceKind::HitAttributes:
        case LayoutResourceKind::RayPayload:
            return true;
        }
    }
};

void moveEntryPointUniformParamsToGlobalScope(
    IRModule*   module)
{
    MoveEntryPointUniformParametersToGlobalScope context;
    context.module = module;
    context.processModule();
}

}