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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
|
// slang-ir-entry-point-uniforms.cpp
#include "slang-ir-entry-point-uniforms.h"
#include "slang-ir.h"
#include "slang-ir-insts.h"
#include "slang-ir-entry-point-pass.h"
#include "slang-mangle.h"
namespace Slang
{
// The transformations 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 should 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.
// For clarity and flexibility, the work is split across two
// different IR passes:
//
// * The first pass simply collects together uniform parameters
// into a single parameter of `struct` or `ConstantBuffer<...>` type.
//
// * The second pass transforms entry-point uniform parameters
// into global shader parameters.
// First we start with some helper subroutines for detecting
// whether a parameter represents a varying input rather than
// a uniform parameter.
// 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;
}
}
bool isVaryingParameter(IRTypeLayout* typeLayout)
{
// If *any* of the resources consumed by the parameter type
// is *not* a varying resource kind, then we consider the
// whole parameter to be uniform (and thus not varying).
//
// Note that this means that an empty type will always
// be considered varying, even if it had been explicitly
// marked `uniform`.
//
// Note that this logic rules out support for parameters
// that mix varying and non-varying resource kinds.
//
// TODO: This whole convoluted definition exists because
// we currently don't give system-value parameters any
// reosurce kind, so they show up as empty. Simply
// adding `LayoutResourceKind`s for system-value inputs
// and outputs would allow for simpler logic here.
//
for(auto sizeAttr : typeLayout->getSizeAttrs())
{
if(!isVaryingResourceKind(sizeAttr->getResourceKind()))
return false;
}
return true;
}
bool isVaryingParameter(IRVarLayout* varLayout)
{
return isVaryingParameter(varLayout->getTypeLayout());
}
struct CollectEntryPointUniformParams : PerEntryPointPass
{
CollectEntryPointUniformParamsOptions m_options;
// *If* the entry point has any uniform parameter then we want to create a
// structure type to house them, and a single collected 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;
IRParam* collectedParam = nullptr;
IRVarLayout* entryPointParamsLayout = nullptr;
bool needConstantBuffer = false;
void processEntryPointImpl(EntryPointInfo const& info) SLANG_OVERRIDE
{
auto entryPointFunc = info.func;
// This pass object may be used across multiple entry points,
// so we need to make sure to reset state that could have been
// left over from a previous entry point.
//
paramStructType = nullptr;
collectedParam = nullptr;
// 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 = entryPointFunc->findDecoration<IRLayoutDecoration>();
SLANG_ASSERT(funcLayoutDecoration);
if(!funcLayoutDecoration)
return;
auto entryPointLayout = as<IREntryPointLayout>(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,
//
entryPointParamsLayout = entryPointLayout->getParamsLayout();
needConstantBuffer = as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout()) != nullptr;
auto entryPointParamsStructLayout = getScopeStructLayout(entryPointLayout);
// We will set up an IR builder so that we are ready to generate code.
//
IRBuilder builderStorage(m_sharedBuilder);
auto builder = &builderStorage;
if(m_options.alwaysCreateCollectedParam)
ensureCollectedParamAndTypeHaveBeenCreated();
// 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;
UInt paramCounter = 0;
for( IRParam* param = entryPointFunc->getFirstParam(); param; param = nextParam )
{
nextParam = param->getNextParam();
UInt paramIndex = paramCounter++;
// 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<IRVarLayout>(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.
//
ensureCollectedParamAndTypeHaveBeenCreated();
// Now that we've ensured the global `struct` type and collected shader paramter
// exist, we need to add a field to the `struct` to represent the
// current parameter.
//
auto paramType = param->getFullType();
builder->setInsertBefore(paramStructType);
// We need to know the "key" that should be used for the parameter,
// so we will read it off of the entry-point layout information.
//
// TODO: Maybe we should associate the key to the parameter via
// a decoration to avoid this indirection?
//
// TODO: Alternatively, we should make this pass responsible for
// dealing with the transfer of layout information from the entry
// point to its parameters, rather than baking that behavior into
// the linker. After all, this pass is traversing the same information
// anyway, so it could do the work while it is here...
//
auto paramFieldKey = cast<IRStructKey>(entryPointParamsStructLayout->getFieldLayoutAttrs()[paramIndex]->getFieldKey());
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);
// 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),
collectedParam,
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,
collectedParam,
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();
}
if( collectedParam )
{
collectedParam->insertBefore(entryPointFunc->getFirstBlock()->getFirstChild());
}
fixUpFuncType(entryPointFunc);
}
void ensureCollectedParamAndTypeHaveBeenCreated()
{
if(paramStructType)
return;
IRBuilder builder(m_sharedBuilder);
// First we create the structure to hold the parameters.
//
builder.setInsertBefore(m_entryPoint.func);
paramStructType = builder.createStructType();
builder.addNameHintDecoration(paramStructType, UnownedTerminatedStringSlice("EntryPointParams"));
if( needConstantBuffer )
{
// If we need a constant buffer, then the global
// shader parameter will be a `ConstantBuffer<paramStructType>`
//
auto constantBufferType = builder.getConstantBufferType(paramStructType);
collectedParam = builder.createParam(constantBufferType);
}
else
{
// Otherwise, the global shader parameter is just
// an instance of `paramStructType`.
//
collectedParam = builder.createParam(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(collectedParam, entryPointParamsLayout);
// We add a name hint to the global parameter so that it will
// emit to more readable code when referenced.
//
builder.addNameHintDecoration(collectedParam, UnownedTerminatedStringSlice("entryPointParams"));
}
};
struct MoveEntryPointUniformParametersToGlobalScope : PerEntryPointPass
{
void processEntryPointImpl(EntryPointInfo const& info) SLANG_OVERRIDE
{
auto entryPointFunc = info.func;
// We will set up an IR builder so that we are ready to generate code.
//
IRBuilder builderStorage(m_sharedBuilder);
auto builder = &builderStorage;
builder->setInsertBefore(entryPointFunc);
// 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 = entryPointFunc->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<IRVarLayout>(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;
auto paramType = param->getFullType();
builder->setInsertBefore(entryPointFunc);
auto globalParam = builder->createGlobalParam(paramType);
param->transferDecorationsTo(globalParam);
// We also decorate the parameter for the entry-point parameters
// so that we can find it again in downstream passes (like emit
// for CPU/CUDA) that might want to treat entry-point parameters
// different from other cases.
//
// TODO: Once we have support for multiple entry points to be emitted
// at once, we need a way to associate these per-entry-point parameters
// more closely with the original entry point. The two easiest options
// are:
//
// 1. Don't move the new aggregate parameter to the global scope
// on those targets, and instead keep it as a parameter of the
// entry point.
//
// 2. Use a decoration on the entry point itself to point at the
// global parameter for its per-entry-point parameter data.
//
builder->addDecoration(globalParam, kIROp_EntryPointParamDecoration);
param->replaceUsesWith(globalParam);
param->removeAndDeallocate();
}
fixUpFuncType(entryPointFunc);
}
};
void collectEntryPointUniformParams(
IRModule* module,
CollectEntryPointUniformParamsOptions const& options)
{
CollectEntryPointUniformParams context;
context.m_options = options;
context.processModule(module);
}
void moveEntryPointUniformParamsToGlobalScope(
IRModule* module)
{
MoveEntryPointUniformParametersToGlobalScope context;
context.processModule(module);
}
}
|