Skip to content

Commit 9590948

Browse files
author
Tim Foley
authored
Add support for global uniform shader parameters (shader-slang#1433)
* Adding support for global uniform shader parameters This change adds support for Slang programmers to declare shader parameters of "ordinary" types at global scope: ```hlsl uniform float gScaleFactor; void main() { ... *= gScaleFactor; ... } ``` The generated HLSL/GLSL/DXIL/SPIR-V output will be something along the lines of: ```hlsl struct GlobalParams { float gScaleFactor; } cbuffer globalParams { GlobalParams globalParams; } void main() { ... *= globalParams.gScaleFactor; ... } ``` The binding information used for the implicit `globalParams` constant buffer will be determined by the existing implicit parameter binding logic (which already had support for this kind of transformation). The reason this change is being pursued right now is because it is one step toward removing the implicit `KernelContext` type that is used to wrap the generated code for our CPU and CUDA C++ targets. Handling global-scope parameters of ordinary type requires an IR pass that synthesizes the `GlobalParams` structure type above, and that step ends up removing the need for the similar `UniformState` structure that was being used in the CPU/CUDA emit logic. A more detailed guide to the changes included follows: * The diagnostic for a global-scope variable that is implicitly a shader parameter was kept, but changed to a warning. Users can opt out of the warning by decorating their parameter as a `uniform` (since that keyword is already being used to mark entry-point parameters that should be treated as uniform shader parameters). * To simplify the task of finding the global shader parameters, the `CLikeSourceEmitter` type has been given an `m_irModule` member. The previous emit logic for `UniformState` was having to do a roundabout solution involving the `EmitAction`s to deal with not having direct access to the module. * Removed a few dead declarations in the emit logic (related to a much earlier point where emit was based on the AST instead of the IR). * Made the computation of type names in C++ emit take into account `ConstantBuffer<T>` and `ParameterBlock<T>`. As far as I can tell, these were being handled with some special-case hacks in the emit logic instead of being supported more fundamentally. It might actually be good to pass these through as `ConstantBuffer<T>` and `ParameterBlock<T>` in the C++ output, and allow the prelude to customize their translation (defaulting to defining them as `T*`). * Removed the special-case C++ emit logic for references to global shader parameters. There are now at most two global shader parameters to deal with, and the default emit logic (referring to them by name) does the Right Thing. * Changed the handling of entry points for C++ (both CPU and CUDA) so that it handles the bundled-up shader paameters for the global and entry-point scopes the same way. The main complication here is OptiX, where parameter data is passed very differently than it is for CUDA compute kernels. * Reverted changes to `ir-entry-point-uniforms` that had made its logic depend on the compilation target. The parameter binding logic was already responsible for deciding if a given target needed to wrap up its entry-point parameters in a constant buffer, and the IR pass was respecting that layout information. The current workaround had been removing the `ConstantBuffer<T>` indirection from this IR pass for CPU/CUDA, but then reintroducing the same indirection later on in the emit step. * Added an explicit IR pass with the task of collecting global-scope parameters of uniform/ordinary type and packaging them up into a `struct`, and then optionally packaging that `struct` up in a constant buffer. This pass bases its decisions on the IR layout information that was already computed, so it should match whatever policy choices were made at the layout level. * Changed the "key" operand on IR `struct` layout information to not assume an `IRStructKey`. The problem here is that the global scope gets a `StructTypeLayout` to represent its members, and this is convenient (rather than having to always special-case logic that handles the global scope), but the "fields" of that struct are global variables which do not have `IRStructKey`s associated with them. The simplest solution is to use the variables themselves as the keys, which required removing the assumption in the IR encoding. * Updated the IR layout process to compute a layout for the global scope of an entire program, and to attach that to the `IRModule` via a decoration. Updated the IR linking process to carry through that decoration to the linked output. This is necessary so that the IR pass that transforms global parameters can access the global-scope layout information. An important concern with this approach is that the contents and layout of the monolithic `GlobalParams` structure depends on the exact set of modules that were linked (and the order in which they were specified, in some cases). This isn't really a new thing with this change, but it becomes more important as we start to think of how to generalize things to better support separate compilation and linking. There are changes that can (and should) be made to the way that IR layouts are computed for programs (e.g., so that we compute layout per-module and then combine them rather than as a whole-program step). In this case, the problem of forming the combined/linked global layout can be moved down the IR level and not be reliant on AST-level information. Just changing the way layout and linking interact would not change the fundamental problem that global shader parameters as they currently exist in Slang/HLSL/GLSL are not readily compatible with true separate compilation. We either need to find a solution strategy that we can apply to allow existing shaders to work with separate compilation *or* we need to incrementally work toward removing support for global-scope shader parameters in favor of explicit entry-point parameters in all cases. * fixup: missing files * fixup: comment the new code
1 parent cfb41bb commit 9590948

24 files changed

+655
-256
lines changed

examples/heterogeneous-hello-world/heterogeneous-hello-world.vcxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,4 @@
182182
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
183183
<ImportGroup Label="ExtensionTargets">
184184
</ImportGroup>
185-
</Project>
185+
</Project>

source/slang/slang-diagnostic-defs.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ DIAGNOSTIC(39015, Error, wholeSpaceParameterRequiresZeroBinding, "shader paramet
453453
DIAGNOSTIC(39013, Error, dontExpectOutParametersForStage, "the '$0' stage does not support `out` or `inout` entry point parameters")
454454
DIAGNOSTIC(39014, Error, dontExpectInParametersForStage, "the '$0' stage does not support `in` entry point parameters")
455455

456-
DIAGNOSTIC(39016, Error, globalUniformsNotSupported, "'$0' is implicitly a global uniform shader parameter, which is currently unsupported by Slang. If a uniform parameter is intended, use a constant buffer or parameter block. If a global is intended, use the 'static' modifier.")
456+
DIAGNOSTIC(39016, Warning, globalUniformNotExpected, "'$0' is implicitly a global shader parameter, not a global variable. If a global variable is intended, add the 'static' modifier. If a uniform shader parameter is intended, add the 'uniform' modifier to silence this warning.")
457457

458458
DIAGNOSTIC(39017, Error, tooManyShaderRecordConstantBuffers, "can have at most one 'shader record' attributed constant buffer; found $0.")
459459

source/slang/slang-emit-c-like.h

+2-8
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class CLikeSourceEmitter: public RefObject
278278
void computeEmitActions(IRModule* module, List<EmitAction>& ioActions);
279279

280280
void executeEmitActions(List<EmitAction> const& actions);
281-
void emitModule(IRModule* module) { emitModuleImpl(module); }
281+
void emitModule(IRModule* module) { m_irModule = module; emitModuleImpl(module); }
282282

283283
void emitPreprocessorDirectives() { emitPreprocessorDirectivesImpl(); }
284284
void emitSimpleType(IRType* type);
@@ -352,6 +352,7 @@ class CLikeSourceEmitter: public RefObject
352352
List<IRWitnessTableEntry*> getSortedWitnessTableEntries(IRWitnessTable* witnessTable);
353353

354354
BackEndCompileRequest* m_compileRequest = nullptr;
355+
IRModule* m_irModule = nullptr;
355356

356357
// The stage for which we are emitting code.
357358
//
@@ -370,15 +371,8 @@ class CLikeSourceEmitter: public RefObject
370371
// Where source is written to
371372
SourceWriter* m_writer;
372373

373-
// We only want to emit each `import`ed module one time, so
374-
// we maintain a set of already-emitted modules.
375-
HashSet<ModuleDecl*> m_modulesAlreadyEmitted;
376-
377-
ModuleDecl* m_program = nullptr;
378-
379374
UInt m_uniqueIDCounter = 1;
380375
Dictionary<IRInst*, UInt> m_mapIRValueToID;
381-
Dictionary<Decl*, UInt> m_mapDeclToID;
382376

383377
HashSet<String> m_irDeclsVisited;
384378

source/slang/slang-emit-cpp.cpp

+80-109
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,16 @@ SlangResult CPPSourceEmitter::calcTypeName(IRType* type, CodeGenTarget target, S
500500
out << "void*";
501501
return SLANG_OK;
502502
}
503+
case kIROp_ConstantBufferType:
504+
case kIROp_ParameterBlockType:
505+
{
506+
auto groupType = cast<IRParameterGroupType>(type);
507+
auto elementType = groupType->getElementType();
508+
509+
SLANG_RETURN_ON_FAIL(calcTypeName(elementType, target, out));
510+
out << "*";
511+
return SLANG_OK;
512+
}
503513
default:
504514
{
505515
if (isNominalOp(type->op))
@@ -2357,29 +2367,6 @@ void CPPSourceEmitter::emitOperandImpl(IRInst* inst, EmitOpInfo const& outerPre
23572367

23582368
switch (inst->op)
23592369
{
2360-
case 0: // nothing yet
2361-
case kIROp_GlobalParam:
2362-
{
2363-
String name = getName(inst);
2364-
2365-
if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration))
2366-
{
2367-
// It's an entry point parameter
2368-
// The parameter is held in a struct so always deref
2369-
m_writer->emit("(*");
2370-
m_writer->emit(name);
2371-
m_writer->emit(")");
2372-
}
2373-
else
2374-
{
2375-
// It's in UniformState
2376-
m_writer->emit("(");
2377-
m_writer->emit("uniformState->");
2378-
m_writer->emit(name);
2379-
m_writer->emit(")");
2380-
}
2381-
break;
2382-
}
23832370
case kIROp_Param:
23842371
{
23852372
auto varLayout = getVarLayout(inst);
@@ -2443,7 +2430,7 @@ static bool _isFunction(IROp op)
24432430
return op == kIROp_Func;
24442431
}
24452432

2446-
void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointGlobalParams, const String& funcName, const UnownedStringSlice& varyingTypeName)
2433+
void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointParams, IRGlobalParam* globalParams, const String& funcName, const UnownedStringSlice& varyingTypeName)
24472434
{
24482435
auto resultType = func->getResultType();
24492436

@@ -2456,27 +2443,35 @@ void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalPara
24562443

24572444
m_writer->emit("(");
24582445
m_writer->emit(varyingTypeName);
2459-
m_writer->emit("* varyingInput, void* params, void* uniformState)");
2446+
m_writer->emit("* varyingInput, void* entryPointParams, void* globalParams)");
24602447
emitSemantics(func);
24612448
m_writer->emit("\n{\n");
24622449

24632450
m_writer->indent();
24642451
// Initialize when constructing so that globals are zeroed
24652452
m_writer->emit("KernelContext context = {};\n");
2466-
m_writer->emit("context.uniformState = (UniformState*)uniformState;\n");
24672453

2468-
if (entryPointGlobalParams)
2454+
if (entryPointParams)
24692455
{
2470-
auto varDecl = entryPointGlobalParams;
2471-
auto rawType = varDecl->getDataType();
2456+
auto param = entryPointParams;
2457+
auto paramType = param->getDataType();
24722458

2473-
auto varType = rawType;
2459+
m_writer->emit("context.");
2460+
m_writer->emit(getName(param));
2461+
m_writer->emit(" = (");
2462+
emitType(paramType);
2463+
m_writer->emit(")entryPointParams; \n");
2464+
}
2465+
if (globalParams)
2466+
{
2467+
auto param = globalParams;
2468+
auto paramType = param->getDataType();
24742469

24752470
m_writer->emit("context.");
2476-
m_writer->emit(getName(varDecl));
2471+
m_writer->emit(getName(param));
24772472
m_writer->emit(" = (");
2478-
emitType(varType);
2479-
m_writer->emit("*)params; \n");
2473+
emitType(paramType);
2474+
m_writer->emit(")globalParams; \n");
24802475
}
24812476
}
24822477

@@ -2677,71 +2672,53 @@ void CPPSourceEmitter::_emitForwardDeclarations(const List<EmitAction>& actions)
26772672
}
26782673
}
26792674

