https://github.com/shader-slang/slang
Tip revision: d386e27dc319b2feb362acd430ff5c640d8c6fba authored by jsmall-nvidia on 02 June 2020, 19:26:51 UTC
Added spGetBuildTagString. (#1365)
Added spGetBuildTagString. (#1365)
Tip revision: d386e27
slang-ir-entry-point-uniforms.cpp
// slang-ir-entry-point-uniforms.cpp
#include "slang-ir-entry-point-uniforms.h"
#include "slang-ir.h"
#include "slang-ir-insts.h"
#include "slang-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 should be:
//
// struct Params
// {
// Texture2D t;
// SamplerState s;
// }
// Params params;
//
// float4 fragmentMain(
// float2 uv : UV) : SV_Target
// {
// return params.t.Sample(params.s, uv) + params.c;
// }
//
// Note that this pass should always come before type legalization,
// which will take responsibility for turning a variable like
// `params` above into individual variables for the `t` and
// `s` fields.
// 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;
// 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.
//
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 = 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,
//
auto entryPointParamsLayout = entryPointLayout->getParamsLayout();
bool needConstantBuffer = targetNeedsConstantBuffer && as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout());
auto entryPointParamsStructLayout = getScopeStructLayout(entryPointLayout);
// 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;
UInt paramCounter = 0;
for( IRParam* param = func->getFirstParam(); param; param = nextParam )
{
nextParam = param->getNextParam();
UInt paramIndex = paramCounter++;
// We expect all entry-point parameters to have layout information,
// but we will be defensive and skip parameters without the required
// information when we are in a release build.
//
auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
SLANG_ASSERT(layoutDecoration);
if(!layoutDecoration)
continue;
auto paramLayout = as<IRVarLayout>(layoutDecoration->getLayout());
SLANG_ASSERT(paramLayout);
if(!paramLayout)
continue;
// A parameter that has varying input/output behavior should be left alone,
// since this pass is only supposed to apply to uniform (non-varying)
// parameters.
//
if(isVaryingParameter(paramLayout))
continue;
// At this point we know that `param` is not a varying shader parameter,
// so that we want to turn it into an equivalent global shader parameter.
//
// If this is the first parameter we are running into, then we need
// to deal with creating the structure type and global shader
// parameter that our transformed entry point will use.
//
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);
// 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
// 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);
// 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 = entryPointParamsStructLayout->getFieldLayoutAttrs()[paramIndex]->getFieldKey();
auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType);
SLANG_UNUSED(paramField);
// We will transfer all decorations on the parameter over to the key
// so that they can affect downstream emit logic.
//
// TODO: We should double-check whether any of the decorations should
// be moved to the *field* instead.
//
param->transferDecorationsTo(paramFieldKey);
// At this point we want to eliminate the original entry point
// parameter, in favor of the `struct` field we declared.
// That required replacing any uses of the parameter with
// appropriate code to pull out the field.
//
// We *could* extract the field at the start of the shader
// and then do a `replaceAllUsesWith` to propragate it
// down, but in practice we expect that it is better for
// performance to "rematerialize" the value of a shader
// parameter as close to where it is used as possible.
//
// We are therefore going to replace the uses one at a time.
//
while(auto use = param->firstUse )
{
// Given a `use` of the paramter, we will insert
// the replacement code right before the instruction
// that is doing the using.
//
builder->setInsertBefore(use->getUser());
// The way to extract the field that corresponds
// to the parameter depends on whether or not
// we generated a constant buffer.
//
IRInst* fieldVal = nullptr;
if( needConstantBuffer )
{
// A constant buffer behaves like a pointer
// at the IR level, so we first do a pointer
// offset operation to compute what amounts
// to `&cb->field`, and then load from that address.
//
auto fieldAddress = builder->emitFieldAddress(
builder->getPtrType(paramType),
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();
}
fixUpFuncType(func);
}
// We need to be able to determine if a parameter is logically
// a "varying" parameter based on its layout.
//
bool isVaryingParameter(IRVarLayout* 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->getOffsetAttrs() )
{
if(isVaryingResourceKind(resInfo->getResourceKind()))
return true;
}
// TODO(JS): We probably want a more accurate way of determining if system semantic value
// We can use the flags Flag::SemanticValue for one. But main issue with this test, is for some
// targets currently (CPU) no resources are consumed. Perhaps this is fixed elsewhere by using a 'notional' resource.
// 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->getOffsetAttrs().getCount() == 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,
CodeGenTarget target)
{
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();
}
}