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
|
// slang-ir-optix-entry-point-uniforms.cpp
// Note: A significant portion of this code is taken and modified from
// slang-ir-entry-point-uniforms.cpp
#include "slang-ir-optix-entry-point-uniforms.h"
#include "slang-ir-entry-point-pass.h"
#include "slang-ir-insts.h"
#include "slang-ir-restructure.h"
#include "slang-ir.h"
namespace Slang
{
struct CollectOptixEntryPointUniformParams : PerEntryPointPass
{
// *If* the entry point has any uniform parameter then we want to create a
// structure type to house them, and then replace the shader parameter
// references with an SBT record access.
// 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;
void processEntryPointImpl(EntryPointInfo const& info) SLANG_OVERRIDE
{
auto entryPointFunc = info.func;
auto entryPointDecoration = info.decoration;
// 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 only want to process entry points that are used in OptiX/ray-tracing
// stages, and not ordinary compute entry points (the entry-point `uniform`
// parameters of an ordinary compute entry point will translate to CUDA
// launch parameters).
//
switch (entryPointDecoration->getProfile().getStage())
{
default:
break;
case Stage::Compute:
return;
}
// 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,
//
// TODO: Reconcile the above with CUDA / OptiX...
entryPointParamsLayout = entryPointLayout->getParamsLayout();
auto entryPointParamsStructLayout = getScopeStructLayout(entryPointLayout);
// We will set up an IR builder so that we are ready to generate code.
//
IRBuilder builderStorage(m_module);
auto builder = &builderStorage;
// 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.
//
// In the case of optix, these varyings come in the form of ray payload
// and hit attributes
//
if (isVaryingParameter(paramLayout))
continue;
// At this point we know that `param` is not a varying shader parameter,
// so we'll treat it as part of the SBT record.
//
// 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 parameter
// 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 requires 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;
// 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);
// 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());
}
else
{
// If we didn't find a uniform parameter, we can safely return now.
return;
}
// Now, replace the collected parameter with OptiX SBT accesses.
auto paramType = collectedParam->getFullType();
builder->setInsertBefore(entryPointFunc->getFirstBlock()->getFirstOrdinaryInst());
IRInst* getAttr =
builder->emitIntrinsicInst(paramType, kIROp_GetOptiXSbtDataPtr, 0, nullptr);
collectedParam->replaceUsesWith(getAttr);
collectedParam->removeAndDeallocate();
fixUpFuncType(entryPointFunc);
}
void ensureCollectedParamAndTypeHaveBeenCreated()
{
if (paramStructType)
return;
IRBuilder builder(m_module);
// First we create the structure to hold the parameters.
//
builder.setInsertBefore(m_entryPoint.func);
paramStructType = builder.createStructType();
builder.addNameHintDecoration(
paramStructType,
UnownedTerminatedStringSlice("ShaderRecordParams"));
// If we need a constant buffer, then the global
// shader parameter will be a `ConstantBuffer<paramStructType>`
// TODO: reconcile this with OptiX, as the current logic works, but is still focused on
// VK/DXR..
//
auto constantBufferType = builder.getConstantBufferType(
paramStructType,
builder.getType(kIROp_DefaultBufferLayoutType));
collectedParam = builder.createParam(constantBufferType);
// 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("shaderRecordParams"));
}
};
void collectOptiXEntryPointUniformParams(IRModule* module)
{
// look into all entry point functions by checking the IREntryPointDecoration on the children
// Insts of the module. For any ray tracing entry points, collect all uniform parameters into
// one common struct, and replace parameter usage with SBT record accesses.
CollectOptixEntryPointUniformParams context;
context.processModule(module);
}
} // namespace Slang
|