2680-
void CPPSourceEmitter::_calcGlobalParams(const List<EmitAction>& actions, List<GlobalParamInfo>& outParams, IRGlobalParam** outEntryPointGlobalParams)
2675+
void CPPSourceEmitter::_findShaderParams(
2676+
IRGlobalParam** outEntryPointParam,
2677+
IRGlobalParam** outGlobalParam)
26812678
{
2682-
outParams.clear();
2683-
*outEntryPointGlobalParams = nullptr;
2679+
SLANG_ASSERT(outEntryPointParam);
2680+
SLANG_ASSERT(outGlobalParam);
26842681

2685-
IRGlobalParam* entryPointGlobalParams = nullptr;
2686-
for (auto action : actions)
2687-
{
2688-
if (action.level == EmitAction::Level::Definition && action.inst->op == kIROp_GlobalParam)
2689-
{
2690-
auto inst = action.inst;
2682+
IRGlobalParam*& entryPointParam = *outEntryPointParam;
2683+
IRGlobalParam*& globalParam = *outGlobalParam;
26912684

2692-
if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration))
2693-
{
2694-
// Should only be one instruction marked this way
2695-
SLANG_ASSERT(entryPointGlobalParams == nullptr);
2696-
entryPointGlobalParams = as<IRGlobalParam>(inst);
2697-
continue;
2698-
}
2699-
2700-
IRVarLayout* varLayout = CLikeSourceEmitter::getVarLayout(action.inst);
2701-
SLANG_ASSERT(varLayout);
2702-
2703-
IRVarOffsetAttr* offsetAttr = varLayout->findOffsetAttr(LayoutResourceKind::Uniform);
2704-
IRTypeLayout* typeLayout = varLayout->getTypeLayout();
2705-
IRTypeSizeAttr* sizeAttr = typeLayout->findSizeAttr(LayoutResourceKind::Uniform);
2706-
2707-
GlobalParamInfo paramInfo;
2708-
paramInfo.inst = action.inst;
2709-
// Index is the byte offset for uniform
2710-
paramInfo.offset = offsetAttr ? offsetAttr->getOffset() : 0;
2711-
paramInfo.size = sizeAttr ? sizeAttr->getFiniteSize() : 0;
2685+
for(auto inst : m_irModule->getGlobalInsts())
2686+
{
2687+
auto param = as<IRGlobalParam>(inst);
2688+
if(!param)
2689+
continue;
27122690

2713-
outParams.add(paramInfo);
2691+
// Currently, the entry-point parameters
2692+
// are represented as a single parameter
2693+
// at the global scope, and the same is
2694+
// true of the parameters that were
2695+
// originally declared as globals.
2696+
//
2697+
// We need to find capture each of these
2698+
// parameters, and we need to tell them
2699+
// apart. Luckily, the logic that
2700+
// moved the entry-point parameters to
2701+
// global scope will ahve also marked
2702+
// the entry-point parameters with
2703+
// a decoration that we can detect.
2704+
//
2705+
if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration))
2706+
{
2707+
// Should only be one instruction marked this way
2708+
SLANG_ASSERT(entryPointParam == nullptr);
2709+
entryPointParam = param;
2710+
continue;
27142711
}
2715-
}
2716-
2717-
// We want to sort by layout offset, and insert suitable padding
2718-
outParams.sort();
2719-
2720-
*outEntryPointGlobalParams = entryPointGlobalParams;
2721-
}
2722-
2723-
void CPPSourceEmitter::_emitUniformStateMembers(const List<EmitAction>& actions, IRGlobalParam** outEntryPointGlobalParams)
2724-
{
2725-
List<GlobalParamInfo> params;
2726-
_calcGlobalParams(actions, params, outEntryPointGlobalParams);
2727-
2728-
int padIndex = 0;
2729-
size_t offset = 0;
2730-
for (const auto& paramInfo : params)
2731-
{
2732-
if (offset < paramInfo.offset)
2712+
else
27332713
{
2734-
// We want to output some padding
2735-
StringBuilder builder;
2736-
builder << "uint8_t _pad" << (padIndex++) << "[" << (paramInfo.offset - offset) << "];\n";
2737-
m_writer->emit(builder);
2714+
// There should only be one instruction representing
2715+
// the global-scope shader parameters.
2716+
//
2717+
SLANG_ASSERT(globalParam == nullptr);
2718+
globalParam = param;
2719+
continue;
27382720
}
2739-
2740-
emitGlobalInst(paramInfo.inst);
2741-
// Set offset after this
2742-
offset = paramInfo.offset + paramInfo.size;
27432721
}
2744-
m_writer->emit("\n");
27452722
}
27462723

