diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-emit-c-like.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-emit-cpp.cpp | 189 | ||||
| -rw-r--r-- | source/slang/slang-emit-cpp.h | 21 | ||||
| -rw-r--r-- | source/slang/slang-emit-cuda.cpp | 74 | ||||
| -rw-r--r-- | source/slang/slang-emit.cpp | 29 | ||||
| -rw-r--r-- | source/slang/slang-ir-collect-global-uniforms.cpp | 288 | ||||
| -rw-r--r-- | source/slang/slang-ir-collect-global-uniforms.h | 18 | ||||
| -rw-r--r-- | source/slang/slang-ir-entry-point-uniforms.cpp | 62 | ||||
| -rw-r--r-- | source/slang/slang-ir-entry-point-uniforms.h | 3 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-ir-link.cpp | 16 | ||||
| -rw-r--r-- | source/slang/slang-ir-link.h | 3 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 56 | ||||
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 39 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 2 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 6 |
18 files changed, 602 insertions, 234 deletions
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 5a841dfc9..d3aaeb948 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -453,7 +453,7 @@ DIAGNOSTIC(39015, Error, wholeSpaceParameterRequiresZeroBinding, "shader paramet DIAGNOSTIC(39013, Error, dontExpectOutParametersForStage, "the '$0' stage does not support `out` or `inout` entry point parameters") DIAGNOSTIC(39014, Error, dontExpectInParametersForStage, "the '$0' stage does not support `in` entry point parameters") -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.") +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.") DIAGNOSTIC(39017, Error, tooManyShaderRecordConstantBuffers, "can have at most one 'shader record' attributed constant buffer; found $0.") diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h index c37a1514e..b04b075c5 100644 --- a/source/slang/slang-emit-c-like.h +++ b/source/slang/slang-emit-c-like.h @@ -278,7 +278,7 @@ public: void computeEmitActions(IRModule* module, List<EmitAction>& ioActions); void executeEmitActions(List<EmitAction> const& actions); - void emitModule(IRModule* module) { emitModuleImpl(module); } + void emitModule(IRModule* module) { m_irModule = module; emitModuleImpl(module); } void emitPreprocessorDirectives() { emitPreprocessorDirectivesImpl(); } void emitSimpleType(IRType* type); @@ -352,6 +352,7 @@ public: List<IRWitnessTableEntry*> getSortedWitnessTableEntries(IRWitnessTable* witnessTable); BackEndCompileRequest* m_compileRequest = nullptr; + IRModule* m_irModule = nullptr; // The stage for which we are emitting code. // @@ -370,15 +371,8 @@ public: // Where source is written to SourceWriter* m_writer; - // We only want to emit each `import`ed module one time, so - // we maintain a set of already-emitted modules. - HashSet<ModuleDecl*> m_modulesAlreadyEmitted; - - ModuleDecl* m_program = nullptr; - UInt m_uniqueIDCounter = 1; Dictionary<IRInst*, UInt> m_mapIRValueToID; - Dictionary<Decl*, UInt> m_mapDeclToID; HashSet<String> m_irDeclsVisited; diff --git a/source/slang/slang-emit-cpp.cpp b/source/slang/slang-emit-cpp.cpp index 6b7fc46bf..1ff5af4fb 100644 --- a/source/slang/slang-emit-cpp.cpp +++ b/source/slang/slang-emit-cpp.cpp @@ -500,6 +500,16 @@ SlangResult CPPSourceEmitter::calcTypeName(IRType* type, CodeGenTarget target, S out << "void*"; return SLANG_OK; } + case kIROp_ConstantBufferType: + case kIROp_ParameterBlockType: + { + auto groupType = cast<IRParameterGroupType>(type); + auto elementType = groupType->getElementType(); + + SLANG_RETURN_ON_FAIL(calcTypeName(elementType, target, out)); + out << "*"; + return SLANG_OK; + } default: { if (isNominalOp(type->op)) @@ -2357,29 +2367,6 @@ void CPPSourceEmitter::emitOperandImpl(IRInst* inst, EmitOpInfo const& outerPre switch (inst->op) { - case 0: // nothing yet - case kIROp_GlobalParam: - { - String name = getName(inst); - - if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration)) - { - // It's an entry point parameter - // The parameter is held in a struct so always deref - m_writer->emit("(*"); - m_writer->emit(name); - m_writer->emit(")"); - } - else - { - // It's in UniformState - m_writer->emit("("); - m_writer->emit("uniformState->"); - m_writer->emit(name); - m_writer->emit(")"); - } - break; - } case kIROp_Param: { auto varLayout = getVarLayout(inst); @@ -2443,7 +2430,7 @@ static bool _isFunction(IROp op) return op == kIROp_Func; } -void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointGlobalParams, const String& funcName, const UnownedStringSlice& varyingTypeName) +void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointParams, IRGlobalParam* globalParams, const String& funcName, const UnownedStringSlice& varyingTypeName) { auto resultType = func->getResultType(); @@ -2456,27 +2443,35 @@ void CPPSourceEmitter::_emitEntryPointDefinitionStart(IRFunc* func, IRGlobalPara m_writer->emit("("); m_writer->emit(varyingTypeName); - m_writer->emit("* varyingInput, void* params, void* uniformState)"); + m_writer->emit("* varyingInput, void* entryPointParams, void* globalParams)"); emitSemantics(func); m_writer->emit("\n{\n"); m_writer->indent(); // Initialize when constructing so that globals are zeroed m_writer->emit("KernelContext context = {};\n"); - m_writer->emit("context.uniformState = (UniformState*)uniformState;\n"); - if (entryPointGlobalParams) + if (entryPointParams) { - auto varDecl = entryPointGlobalParams; - auto rawType = varDecl->getDataType(); + auto param = entryPointParams; + auto paramType = param->getDataType(); - auto varType = rawType; + m_writer->emit("context."); + m_writer->emit(getName(param)); + m_writer->emit(" = ("); + emitType(paramType); + m_writer->emit(")entryPointParams; \n"); + } + if (globalParams) + { + auto param = globalParams; + auto paramType = param->getDataType(); m_writer->emit("context."); - m_writer->emit(getName(varDecl)); + m_writer->emit(getName(param)); m_writer->emit(" = ("); - emitType(varType); - m_writer->emit("*)params; \n"); + emitType(paramType); + m_writer->emit(")globalParams; \n"); } } @@ -2677,71 +2672,53 @@ void CPPSourceEmitter::_emitForwardDeclarations(const List<EmitAction>& actions) } } -void CPPSourceEmitter::_calcGlobalParams(const List<EmitAction>& actions, List<GlobalParamInfo>& outParams, IRGlobalParam** outEntryPointGlobalParams) +void CPPSourceEmitter::_findShaderParams( + IRGlobalParam** outEntryPointParam, + IRGlobalParam** outGlobalParam) { - outParams.clear(); - *outEntryPointGlobalParams = nullptr; + SLANG_ASSERT(outEntryPointParam); + SLANG_ASSERT(outGlobalParam); - IRGlobalParam* entryPointGlobalParams = nullptr; - for (auto action : actions) - { - if (action.level == EmitAction::Level::Definition && action.inst->op == kIROp_GlobalParam) - { - auto inst = action.inst; + IRGlobalParam*& entryPointParam = *outEntryPointParam; + IRGlobalParam*& globalParam = *outGlobalParam; - if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration)) - { - // Should only be one instruction marked this way - SLANG_ASSERT(entryPointGlobalParams == nullptr); - entryPointGlobalParams = as<IRGlobalParam>(inst); - continue; - } - - IRVarLayout* varLayout = CLikeSourceEmitter::getVarLayout(action.inst); - SLANG_ASSERT(varLayout); - - IRVarOffsetAttr* offsetAttr = varLayout->findOffsetAttr(LayoutResourceKind::Uniform); - IRTypeLayout* typeLayout = varLayout->getTypeLayout(); - IRTypeSizeAttr* sizeAttr = typeLayout->findSizeAttr(LayoutResourceKind::Uniform); - - GlobalParamInfo paramInfo; - paramInfo.inst = action.inst; - // Index is the byte offset for uniform - paramInfo.offset = offsetAttr ? offsetAttr->getOffset() : 0; - paramInfo.size = sizeAttr ? sizeAttr->getFiniteSize() : 0; + for(auto inst : m_irModule->getGlobalInsts()) + { + auto param = as<IRGlobalParam>(inst); + if(!param) + continue; - outParams.add(paramInfo); + // Currently, the entry-point parameters + // are represented as a single parameter + // at the global scope, and the same is + // true of the parameters that were + // originally declared as globals. + // + // We need to find capture each of these + // parameters, and we need to tell them + // apart. Luckily, the logic that + // moved the entry-point parameters to + // global scope will ahve also marked + // the entry-point parameters with + // a decoration that we can detect. + // + if (inst->findDecorationImpl(kIROp_EntryPointParamDecoration)) + { + // Should only be one instruction marked this way + SLANG_ASSERT(entryPointParam == nullptr); + entryPointParam = param; + continue; } - } - - // We want to sort by layout offset, and insert suitable padding - outParams.sort(); - - *outEntryPointGlobalParams = entryPointGlobalParams; -} - -void CPPSourceEmitter::_emitUniformStateMembers(const List<EmitAction>& actions, IRGlobalParam** outEntryPointGlobalParams) -{ - List<GlobalParamInfo> params; - _calcGlobalParams(actions, params, outEntryPointGlobalParams); - - int padIndex = 0; - size_t offset = 0; - for (const auto& paramInfo : params) - { - if (offset < paramInfo.offset) + else { - // We want to output some padding - StringBuilder builder; - builder << "uint8_t _pad" << (padIndex++) << "[" << (paramInfo.offset - offset) << "];\n"; - m_writer->emit(builder); + // There should only be one instruction representing + // the global-scope shader parameters. + // + SLANG_ASSERT(globalParam == nullptr); + globalParam = param; + continue; } - - emitGlobalInst(paramInfo.inst); - // Set offset after this - offset = paramInfo.offset + paramInfo.size; } - m_writer->emit("\n"); } void CPPSourceEmitter::emitModuleImpl(IRModule* module) @@ -2756,26 +2733,15 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module) _emitForwardDeclarations(actions); - IRGlobalParam* entryPointGlobalParams = nullptr; - - // Output the global parameters in a 'UniformState' structure - { - m_writer->emit("struct UniformState\n{\n"); - m_writer->indent(); - - _emitUniformStateMembers(actions, &entryPointGlobalParams); - - m_writer->dedent(); - m_writer->emit("\n};\n\n"); - } + IRGlobalParam* entryPointParams = nullptr; + IRGlobalParam* globalParams = nullptr; + _findShaderParams(&entryPointParams, &globalParams); // Output the 'Context' which will be used for execution { m_writer->emit("struct KernelContext\n{\n"); m_writer->indent(); - m_writer->emit("UniformState* uniformState;\n"); - m_writer->emit("uint3 dispatchThreadID;\n"); //if (m_semanticUsedFlags & SemanticUsedFlag::GroupID) @@ -2797,9 +2763,14 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module) m_writer->emit("}\n"); } - if (entryPointGlobalParams) + + if (globalParams) + { + emitGlobalInst(globalParams); + } + if (entryPointParams) { - emitGlobalInst(entryPointGlobalParams); + emitGlobalInst(entryPointParams); } // Output all the thread locals @@ -2865,7 +2836,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module) String threadFuncName = builder; - _emitEntryPointDefinitionStart(func, entryPointGlobalParams, threadFuncName, UnownedStringSlice::fromLiteral("ComputeThreadVaryingInput")); + _emitEntryPointDefinitionStart(func, entryPointParams, globalParams, threadFuncName, UnownedStringSlice::fromLiteral("ComputeThreadVaryingInput")); if (m_semanticUsedFlags & SemanticUsedFlag::GroupThreadID) { @@ -2896,7 +2867,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module) String groupFuncName = builder; - _emitEntryPointDefinitionStart(func, entryPointGlobalParams, groupFuncName, UnownedStringSlice::fromLiteral("ComputeVaryingInput")); + _emitEntryPointDefinitionStart(func, entryPointParams, globalParams, groupFuncName, UnownedStringSlice::fromLiteral("ComputeVaryingInput")); m_writer->emit("const uint3 start = "); _emitInitAxisValues(groupThreadSize, UnownedStringSlice::fromLiteral("varyingInput->startGroupID"), UnownedStringSlice()); @@ -2918,7 +2889,7 @@ void CPPSourceEmitter::emitModuleImpl(IRModule* module) // Emit the main version - which takes a dispatch size { - _emitEntryPointDefinitionStart(func, entryPointGlobalParams, funcName, UnownedStringSlice::fromLiteral("ComputeVaryingInput")); + _emitEntryPointDefinitionStart(func, entryPointParams, globalParams, funcName, UnownedStringSlice::fromLiteral("ComputeVaryingInput")); m_writer->emit("const uint3 start = "); _emitInitAxisValues(groupThreadSize, UnownedStringSlice::fromLiteral("varyingInput->startGroupID"), UnownedStringSlice()); diff --git a/source/slang/slang-emit-cpp.h b/source/slang/slang-emit-cpp.h index c64b92b0f..5a18686ec 100644 --- a/source/slang/slang-emit-cpp.h +++ b/source/slang/slang-emit-cpp.h @@ -37,18 +37,6 @@ public: int colCount; }; - struct GlobalParamInfo - { - typedef GlobalParamInfo ThisType; - bool operator<(const ThisType& rhs) const { return offset < rhs.offset; } - bool operator==(const ThisType& rhs) const { return offset == rhs.offset; } - bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } - - IRInst* inst; - UInt offset; - UInt size; - }; - virtual void useType(IRType* type); virtual void emitCall(const HLSLIntrinsic* specOp, IRInst* inst, const IRUse* operands, int numOperands, const EmitOpInfo& inOuterPrec); virtual void emitTypeDefinition(IRType* type); @@ -94,8 +82,11 @@ protected: void _maybeEmitSpecializedOperationDefinition(const HLSLIntrinsic* specOp); void _emitForwardDeclarations(const List<EmitAction>& actions); - void _calcGlobalParams(const List<EmitAction>& actions, List<GlobalParamInfo>& outParams, IRGlobalParam** outEntryPointGlobalParams); - void _emitUniformStateMembers(const List<EmitAction>& actions, IRGlobalParam** outEntryPointGlobalParams); + + /// Find the IR global parameters representing the entry-point and global shader parameters (if any) + void _findShaderParams( + IRGlobalParam** outEntryPointParam, + IRGlobalParam** outGlobalParam); void _emitAryDefinition(const HLSLIntrinsic* specOp); @@ -127,7 +118,7 @@ protected: SlangResult _calcCPPTextureTypeName(IRTextureTypeBase* texType, StringBuilder& outName); - void _emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointGlobalParams, const String& funcName, const UnownedStringSlice& varyingTypeName); + void _emitEntryPointDefinitionStart(IRFunc* func, IRGlobalParam* entryPointParams, IRGlobalParam* globalParams, const String& funcName, const UnownedStringSlice& varyingTypeName); void _emitEntryPointDefinitionEnd(IRFunc* func); void _emitEntryPointGroup(const Int sizeAlongAxis[kThreadGroupAxisCount], const String& funcName); void _emitEntryPointGroupRange(const Int sizeAlongAxis[kThreadGroupAxisCount], const String& funcName); diff --git a/source/slang/slang-emit-cuda.cpp b/source/slang/slang-emit-cuda.cpp index b79518052..d05c4edfc 100644 --- a/source/slang/slang-emit-cuda.cpp +++ b/source/slang/slang-emit-cuda.cpp @@ -711,19 +711,9 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) _emitForwardDeclarations(actions); - IRGlobalParam* entryPointGlobalParams = nullptr; - - // Output the global parameters in a 'UniformState' structure - { - m_writer->emit("struct UniformState\n{\n"); - m_writer->indent(); - - // We need these to be prefixed by __device__ - _emitUniformStateMembers(actions, &entryPointGlobalParams); - - m_writer->dedent(); - m_writer->emit("\n};\n\n"); - } + IRGlobalParam* entryPointParams = nullptr; + IRGlobalParam* globalParams = nullptr; + _findShaderParams(&entryPointParams, &globalParams); // Output group shared variables @@ -742,11 +732,13 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) m_writer->emit("struct KernelContext\n{\n"); m_writer->indent(); - m_writer->emit("UniformState* uniformState;\n"); - - if (entryPointGlobalParams) + if (globalParams) { - emitGlobalInst(entryPointGlobalParams); + emitGlobalInst(globalParams); + } + if (entryPointParams) + { + emitGlobalInst(entryPointParams); } // Output all the thread locals @@ -813,7 +805,7 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) #undef CASE } - if( stage != Stage::Compute ) + if(globalParams && stage != Stage::Compute ) { // Non-compute shaders (currently just OptiX ray tracing kernels) // require parameter data that is shared across multiple kernels @@ -826,7 +818,20 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) // used here (`SLANG_globalParams`) is thus a part of the // binary interface for Slang->OptiX translation. // - m_writer->emit("extern \"C\" { __constant__ UniformState SLANG_globalParams; }\n"); + // TODO: We need to make a decision about how indirected + // the parameter passing for global-scope data is going to + // be for CUDA and OptiX (ideally with an answer that is + // consistent across the two). For now we are deciding to + // make this global `__constant__` variable represent the + // global parameter data directly, rather than indirectly. + // + auto globalParamsPtrType = as<IRPointerLikeType>(globalParams->getDataType()); + SLANG_ASSERT(globalParamsPtrType); + auto gloablParamsElementType = globalParamsPtrType->getElementType(); + // + m_writer->emit("extern \"C\" { __constant__ "); + emitType(gloablParamsElementType, "SLANG_globalParams"); + m_writer->emit("; }\n"); } // As a convenience for anybody reading the generated @@ -869,7 +874,7 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) // `uniform` parameter data, and the second points to the global-scope // parameter data (if any). // - m_writer->emit("(void* entryPointShaderParameters, void* uniformState)"); + m_writer->emit("(void* entryPointParams, void* globalParams)"); } else { @@ -891,18 +896,27 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) // a compute shader or a ray-tracing shader, so we need to alter how we initialize // the pointer in our `context` based on the stage. // - if( stage == Stage::Compute ) - { - m_writer->emit("context.uniformState = (UniformState*)uniformState;\n"); - } - else + if( globalParams ) { - m_writer->emit("context.uniformState = &SLANG_globalParams;\n"); + if( stage == Stage::Compute ) + { + m_writer->emit("context."); + m_writer->emit(getName(globalParams)); + m_writer->emit(" = ("); + emitType(globalParams->getDataType()); + m_writer->emit(")globalParams;\n"); + } + else + { + m_writer->emit("context."); + m_writer->emit(getName(globalParams)); + m_writer->emit(" = &SLANG_globalParams;\n"); + } } - if (entryPointGlobalParams) + if (entryPointParams) { - auto varDecl = entryPointGlobalParams; + auto varDecl = entryPointParams; auto rawType = varDecl->getDataType(); auto varType = rawType; @@ -910,7 +924,7 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) m_writer->emit(getName(varDecl)); m_writer->emit(" = ("); emitType(varType); - m_writer->emit("*)"); + m_writer->emit(")"); // Similar to the case for global parameter data above, the entry-point // uniform parameter data gets passed in differently for compute kernels @@ -922,7 +936,7 @@ void CUDASourceEmitter::emitModuleImpl(IRModule* module) // in as an explicit parameter on the CUDA kernel, and we simply // cast it to the expected type here. // - m_writer->emit("entryPointShaderParameters"); + m_writer->emit("entryPointParams"); } else { diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index f824ac50f..7d2260597 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -6,6 +6,7 @@ #include "slang-ir-bind-existentials.h" #include "slang-ir-byte-address-legalize.h" +#include "slang-ir-collect-global-uniforms.h" #include "slang-ir-dce.h" #include "slang-ir-entry-point-uniforms.h" #include "slang-ir-glsl-legalize.h" @@ -224,26 +225,42 @@ Result linkAndOptimizeIR( #endif validateIRModuleIfEnabled(compileRequest, irModule); - - - - // Now that we've linked the IR code, any layout/binding // information has been attached to shader parameters // and entry points. Now we are safe to make transformations // that might move code without worrying about losing // the connection between a parameter and its layout. + + // One example of a transformation that needs to wait until + // we have layout information is the step where we collect + // any global-scope shader parameters with ordinary/uniform + // type into an aggregate `struct`, and then (optionally) + // wrap that `struct` up in a constant buffer. + // + // This step allows shaders to declare parameters of ordinary + // type as globals in the input file, while ensuring that + // downstream passes for graphics APIs like Vulkan and D3D + // can assume that all ordinary/uniform data is strictly + // passed using constant buffers. // - // An easy transformation of this kind is to take uniform + collectGlobalUniformParameters(irModule, outLinkedIR.globalScopeVarLayout); +#if 0 + dumpIRIfEnabled(compileRequest, irModule, "GLOBAL UNIFORMS COLLECTED"); +#endif + validateIRModuleIfEnabled(compileRequest, irModule); + + // Another transformation that needed to wait until we + // had layout information on parameters is to take uniform // parameters of a shader entry point and move them into // the global scope instead. // - moveEntryPointUniformParamsToGlobalScope(irModule, target); + moveEntryPointUniformParamsToGlobalScope(irModule); #if 0 dumpIRIfEnabled(compileRequest, irModule, "ENTRY POINT UNIFORMS MOVED"); #endif validateIRModuleIfEnabled(compileRequest, irModule); + // Desguar any union types, since these will be illegal on // various targets. // diff --git a/source/slang/slang-ir-collect-global-uniforms.cpp b/source/slang/slang-ir-collect-global-uniforms.cpp new file mode 100644 index 000000000..306bbdfee --- /dev/null +++ b/source/slang/slang-ir-collect-global-uniforms.cpp @@ -0,0 +1,288 @@ +// slang-ir-collect-global-uniforms.cpp +#include "slang-ir-collect-global-uniforms.h" + +#include "slang-ir-insts.h" + +namespace Slang +{ + +// This file implements a pass that takes input code like: +// +// uniform int gA; +// uniform float gB; +// +// void main() { ... gA + gB ... } +// +// and transforms it into code like: +// +// struct GlobalParams +// { +// int gA; +// float gB; +// } +// +// ConstantBuffer<GlobalParams> globalParams; +// +// void main() { ... globalParams.gA + globalParams.gB ... } +// +// The main consequence of this transformation is that we can support +// global `uniform` shader parameters of "ordinary" data types when +// compiling for targets that don't directly support that feature +// (e.g., GLSL/SPIR-V). +// +// In addition, on targets that already support a similar transformation +// (e.g., when compiling to DXBC/DXIL via fxc/dxc), making the `globalParams` +// constant buffer explicit allows us to control the binding that is +// assigned to it using our existing logic for automatic layout, rather than +// being left at the whims of the undocumented defaults of those compilers. +// +// A final consequence of this pass is that for targets where *all* +// shader parameters use "ordinary" data types (because there are no +// non-first-class types), we end up with a conveniently packaged up +// single parameter and type that encapsulates all of the shader inputs. +// +struct CollectGlobalUniformParametersContext +{ + // In orderto perform our transformation, we need access to the module + // to be transformed, as well as the layout information representing + // the global-scope shader parameters. + // + IRModule* module; + IRVarLayout* globalScopeVarLayout; + + // This is a relatively simple pass, and it is all driven + // by a single subroutine. + // + void processModule() + { + // We start by looking at the layout that was computed for the global-scope + // parameters to determine how the parameters are supposed to be pacakged. + // + // This step relies on the earlier layout computation logic to have implemented + // any target-specific policies around how the global-scope parametesr are + // to be passed, and therefore we avoid trying to make any target-specific + // decisions in this pass. + // + auto globalScopeTypeLayout = globalScopeVarLayout->getTypeLayout(); + auto globalParamsTypeLayout = globalScopeTypeLayout; + + // One example of a difference that might appear in the global-scope layout + // depending on the target is that the global-scope parameters might end + // up just pacakged as a `struct`, *or* they might be packaged up in a + // `ConstantBuffer<...>` or other parameter group that wraps that `struct`. + // + IRParameterGroupTypeLayout* globalParameterGroupTypeLayout = as<IRParameterGroupTypeLayout>(globalParamsTypeLayout); + if( globalParameterGroupTypeLayout ) + { + // In the case where there is a wrapping `ConstantBuffer<...>`, we want to + // get at the element type of that constant buffer, because it represents + // the packaged-up `struct` that we want to produce. + // + globalParamsTypeLayout = globalParameterGroupTypeLayout->getElementVarLayout()->getTypeLayout(); + } + + // As a special case (in order to avoid disruption to any downstream passes), + // if the layout for the global-scope parameters doesn't include any "ordinary" + // data (represented as `LayoutResourceKind::Uniform`), then we will not do + // the "packaging up" step at all. + // + // This means that the current pass will not change the results for a majority + // of targets (notably, all the current graphics APIs) *unless* global shader + // parameters are declared that use "ordinary' data. + // + // TODO: eventually we should remove this special case, and confirm that the resulting + // logic works across all shaders (it should). Doing so will be a necessary + // step if want to start applying the packaging-up of global-scope parameters on + // a per-module basis. + // + if(!globalParameterGroupTypeLayout && !globalParamsTypeLayout->findSizeAttr(LayoutResourceKind::Uniform)) + return; + + // We expect that the layout for the global-scope parameters is always + // computed for a `struct` type. + // + auto globalParamsStructTypeLayout = as<IRStructTypeLayout>(globalParamsTypeLayout); + SLANG_ASSERT(globalParamsStructTypeLayout); + + // We need to construct a single IR parameter that will represent + // the collected global-scope parameters. The `IRBuilder` we construct + // for this will also be used when replacing the individual parameters. + // + SharedIRBuilder sharedBuilder; + sharedBuilder.module = this->module; + sharedBuilder.session = module->session; + + IRBuilder builderStorage; + IRBuilder* builder = &builderStorage; + + builder->sharedBuilder = &sharedBuilder; + builder->setInsertInto(module->getModuleInst()); + + // The packaged-up global parameters will be turned into fields of + // a new global IR `struct` type, which we give a name of `GlobalParams` + // so that it is identifiable in the output. + // + // Note: the equivalent in fxc/dxc is the `$Globals` constant buffer. + // + auto wrapperStructType = builder->createStructType(); + builder->addNameHintDecoration(wrapperStructType, UnownedTerminatedStringSlice("GlobalParams")); + + // If the computed layout used a bare `struct` type, then we will use + // our `GlobalParams` struct as-is, but if the layout involved an + // implicitly defined `ConstantBuffer<...>`, this is where we construct + // the type `ConstantBuffer<GlobalParams>`. + // + IRType* wrapperParamType = wrapperStructType; + if( globalParameterGroupTypeLayout ) + { + auto wrapperParamGroupType = builder->getConstantBufferType(wrapperStructType); + wrapperParamType = wrapperParamGroupType; + } + + // Now that we've determined what the type of the new single global parameter + // should be, we can go ahead and emit it into the IR module. + // + // We will call the implicit parameter for all the globals `globalParams`. + // + IRGlobalParam* wrapperParam = builder->createGlobalParam(wrapperParamType); + builder->addLayoutDecoration(wrapperParam, globalScopeVarLayout); + builder->addNameHintDecoration(wrapperParam, UnownedTerminatedStringSlice("globalParams")); + + // With the setup work out of the way, we can iterate over the global + // parameters that were present in the layout information (they are + // represented as the fields of the global-scope `struct` layout). + // + for( auto fieldLayoutAttr : globalParamsStructTypeLayout->getFieldLayoutAttrs() ) + { + // We expect the IR layout pass to have encoded field per-field + // layout so that the "key" for the field is the corresponding + // global shader parameter. + // + auto globalParam = as<IRGlobalParam>(fieldLayoutAttr->getFieldKey()); + SLANG_ASSERT(globalParam); + + auto globalParamLayout = fieldLayoutAttr->getLayout(); + + // If the given parameter doesn't contribute to uniform/ordinary usage, then + // we can safely leave it at the global scope and potentially avoid a lot + // of complications that might otherwise arise (that is, we don't need to worry + // about downstream passes that might have worked for a simple global parameter, + // but that would not work for one nested inside a structure. + // + // TODO: It would be more consistent and robust to *always* wrap up + // these global parameters appropriately, and ensure that all the downstream + // passes can handle that case, since they would need to do so in general. + // + if(!globalParamLayout->getTypeLayout()->findSizeAttr(LayoutResourceKind::Uniform) ) + continue; + + // Once we have decided to do replacement, we need to + // set ourselves up to emit the replacement code. + // + builder->setInsertBefore(globalParam); + + // This global parameter needs to be turned into a field of the global + // parameter structure type, and that field will need a key. + // + auto fieldKey = builder->createStructKey(); + + // The new structure field will need to have whatever decorations + // had been put on the global parameter (notably including any name hint) + // + globalParam->transferDecorationsTo(fieldKey); + + // In order to make sure that the existing IR layout information for + // the global scope remains valid, we will swap out the key in the + // per-field layout information to reference the key we created + // instead of the existing parameter (which we will be removing). + // + fieldLayoutAttr->setOperand(0, fieldKey); + + // Now we can add a field to the `GlobalParams` type that + // will stand in for the parameter: it will have the key we + // just generated, and the type of the original parameter. + // + auto globalParamType = globalParam->getFullType(); + builder->createStructField(wrapperStructType, fieldKey, globalParamType); + + // Next we need to replace the uses of the parameter will + // logic to extract the appropriate field from the aggregated + // parameter. + // + // Unlike trivial cases that can work with `replaceAllUsesWith`, + // we are going to need to different code for each use, and that + // potentially puts us in the bad case of modifying the use-def + // information while also iterating it. + // + // To worka around the problem, we will make a copy of the list of + // uses and work with that instead. + // + List<IRUse*> uses; + for(auto use = globalParam->firstUse; use; use = use->nextUse) + { + uses.add(use); + } + for( auto use : uses ) + { + // For each use site for the global parameter, we will + // insert new code right before the instruction that uses + // the parameter. + // + // TODO: In some cases we might want to emit a single load of + // a global parameter at the start of a function, rather + // than individual loads at multiple points in the body + // of a function. Ideally we can/should annotate the + // `globalParams` parameter with the equivalent of `__restrict__` + // so that these loads can be merged/moved without concern + // for aliasing. + // + auto user = use->user; + builder->setInsertBefore(user); + + IRInst* value = nullptr; + if( globalParameterGroupTypeLayout ) + { + // If the global parameters are being placed in a + // `ConstantBuffer<GlobalParams>`, then we need to + // emit an instruction to compute a pointer to the + // desired field, and then load from it. + // + auto ptrType = builder->getPtrType(globalParamType); + auto fieldAddr = builder->emitFieldAddress(ptrType, wrapperParam, fieldKey); + value = builder->emitLoad(globalParamType, fieldAddr); + } + else + { + // If the global parameters are being bundled in a + // plain old `struct`, then we simple want to emit + // an instruction to extract the desired field. + // + value = builder->emitFieldExtract(globalParamType, wrapperParam, fieldKey); + } + + // Whatever replacement value we computed, we need + // to install it as the value to be used at the use site. + // + use->set(value); + } + + // Once we've replaced all uses of the global parameter, + // we can remove it from the IR module completely. + // + globalParam->removeAndDeallocate(); + } + } +}; + +void collectGlobalUniformParameters( + IRModule* module, + IRVarLayout* globalScopeVarLayout) +{ + CollectGlobalUniformParametersContext context; + context.module = module; + context.globalScopeVarLayout = globalScopeVarLayout; + + context.processModule(); +} + +} diff --git a/source/slang/slang-ir-collect-global-uniforms.h b/source/slang/slang-ir-collect-global-uniforms.h new file mode 100644 index 000000000..5a173f4db --- /dev/null +++ b/source/slang/slang-ir-collect-global-uniforms.h @@ -0,0 +1,18 @@ +// slang-ir-collect-global-uniforms.h +#pragma once + +#include "slang-compiler.h" + +namespace Slang +{ +struct IRModule; +struct IRVarLayout; + + /// Collect global-scope shader parameters that use uniform/ordinary + /// storage into a single `struct` (possibly wrapped in a constant buffer). + /// +void collectGlobalUniformParameters( + IRModule* module, + IRVarLayout* globalScopeVarLayout); + +} diff --git a/source/slang/slang-ir-entry-point-uniforms.cpp b/source/slang/slang-ir-entry-point-uniforms.cpp index 388a7004d..9c3c029a5 100644 --- a/source/slang/slang-ir-entry-point-uniforms.cpp +++ b/source/slang/slang-ir-entry-point-uniforms.cpp @@ -98,12 +98,6 @@ struct MoveEntryPointUniformParametersToGlobalScope // IRModule* module; - // The target can determine how a variable is moved out into global scope - CodeGenTarget codeGenTarget; - - // If true the target needs constant buffer wrapping (for uniforms say) - bool targetNeedsConstantBuffer; - // We will process a whole module by visiting all // its global functions, looking for entry points. // @@ -168,7 +162,7 @@ struct MoveEntryPointUniformParametersToGlobalScope // an explicit IR constant buffer for that wrapper, // auto entryPointParamsLayout = entryPointLayout->getParamsLayout(); - bool needConstantBuffer = targetNeedsConstantBuffer && as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout()); + bool needConstantBuffer = as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout()) != nullptr; auto entryPointParamsStructLayout = getScopeStructLayout(entryPointLayout); @@ -237,6 +231,7 @@ struct MoveEntryPointUniformParametersToGlobalScope // builder->setInsertBefore(func); paramStructType = builder->createStructType(); + builder->addNameHintDecoration(paramStructType, UnownedTerminatedStringSlice("EntryPointParams")); if( needConstantBuffer ) { @@ -252,9 +247,6 @@ struct MoveEntryPointUniformParametersToGlobalScope // an instance of `paramStructType`. // globalParam = builder->createGlobalParam(paramStructType); - - // Mark that this global comes from the entry point - builder->addDecoration(globalParam, kIROp_EntryPointParamDecoration); } // No matter what, the global shader parameter should have the layout @@ -262,6 +254,30 @@ struct MoveEntryPointUniformParametersToGlobalScope // contained parameters will end up in the right place(s). // builder->addLayoutDecoration(globalParam, entryPointParamsLayout); + + // We add a name hint to the global parameter so that it will + // emit to more readable code when referenced. + // + builder->addNameHintDecoration(globalParam, UnownedTerminatedStringSlice("entryPointParams")); + + // 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); } // Now that we've ensured the global `struct` type and shader paramter @@ -285,7 +301,7 @@ struct MoveEntryPointUniformParametersToGlobalScope // the linker. After all, this pass is traversing the same information // anyway, so it could do the work while it is here... // - auto paramFieldKey = entryPointParamsStructLayout->getFieldLayoutAttrs()[paramIndex]->getFieldKey(); + auto paramFieldKey = cast<IRStructKey>(entryPointParamsStructLayout->getFieldLayoutAttrs()[paramIndex]->getFieldKey()); auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType); SLANG_UNUSED(paramField); @@ -435,32 +451,10 @@ struct MoveEntryPointUniformParametersToGlobalScope }; void moveEntryPointUniformParamsToGlobalScope( - IRModule* module, - CodeGenTarget target) + IRModule* module) { MoveEntryPointUniformParametersToGlobalScope context; - context.module = module; - context.codeGenTarget = target; - context.targetNeedsConstantBuffer = true; - - // Check if this target needs constant buffer wrapping - switch (target) - { - case CodeGenTarget::CPPSource: - case CodeGenTarget::CSource: - case CodeGenTarget::Executable: - case CodeGenTarget::SharedLibrary: - case CodeGenTarget::HostCallable: - case CodeGenTarget::CUDASource: - case CodeGenTarget::PTX: - { - context.targetNeedsConstantBuffer = false; - break; - } - default: break; - } - context.processModule(); } diff --git a/source/slang/slang-ir-entry-point-uniforms.h b/source/slang/slang-ir-entry-point-uniforms.h index 0e978b9eb..3b6a0743b 100644 --- a/source/slang/slang-ir-entry-point-uniforms.h +++ b/source/slang/slang-ir-entry-point-uniforms.h @@ -9,7 +9,6 @@ struct IRModule; /// Move any uniform parameters of entry points to the global scope instead. void moveEntryPointUniformParamsToGlobalScope( - IRModule* module, - CodeGenTarget target); + IRModule* module); } diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index 159021412..93e709c45 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -793,9 +793,9 @@ struct IRStructFieldLayoutAttr : IRAttr { IR_LEAF_ISA(StructFieldLayoutAttr) - IRStructKey* getFieldKey() + IRInst* getFieldKey() { - return cast<IRStructKey>(getOperand(0)); + return getOperand(0); } IRVarLayout* getLayout() @@ -836,7 +836,7 @@ struct IRStructTypeLayout : IRTypeLayout : Super::Builder(irBuilder) {} - void addField(IRStructKey* key, IRVarLayout* layout) + void addField(IRInst* key, IRVarLayout* layout) { FieldInfo info; info.key = key; @@ -855,7 +855,7 @@ struct IRStructTypeLayout : IRTypeLayout struct FieldInfo { - IRStructKey* key; + IRInst* key; IRVarLayout* layout; }; @@ -2099,7 +2099,7 @@ struct IRBuilder IRPendingLayoutAttr* getPendingLayoutAttr( IRLayout* pendingLayout); IRStructFieldLayoutAttr* getFieldLayoutAttr( - IRStructKey* key, + IRInst* key, IRVarLayout* layout); IRCaseTypeLayoutAttr* getCaseTypeLayoutAttr( IRTypeLayout* layout); diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp index 3935eab07..704917629 100644 --- a/source/slang/slang-ir-link.cpp +++ b/source/slang/slang-ir-link.cpp @@ -1403,7 +1403,8 @@ LinkedIR linkIR( // responsible for associating layout information to those // global symbols via decorations. // - insertGlobalValueSymbols(sharedContext, targetProgram->getExistingIRModuleForLayout()); + auto irModuleForLayout = targetProgram->getExistingIRModuleForLayout(); + insertGlobalValueSymbols(sharedContext, irModuleForLayout); auto context = state->getContext(); @@ -1454,6 +1455,18 @@ LinkedIR linkIR( auto entryPointMangledName = program->getEntryPointMangledName(entryPointIndices[0]); auto irEntryPoint = specializeIRForEntryPoint(context, entryPointMangledName); + // Layout information for global shader parameters is also required, + // and in particular every global parameter that is part of the layout + // should be present in the initial IR module so that steps that + // need to operate on all the global parameters can do so. + // + IRVarLayout* irGlobalScopeVarLayout = nullptr; + if( auto irGlobalScopeLayoutDecoration = irModuleForLayout->getModuleInst()->findDecoration<IRLayoutDecoration>() ) + { + auto irOriginalGlobalScopeVarLayout = irGlobalScopeLayoutDecoration->getLayout(); + irGlobalScopeVarLayout = cast<IRVarLayout>(cloneValue(context, irOriginalGlobalScopeVarLayout)); + } + // Bindings for global generic parameters are currently represented // as stand-alone global-scope instructions in the IR module for // `SpecializedComponentType`s. These instructions are required for @@ -1516,6 +1529,7 @@ LinkedIR linkIR( LinkedIR linkedIR; linkedIR.module = state->irModule; linkedIR.entryPoint = irEntryPoint; + linkedIR.globalScopeVarLayout = irGlobalScopeVarLayout; return linkedIR; } diff --git a/source/slang/slang-ir-link.h b/source/slang/slang-ir-link.h index dc9ad50a0..ce2af64d8 100644 --- a/source/slang/slang-ir-link.h +++ b/source/slang/slang-ir-link.h @@ -5,10 +5,13 @@ namespace Slang { + struct IRVarLayout; + struct LinkedIR { RefPtr<IRModule> module; IRFunc* entryPoint; + IRVarLayout* globalScopeVarLayout; }; diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index ef5ecb959..25bf0f9f3 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -3665,7 +3665,7 @@ namespace Slang } IRStructFieldLayoutAttr* IRBuilder::getFieldLayoutAttr( - IRStructKey* key, + IRInst* key, IRVarLayout* layout) { IRInst* operands[] = { key, layout }; @@ -4985,6 +4985,12 @@ namespace Slang if(as<IRConstant>(this)) return false; + if(as<IRLayout>(this)) + return false; + + if(as<IRAttr>(this)) + return false; + switch(op) { // By default, assume that we might have side effects, diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 1ba99dffb..adebefc8b 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -7583,7 +7583,7 @@ struct IRLayoutGenContext : IRGenContext {} /// Cache for custom key instructions used for entry-point parameter layout information. - Dictionary<ParamDecl*, IRStructKey*> mapEntryPointParamToKey; + Dictionary<ParamDecl*, IRInst*> mapEntryPointParamToKey; }; /// Lower an AST-level type layout to an IR-level type layout. @@ -7654,7 +7654,7 @@ IRTypeLayout* lowerTypeLayout( { auto fieldDecl = fieldLayout->varDecl; - IRStructKey* irFieldKey = nullptr; + IRInst* irFieldKey = nullptr; if(auto paramDecl = as<ParamDecl>(fieldDecl) ) { // There is a subtle special case here. @@ -7699,9 +7699,8 @@ IRTypeLayout* lowerTypeLayout( } else { - IRInst* irFieldKeyInst = getSimpleVal(context, + irFieldKey = getSimpleVal(context, ensureDecl(context, fieldDecl)); - irFieldKey = as<IRStructKey>(irFieldKeyInst); } SLANG_ASSERT(irFieldKey); @@ -7762,9 +7761,9 @@ IRTypeLayout* lowerTypeLayout( IRVarLayout* lowerVarLayout( IRLayoutGenContext* context, - VarLayout* varLayout) + VarLayout* varLayout, + IRTypeLayout* irTypeLayout) { - auto irTypeLayout = lowerTypeLayout(context, varLayout->typeLayout); IRVarLayout::Builder irLayoutBuilder(context->irBuilder, irTypeLayout); for( auto resInfo : varLayout->resourceInfos ) @@ -7806,6 +7805,14 @@ IRVarLayout* lowerVarLayout( return irLayoutBuilder.build(); } +IRVarLayout* lowerVarLayout( + IRLayoutGenContext* context, + VarLayout* varLayout) +{ + auto irTypeLayout = lowerTypeLayout(context, varLayout->typeLayout); + return lowerVarLayout(context, varLayout, irTypeLayout); +} + /// Handle the lowering of an entry-point result layout to the IR IRVarLayout* lowerEntryPointResultLayout( IRLayoutGenContext* context, @@ -7893,10 +7900,12 @@ RefPtr<IRModule> TargetProgram::createIRModuleForLayout(DiagnosticSink* sink) // Okay, now we need to walk through and decorate everything. auto globalStructLayout = getScopeStructLayout(programLayout); - for(auto globalVarPair : globalStructLayout->mapVarToLayout) + + IRStructTypeLayout::Builder globalStructTypeLayoutBuilder(builder); + + for(auto varLayout : globalStructLayout->fields) { - auto varDecl = globalVarPair.Key; - auto varLayout = globalVarPair.Value; + auto varDecl = varLayout->varDecl; // Ensure that an `[import(...)]` declaration for the variable // has been emitted to this module, so that we will have something @@ -7909,7 +7918,36 @@ RefPtr<IRModule> TargetProgram::createIRModuleForLayout(DiagnosticSink* sink) // Now attach the decoration to the variable. // builder->addLayoutDecoration(irVar, irLayout); + + // Also add this to our mapping for the global-scope structure type + globalStructTypeLayoutBuilder.addField(irVar, irLayout); } + auto irGlobalStructTypeLayout = _lowerTypeLayoutCommon(context, &globalStructTypeLayoutBuilder, globalStructLayout); + + auto globalScopeVarLayout = programLayout->parametersLayout; + auto globalScopeTypeLayout = globalScopeVarLayout->typeLayout; + IRTypeLayout* irGlobalScopeTypeLayout = irGlobalStructTypeLayout; + if( auto paramGroupTypeLayout = as<ParameterGroupTypeLayout>(globalScopeTypeLayout) ) + { + IRParameterGroupTypeLayout::Builder globalParameterGroupTypeLayoutBuilder(builder); + + auto irElementTypeLayout = irGlobalStructTypeLayout; + auto irElementVarLayout = lowerVarLayout(context, paramGroupTypeLayout->elementVarLayout, irElementTypeLayout); + + globalParameterGroupTypeLayoutBuilder.setContainerVarLayout( + lowerVarLayout(context, paramGroupTypeLayout->containerVarLayout)); + globalParameterGroupTypeLayoutBuilder.setElementVarLayout(irElementVarLayout); + globalParameterGroupTypeLayoutBuilder.setOffsetElementTypeLayout( + lowerTypeLayout(context, paramGroupTypeLayout->offsetElementTypeLayout)); + + auto irParamGroupTypeLayout = _lowerTypeLayoutCommon(context, &globalParameterGroupTypeLayoutBuilder, paramGroupTypeLayout); + + irGlobalScopeTypeLayout = irParamGroupTypeLayout; + } + + auto irGlobalScopeVarLayout = lowerVarLayout(context, globalScopeVarLayout, irGlobalScopeTypeLayout); + + builder->addLayoutDecoration(irModule->getModuleInst(), irGlobalScopeVarLayout); for( auto entryPointLayout : programLayout->entryPoints ) { diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index ee3ef1234..6f2a1632e 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -689,6 +689,7 @@ static RefPtr<VarLayout> _createVarLayout( if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout) { RefPtr<VarLayout> pendingVarLayout = new VarLayout(); + pendingVarLayout->varDecl = varDeclRef; pendingVarLayout->typeLayout = pendingDataTypeLayout; varLayout->pendingVarLayout = pendingVarLayout; } @@ -2857,29 +2858,41 @@ static void collectParameters( program->acceptVisitor(&visitor, nullptr); } - /// Emit a diagnostic about a uniform parameter at global scope. + /// Emit a diagnostic about a uniform/ordinary parameter at global scope. void diagnoseGlobalUniform( SharedParameterBindingContext* sharedContext, VarDeclBase* varDecl) { - // It is entirely possible for Slang to support uniform parameters at the global scope, - // by bundling them into an implicit constant buffer, and indeed the layout algorithm - // implemented in this file computes a layout *as if* the Slang compiler does just that. + // This subroutine gets invoked if a shader parameter containing + // "ordinary" data (sometimes just called "uniform" data) is present + // at the global scope. // - // The missing link is the downstream IR and code generation steps, where we would need - // to collect all of the global-scope uniforms into a common `struct` type and then - // create a new constant buffer parameter over that type. + // Slang can support such parameters by aggregating them into + // an implicit constant buffer, but it is also common for programmers + // to accidentally declare a global-scope shader parameter when they + // meant to declare a global variable instead: // - // For now it is easier to simply ban this case, since most shader authors have - // switched to modern HLSL/GLSL style with `cbuffer` or `uniform` block declarations. + // int gCounter = 0; // this is a shader parameter, not a global // - // TODO: In the long run it may be best to require *all* global-scope shader parameters - // to be marked with a keyword (e.g., `uniform`) so that ordinary global variable syntax can be - // used safely. + // In order to avoid mistakes, we'd like to warn the user when + // they write code like the above, and hint to them that they + // should make their intention more explicit with a keyword: // - getSink(sharedContext)->diagnose(varDecl, Diagnostics::globalUniformsNotSupported, varDecl->getName()); + // static int gCounter = 0; // this is now a (static) global + // + // uniform int gCounter; // this is now explicitly a shader parameter + // + // We skip the diagnostic whenever the variable was explicitly `uniform`, + // under the assumption that the programmer who added that modifier + // knew what they were opting into. + // + if(varDecl->hasModifier<HLSLUniformModifier>()) + return; + + getSink(sharedContext)->diagnose(varDecl, Diagnostics::globalUniformNotExpected, varDecl->getName()); } + static int _calcTotalNumUsedRegistersForLayoutResourceKind(ParameterBindingContext* bindingContext, LayoutResourceKind kind) { int numUsed = 0; diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 5850bc2b8..95f98e4ec 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -223,6 +223,7 @@ <ClInclude Include="slang-ir-bind-existentials.h" /> <ClInclude Include="slang-ir-byte-address-legalize.h" /> <ClInclude Include="slang-ir-clone.h" /> + <ClInclude Include="slang-ir-collect-global-uniforms.h" /> <ClInclude Include="slang-ir-constexpr.h" /> <ClInclude Include="slang-ir-dce.h" /> <ClInclude Include="slang-ir-dominators.h" /> @@ -313,6 +314,7 @@ <ClCompile Include="slang-ir-bind-existentials.cpp" /> <ClCompile Include="slang-ir-byte-address-legalize.cpp" /> <ClCompile Include="slang-ir-clone.cpp" /> + <ClCompile Include="slang-ir-collect-global-uniforms.cpp" /> <ClCompile Include="slang-ir-constexpr.cpp" /> <ClCompile Include="slang-ir-dce.cpp" /> <ClCompile Include="slang-ir-dominators.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index fdc1bf45d..561599a8f 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -120,6 +120,9 @@ <ClInclude Include="slang-ir-clone.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-ir-collect-global-uniforms.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-ir-constexpr.h"> <Filter>Header Files</Filter> </ClInclude> @@ -386,6 +389,9 @@ <ClCompile Include="slang-ir-clone.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-ir-collect-global-uniforms.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-ir-constexpr.cpp"> <Filter>Source Files</Filter> </ClCompile> |
