diff options
Diffstat (limited to 'source/slang/ir-entry-point-uniforms.cpp')
| -rw-r--r-- | source/slang/ir-entry-point-uniforms.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp new file mode 100644 index 000000000..64deec1c5 --- /dev/null +++ b/source/slang/ir-entry-point-uniforms.cpp @@ -0,0 +1,423 @@ +// ir-entry-point-uniforms.cpp +#include "ir-entry-point-uniforms.h" + +#include "ir.h" +#include "ir-insts.h" + +#include "mangle.h" + +namespace Slang +{ + + +// The transformation in this file will solve the problem of taking +// code like the following: +// +// float4 fragmentMain( +// uniform Texture2D t, +// uniform SamplerState s; +// uniform float4 c, +// float2 uv : UV) : SV_Target +// { +// return t.Sample(s, uv) + c; +// } +// +// and transforming into code like this: +// +// struct Params +// { +// Texture2D t; +// SamplerState s; +// float4 c; +// } +// ConstantBuffer<Params> params; +// +// float4 fragmentMain( +// float2 uv : UV) : SV_Target +// { +// return params.t.Sample(params.s, uv) + params.c; +// } +// +// As can be seen in this example, the `uniform` parameters +// declared as entry point parameters have been moved into +// a `struct` declaration that we then use to declare a global +// shader parameter that is a `ConstantBuffer`. We then +// rewrite references to those parameters to refer to the +// contents of the new constant buffer instead. +// +// We perform this transformation after the target-specific +// linking step, because that will have attached layout information +// to the entry point and its parameters. We need that layout +// information so that we can: +// +// * Identify which parameters are uniform vs. varying. +// * Have an appropriate layout to attached to the synthesized +// global shader parameter `params`. +// +// One additional wrinkle this pass has to deal with is that +// in the case where the shader doesn't have any "ordinary" +// uniform parameters like `c` (e.g., it only has resource/object +// parameters), we do *not* wrap the parameter `struct` in +// a `ConstantBuffer`. For example, suppose we have: +// +// float4 fragmentMain( +// uniform Texture2D t, +// uniform SamplerState s; +// float2 uv : UV) : SV_Target +// { +// return t.Sample(s, uv); +// } +// +// In this case the output of the transformation shold be: +// +// struct Params +// { +// Texture2D t; +// SamplerState s; +// } +// Params params; +// +// float4 fragmentMain( +// float2 uv : UV) : SV_Target +// { +// return params.t.Sample(params.s, uv) + params.c; +// } +// +// Note that this pass should always come before type legalization, +// which will take responsibility for turning a variable like +// `params` above into individual variables for the `t` and +// `s` fields. + +// The overall structure here is similar to many other IR passes. +// We define a "context" structure to encapsulate the pass. +// +struct MoveEntryPointUniformParametersToGlobalScope +{ + // We'll hang on to the module we are processing, + // so that we can refer to it when setting up `IRBuilder`s. + // + IRModule* module; + + // We will process a whole module by visiting all + // its global functions, looking for entry points. + // + void processModule() + { + // Note that we are only looking at true global-scope + // functions and not functions nested inside of + // IR generics. When using generic entry points, this + // pass should be run after the entry point(s) have + // been specialized to their generic type parameters. + + for( auto inst : module->getGlobalInsts() ) + { + // We are only interested in entry points. + // + // Every entry point must be a function. + // + auto func = as<IRFunc>(inst); + if( !func ) + continue; + + // Entry points will always have the `[entryPoint]` + // decoration to differentiate them from ordinary + // functions. + // + // TODO: we could make `IREntryPoint` a subclass of + // `IRFunc` if desired, to avoid having to attach + // an explicit decoration to identify them. + // + if( !func->findDecorationImpl(kIROp_EntryPointDecoration) ) + continue; + + // If we fine a candidate entry point, then we + // will process it. + // + processEntryPoint(func); + } + } + + void processEntryPoint(IRFunc* func) + { + // We expect all entry points to have explicit layout information attached. + // + // We will assert that we have the information we need, but try to be + // defensive and bail out in the failure case in release builds. + // + auto funcLayoutDecoration = func->findDecoration<IRLayoutDecoration>(); + SLANG_ASSERT(funcLayoutDecoration); + if(!funcLayoutDecoration) + return; + + auto entryPointLayout = dynamic_cast<EntryPointLayout*>(funcLayoutDecoration->getLayout()); + SLANG_ASSERT(entryPointLayout); + if(!entryPointLayout) + return; + + // The parameter layout for an entry point will either be a structure + // type layout, or a constant buffer (a case of parameter group) + // wrapped around such a structure. + // + // If we are in the latter case we will need to make sure to allocate + // an explicit IR constant buffer for that wrapper, + // + auto entryPointParamsLayout = entryPointLayout->parametersLayout; + bool needConstantBuffer = entryPointParamsLayout->typeLayout.as<ParameterGroupTypeLayout>() != nullptr; + + // We will set up an IR builder so that we are ready to generate code. + // + SharedIRBuilder sharedBuilderStorage; + auto sharedBuilder = &sharedBuilderStorage; + sharedBuilder->module = module; + sharedBuilder->session = module->getSession(); + + IRBuilder builderStorage; + auto builder = &builderStorage; + builder->sharedBuilder = sharedBuilder; + + // *If* the entry point has any uniform parameter then we want to create a + // structure type to house them, and a global shader parameter (either + // an instance of that type or a constant buffer). + // + // We only want to create these if actually needed, so we will declare + // them here and then initialize them on-demand. + // + IRStructType* paramStructType = nullptr; + IRGlobalParam* globalParam = nullptr; + + // We will be removing any uniform parameters we run into, so we + // need to iterate the parameter list carefully to deal with + // us modifying it along the way. + // + IRParam* nextParam = nullptr; + for( IRParam* param = func->getFirstParam(); param; param = nextParam ) + { + nextParam = param->getNextParam(); + + // We expect all entry-point parameters to have layout information, + // but we will be defensive and skip parameters without the required + // information when we are in a release build. + // + auto layoutDecoration = param->findDecoration<IRLayoutDecoration>(); + SLANG_ASSERT(layoutDecoration); + if(!layoutDecoration) + continue; + auto paramLayout = dynamic_cast<VarLayout*>(layoutDecoration->getLayout()); + SLANG_ASSERT(paramLayout); + if(!paramLayout) + continue; + + // A parameter that has varying input/output behavior should be left alone, + // since this pass is only supposed to apply to uniform (non-varying) + // parameters. + // + if(isVaryingParameter(paramLayout)) + continue; + + // At this point we know that `param` is not a varying shader parameter, + // so that we want to turn it into an equivalent global shader parameter. + // + // If this is the first parameter we are running into, then we need + // to deal with creating the structure type and global shader + // parameter that our transformed entry point will use. + // + if( !paramStructType ) + { + // First we create the structure to hold the parameters. + // + builder->setInsertBefore(func); + paramStructType = builder->createStructType(); + + if( needConstantBuffer ) + { + // If we need a constant buffer, then the global + // shader parameter will be a `ConstantBuffer<paramStructType>` + // + auto constantBufferType = builder->getConstantBufferType(paramStructType); + globalParam = builder->createGlobalParam(constantBufferType); + } + else + { + // Otherwise, the global shader parameter is just + // an instance of `paramStructType`. + // + globalParam = builder->createGlobalParam(paramStructType); + } + + // No matter what, the global shader parameter should have the layout + // information from the entry point attached to it, so that the + // contained parameters will end up in the right place(s). + // + builder->addLayoutDecoration(globalParam, entryPointParamsLayout); + } + + // Now that we've ensured the global `struct` type and shader paramter + // exist, we need to add a field to the `struct` to represent the + // current parameter. + // + + auto paramType = param->getFullType(); + + builder->setInsertBefore(paramStructType); + auto paramFieldKey = builder->createStructKey(); + auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType); + SLANG_UNUSED(paramField); + + // We will transfer all decorations on the parameter over to the key + // so that they can affect downstream emit logic. + // + // TODO: We should double-check whether any of the decorations should + // be moved to the *field* instead. + // + param->transferDecorationsTo(paramFieldKey); + + // There is a bit of a hacky issue, where downstream passes (notably + // type legalization) require the field keys for `struct` types to + // have mangled names, because those mangled names will be used to + // lookup field layout information inside of the layout information + // for the `struct` type. + // + // TODO: We should fix that design choice in how layout information + // is stored, to avoid the reliance on name strings. + // + builder->addExportDecoration(paramFieldKey, getMangledName(paramLayout->varDecl).getUnownedSlice()); + + // At this point we want to eliminate the original entry point + // parameter, in favor of the `struct` field we declared. + // That required replacing any uses of the parameter with + // appropriate code to pull out the field. + // + // We *could* extract the field at the start of the shader + // and then do a `replaceAllUsesWith` to propragate it + // down, but in practice we expect that it is better for + // performance to "rematerialize" the value of a shader + // parameter as close to where it is used as possible. + // + // We are therefore going to replace the uses one at a time. + // + while(auto use = param->firstUse ) + { + // Given a `use` of the paramter, we will insert + // the replacement code right before the instruction + // that is doing the using. + // + builder->setInsertBefore(use->getUser()); + + // The way to extract the field that corresponds + // to the parameter depends on whether or not + // we generated a constant buffer. + // + IRInst* fieldVal = nullptr; + if( needConstantBuffer ) + { + // A constant buffer behaves like a pointer + // at the IR level, so we first do a pointer + // offset operation to compute what amounts + // to `&cb->field`, and then load from that address. + // + auto fieldAddress = builder->emitFieldAddress( + builder->getPtrType(paramType), + globalParam, + paramFieldKey); + fieldVal = builder->emitLoad(fieldAddress); + } + else + { + // In the ordinary struct case, the parameter + // has an ordinary `struct` type (not a pointer), + // so we just extract the field directly. + // + fieldVal = builder->emitFieldExtract( + paramType, + globalParam, + paramFieldKey); + } + + // We replace the value used at this use site, which + // will have a side effect of making `use` no longer + // be on the list of uses for `param`, so that when + // we get back to the top of the loop the list of + // uses will be shorter. + // + use->set(fieldVal); + } + + // Once we've replaced all the uses of `param`, we + // can go ahead and remove it completely. + // + param->removeAndDeallocate(); + } + } + + // We need to be able to determine if a parameter is logically + // a "varying" parameter based on its layout. + // + bool isVaryingParameter(VarLayout* layout) + { + // If *any* of the resources consumed by the parameter + // is a varying resource kind (e.g., varying input) then + // we consider the whole parameter to be varying. + // + // This is reasonable because there is no way to declare + // a parameter that mixes varying and non-varying fields. + // + for( auto resInfo : layout->resourceInfos ) + { + if(isVaryingResourceKind(resInfo.kind)) + return true; + } + + // Varying parameters with "system value" semantics currently show up as + // consuming no resources, so we need to special-case that here. + // + // Note: an empty `struct` parameter would also show up the same way, but + // we should eliminate any such parameters later on during type legalization. + // + if(layout->resourceInfos.Count() == 0) + return true; + + // if none of the above tests determined that the + // parameter was varying, then we can safely consider + // it to be non-varying (uniform): + return false; + } + + // In order to determine whether a parameter is varying based on its + // layout, we need to know which resource kinds represent varying + // shader parameters. + // + bool isVaryingResourceKind(LayoutResourceKind kind) + { + switch( kind ) + { + default: + return false; + + // Note: The set of cases that are considered + // varying here would need to be extended if we + // add more fine-grained resource kinds (e.g., + // if we ever add an explicit resource kind + // for geometry shader output streams). + // + // Ordinary varying input/output: + case LayoutResourceKind::VaryingInput: + case LayoutResourceKind::VaryingOutput: + // + // Ray-tracing shader input/output: + case LayoutResourceKind::CallablePayload: + case LayoutResourceKind::HitAttributes: + case LayoutResourceKind::RayPayload: + return true; + } + } +}; + +void moveEntryPointUniformParamsToGlobalScope( + IRModule* module) +{ + MoveEntryPointUniformParametersToGlobalScope context; + context.module = module; + context.processModule(); +} + +} |