27472724
void CPPSourceEmitter::emitModuleImpl(IRModule* module)
@@ -2756,26 +2733,15 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module)
27562733

27572734
_emitForwardDeclarations(actions);
27582735

2759-
IRGlobalParam* entryPointGlobalParams = nullptr;
2760-
2761-
// Output the global parameters in a 'UniformState' structure
2762-
{
2763-
m_writer->emit("struct UniformState\n{\n");
2764-
m_writer->indent();
2765-
2766-
_emitUniformStateMembers(actions, &entryPointGlobalParams);
2767-
2768-
m_writer->dedent();
2769-
m_writer->emit("\n};\n\n");
2770-
}
2736+
IRGlobalParam* entryPointParams = nullptr;
2737+
IRGlobalParam* globalParams = nullptr;
2738+
_findShaderParams(&entryPointParams, &globalParams);
27712739

27722740
// Output the 'Context' which will be used for execution
27732741
{
27742742
m_writer->emit("struct KernelContext\n{\n");
27752743
m_writer->indent();
27762744

2777-
m_writer->emit("UniformState* uniformState;\n");
2778-
27792745
m_writer->emit("uint3 dispatchThreadID;\n");
27802746

27812747
//if (m_semanticUsedFlags & SemanticUsedFlag::GroupID)
@@ -2797,9 +2763,14 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module)
27972763
m_writer->emit("}\n");
27982764
}
27992765

