Skip to content

Commit f20c64c

Browse files
author
Tim Foley
authored
Initial support for uniform parameters on entry points (shader-slang#815)
* Initial support for uniform parameters on entry points The basic feature this work adds is the ability to define a shader entry point like: ```hlsl [shader("fragment")] float4 main( uniform Texture2D t, uniform SamplerState s, float2 uv : UV) { return t.Sample(s,uv); } ``` In this example, the `uniform` keyword is used to mark that the given entry point parameters are *not* varying input/output flowing through the pipeline, but rather uniform shader parameters that should function as if the shader was declared more like: ```hlsl Texture2D t, SamplerState s, [shader("fragment")] float4 main( float2 uv : UV) { return t.Sample(s,uv); } ``` Allowing `uniform` parameters on entry points makes it easier to define multiple entry points in one file without accidentally polluting the global scope with shader parameters that only certain entry points care about. This feature is also more or less a prerequisite for allowing generic type parameters directly on entry point functions, since the main use case for those type parameters is for determining what goes in various `ConstantBuffer`s or `ParameterBlock`s. There are two main pieces to the implementation. First, we need to be able to compute appropriate layout information for entry points that include `uniform` parameters. Second, we need to transform the entry point function to move any `uniform` parameters to be ordinary global-scope shader parameters, to make sure that all other back-end passes don't need to worry about this special case. The latter piece of the implementation is, relatively speaking, simpler. The pass in `ir-entry-point-uniforms.{h,cpp}` converts entry point parameters that are determined to be uniform (using the already-computed layout information) into fields of a `struct` type and then declares a global shader parameter based on that `struct` type (and applies already-computed layout information to that parameter). After that, the remaining IR passes (notably including type legalization) will handle things just as for any other global shader parameter. The changes to the layout step are more significant, but most of the changes are just cleanups and fixes to enable the feature. The two major changes that enable entry-point `uniform` parameters are: * In `collectEntryPointParameters` we now dispatch out to a new `computeEntryPointParameterTypeLayout` function, which decided whether to compute the type layout for a `uniform` parameter, or for a varying parameter (what used to be the default behavior handled by `processEntryPointParameterDecl`). * The main `generateParameterBindings` routine was extended so that it allocates registers/bindings to the resources required by each entry point (using `completeBindingsForParameter`) after it has allocated registers/binding to all of the global-scope parameters (this addition is mirrored in `specializeProgramLayout`). The effect of these changes is that the `uniform` parameters of any entry points specified in a compile request will be laid out after the global-scope parameters, in the order the entry points were specified in the compile request. A bunch of smaller changes were made around parameter layout that are worth enumerating so that the diffs make some sense: * The `EntryPointLayout` type was changed so that instead of trying to *be* a `StructTypeLayout`, it instead *owns* one, in the same fashion as `ProgramLayout`. This commonality was factored into a base class `ScopeLayout`, and a bunch of edits followed from that change. * Because `uniform` parameters are moved out of the entry point parameter list early in the IR transformations, the logic in `ir-glsl-legalize.cpp` that tried to look up parameter layout information by index would no longer work if the entry point parameter list had been altered. Instead, that logic now looks for the decorations directly on the parameters. * The `UsedRange` type in `parameter-binding.cpp` was tracking the existing parameter associated with a range using a `ParameterInfo*` (which accounts for the possibility of multiple `VarDecl`s mapping to the same logical shader parameter), when just using a `VarLayout*` is sufficient for all current use cases. The overhead of allocating a `ParameterInfo` seems like overkill for entry-point parameters, where there can't possibly be multiple declarations of the "same" parameter, so avoiding these overheads was a focus when trying to deduplicate code between the global and entry-point parameter cases. * A bunch of parameter binding logic that was specific to GLSL input has been deleted completely. There was no way to even execute this code in the compiler today, and there is pretty much zero chance of us needing (or wanting) to deal with GLSL input in the future. This includes custom `UsedRangeSet`s specific to each translation unit, which were only needed for global-scope `in` and `out` varying declarations in GLSL. * A bunch of functions with `EntryPointParameter` in their names were renamed to use `EntryPointVaryingParameter` to help distinguish that they only apply to the varying case, while entry point `uniform` parameters are handled elsewhere. * The `completeBindingsForParameter` function was re-worked into something that can be used for both global-scope shader parameters (where we have a `ParameterInfo` and possibly explicit bindings) and entry-point parameters (where we expect to have neither). This helps unify the (fairly subtle) logic for how we allocate and assign bindings for resources, constant buffers, parameter blocks, etc. * A small change was made so that the entry-point stage is attached directly to top-level parameters of the entry point, and *not* recursively to every field along the way. This could be a breaking change for some applications, but it makes more logical sense (to me); we'll have to check if this affects Falcor. This change produces different output for several of the reflection tests, but the changes are consistent with no longer attaching stage information to sub-fields of varying `struct`-type parameters. * Because there is a bunch of repeated logic in `parameter-binding.cpp` that has to do with computing a `struct` layout for ordinary/uniform data, I tried to factor that into a single `ScopeLayoutBuilder` type, which handles computing the offsets for any parameters with ordinary data, and then also handles wrapping up the layout in a constant buffer layout if there was any ordinary data at the end. * A similar convenience routine `maybeAllocateConstantBufferBinding` was added because I noticed multiple places in `parameter-binding.cpp` that were trying to allocate a constant buffer binding for global uniforms, and they were wildly inconsistent (and in most cases used logic that would only work for D3D). * The main `generateParameterBindings` routine is significantly shortened by using all of these utilities that were introduced. I tried to comment the places that changed to explain the overall flow correctly. * The `specializeProgramLayout` routine (used to take a `ProgramLayout` from `generateParameterBindings` and specialize it based on knowledge of global generic arguments) had basically been rewritten with more explicit commenting/rationale for what happens in each step. It makes use of the same shared utilities as `generateParameterBindings` and `collectEntryPointParameters`. In terms of testing: * I added a test case to specifically test the new behavior, and in particular I made sure to include a mix of both global and entry-point parameters and also to have entry-point parameters of both ordinary and resource/object types. * I tweaked an existing test for global type parameters to use an entry-point `uniform` parameter instead of a global one, in an effort to migrate it toward being able to use an explicitly generic entry point. * fixups from merge
1 parent 11c547d commit f20c64c

18 files changed

+1270
-605
lines changed

source/slang/emit.cpp

+41-31
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "../core/slang-writer.h"
55
#include "ir-dce.h"
6+
#include "ir-entry-point-uniforms.h"
67
#include "ir-glsl-legalize.h"
78
#include "ir-insts.h"
89
#include "ir-link.h"
@@ -6511,41 +6512,28 @@ EntryPointLayout* findEntryPointLayout(
65116512
return nullptr;
65126513
}
65136514

6514-
// Given a layout computed for a whole program, find
6515-
// the corresponding layout to use when looking up
6516-
// variables at the global scope.
6517-
//
6518-
// It might be that the global scope was logically
6519-
// mapped to a constant buffer, so that we need
6520-
// to "unwrap" that declaration to get at the
6521-
// actual struct type inside.
6522-
StructTypeLayout* getGlobalStructLayout(
6523-
ProgramLayout* programLayout)
6515+
/// Given a layout computed for a scope, get the layout to use when lookup up variables.
6516+
///
6517+
/// A scope (such as the global scope of a program) groups its
6518+
/// parameters into a pseudo-`struct` type for layout purposes,
6519+
/// and in some cases that type will in turn be wrapped in a
6520+
/// `ConstantBuffer` type to indicate that the parameters needed
6521+
/// an implicit constant buffer to be allocated.
6522+
///
6523+
/// This function "unwraps" the type layout to find the structure
6524+
/// type layout that must be stored inside.
6525+
///
6526+
StructTypeLayout* getScopeStructLayout(
6527+
ScopeLayout* scopeLayout)
65246528
{
6525-
auto globalScopeLayout = programLayout->globalScopeLayout->typeLayout;
6526-
if( auto gs = as<StructTypeLayout>(globalScopeLayout) )
6529+
auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;
6530+
if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
65276531
{
6528-
return gs;
6532+
return structTypeLayout;
65296533
}
6530-
else if( auto globalConstantBufferLayout = as<ParameterGroupTypeLayout>(globalScopeLayout) )
6534+
else if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
65316535
{
6532-
// TODO: the `cbuffer` case really needs to be emitted very
6533-
// carefully, but that is beyond the scope of what a simple rewriter
6534-
// can easily do (without semantic analysis, etc.).
6535-
//
6536-
// The crux of the problem is that we need to collect all the
6537-
// global-scope uniforms (but not declarations that don't involve
6538-
// uniform storage...) and put them in a single `cbuffer` declaration,
6539-
// so that we can give it an explicit location. The fields in that
6540-
// declaration might use various type declarations, so we'd really
6541-
// need to emit all the type declarations first, and that involves
6542-
// some large scale re orderings.
6543-
//
6544-
// For now we will punt and just emit the declarations normally,
6545-
// and hope that the global-scope block (`$Globals`) gets auto-assigned
6546-
// the same location that we manually assigned it.
6547-
6548-
auto elementTypeLayout = globalConstantBufferLayout->offsetElementTypeLayout;
6536+
auto elementTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
65496537
auto elementTypeStructLayout = as<StructTypeLayout>(elementTypeLayout);
65506538

65516539
// We expect all constant buffers to contain `struct` types for now
@@ -6560,6 +6548,16 @@ StructTypeLayout* getGlobalStructLayout(
65606548
}
65616549
}
65626550

6551+
/// Given a layout computed for a program, get the layout to use when lookup up variables.
6552+
///
6553+
/// This is just an alias of `getScopeStructLayout`.
6554+
///
6555+
StructTypeLayout* getGlobalStructLayout(
6556+
ProgramLayout* programLayout)
6557+
{
6558+
return getScopeStructLayout(programLayout);
6559+
}
6560+
65636561
void legalizeTypes(
65646562
TypeLegalizationContext* context,
65656563
IRModule* module);
@@ -6657,6 +6655,18 @@ String emitEntryPoint(
66576655
// un-specialized IR.
66586656
dumpIRIfEnabled(compileRequest, irModule);
66596657

6658+
// Now that we've linked the IR code, any layout/binding
6659+
// information has been attached to shader parameters
6660+
// and entry points. Now we are safe to make transformations
6661+
// that might move code without worrying about losing
6662+
// the connection between a parameter and its layout.
6663+
//
6664+
// An easy transformation of this kind is to take uniform
6665+
// parameters of a shader entry point and move them into
6666+
// the global scope instead.
6667+
//
6668+
moveEntryPointUniformParamsToGlobalScope(irModule);
6669+
66606670
// Desguar any union types, since these will be illegal on
66616671
// various targets.
66626672
//

0 commit comments

Comments
 (0)