2800-
if (entryPointGlobalParams)
2766+
2767+
if (globalParams)
2768+
{
2769+
emitGlobalInst(globalParams);
2770+
}
2771+
if (entryPointParams)
28012772
{
2802-
emitGlobalInst(entryPointGlobalParams);
2773+
emitGlobalInst(entryPointParams);
28032774
}
28042775

28052776
// Output all the thread locals
@@ -2865,7 +2836,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module)
28652836

28662837
String threadFuncName = builder;
28672838

2868-
_emitEntryPointDefinitionStart(func, entryPointGlobalParams, threadFuncName, UnownedStringSlice::fromLiteral("ComputeThreadVaryingInput"));
2839+
_emitEntryPointDefinitionStart(func, entryPointParams, globalParams, threadFuncName, UnownedStringSlice::fromLiteral("ComputeThreadVaryingInput"));
28692840

28702841
if (m_semanticUsedFlags & SemanticUsedFlag::GroupThreadID)
28712842
{
@@ -2896,7 +2867,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module)
28962867

28972868
String groupFuncName = builder;
28982869

2899-
_emitEntryPointDefinitionStart(func, entryPointGlobalParams, groupFuncName, UnownedStringSlice::fromLiteral("ComputeVaryingInput"));
2870+
_emitEntryPointDefinitionStart(func, entryPointParams, globalParams, groupFuncName, UnownedStringSlice::fromLiteral("ComputeVaryingInput"));
29002871

29012872
m_writer->emit("const uint3 start = ");
29022873
_emitInitAxisValues(groupThreadSize, UnownedStringSlice::fromLiteral("varyingInput->startGroupID"), UnownedStringSlice());
@@ -2918,7 +2889,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module)
29182889

29192890
// Emit the main version - which takes a dispatch size
29202891
{
2921-
_emitEntryPointDefinitionStart(func, entryPointGlobalParams, funcName, UnownedStringSlice::fromLiteral("ComputeVaryingInput"));
2892+
_emitEntryPointDefinitionStart(func, entryPointParams, globalParams, funcName, UnownedStringSlice::fromLiteral("ComputeVaryingInput"));
29222893

29232894
m_writer->emit("const uint3 start = ");
29242895
_emitInitAxisValues(groupThreadSize, UnownedStringSlice::fromLiteral("varyingInput->startGroupID"), UnownedStringSlice());

source/slang/slang-emit-cpp.h

+6-15
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,6 @@ class CPPSourceEmitter: public CLikeSourceEmitter
3737
int colCount;
3838
};
3939

40-
struct GlobalParamInfo
41-
{
42-
typedef GlobalParamInfo ThisType;
43-
bool operator<(const ThisType& rhs) const { return offset < rhs.offset; }
44-
bool operator==(const ThisType& rhs) const { return offset == rhs.offset; }
45-
bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
46-
47-
IRInst* inst;
48-
UInt offset;
49-
UInt size;
50-
};
51-
5240
virtual void useType(IRType* type);
5341
virtual void emitCall(const HLSLIntrinsic* specOp, IRInst* inst, const IRUse* operands, int numOperands, const EmitOpInfo& inOuterPrec);
5442
virtual void emitTypeDefinition(IRType* type);
@@ -94,8 +82,11 @@ class CPPSourceEmitter: public CLikeSourceEmitter
9482
void _maybeEmitSpecializedOperationDefinition(const HLSLIntrinsic* specOp);
9583

9684
void _emitForwardDeclarations(const List<EmitAction>& actions);
97-
void _calcGlobalParams(const List<EmitAction>& actions, List<GlobalParamInfo>& outParams, IRGlobalParam** outEntryPointGlobalParams);
98-
void _emitUniformStateMembers(const List<EmitAction>& actions, IRGlobalParam** outEntryPointGlobalParams);
85+
86+
/// Find the IR global parameters representing the entry-point and global shader parameters (if any)
87+
void _findShaderParams(
88+
IRGlobalParam** outEntryPointParam,
89+
IRGlobalParam** outGlobalParam);
9990

10091
void _emitAryDefinition(const HLSLIntrinsic* specOp);
10192

@@ -127,7 +118,7 @@ class CPPSourceEmitter: public CLikeSourceEmitter
127118

128119
SlangResult _calcCPPTextureTypeName(IRTextureTypeBase* texType, StringBuilder& outName);
129120

130-
void _emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointGlobalParams, const String& funcName, const UnownedStringSlice& varyingTypeName);
121+
void _emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointParams, IRGlobalParam* globalParams, const String& funcName, const UnownedStringSlice& varyingTypeName);
131122
void _emitEntryPointDefinitionEnd(IRFunc* func);
132123
void _emitEntryPointGroup(const Int sizeAlongAxis[kThreadGroupAxisCount], const String& funcName);
133124
void _emitEntryPointGroupRange(const Int sizeAlongAxis[kThreadGroupAxisCount], const String& funcName);

0 commit comments

Comments
 (0)