diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-02-10 10:25:29 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-02-10 10:25:29 -0800 |
| commit | 60dfb62e638a06ebdcef27138b63033b828ec2ef (patch) | |
| tree | bd7ecea93209402195ecf9f9bf85646217d9a2bc /source/slang/slang-parameter-binding.cpp | |
| parent | 0eed0125fa5e5f425d546efdc2b284b09ffc2785 (diff) | |
Add attributes to enable dual-source blending on Vulkan (#1210)
This change adds support for the `[[vk::location(...)]]` and `[[vk::index(...)]]` attributes, which can be used together to mark up shader outputs for dual-source blending on Vulkan. HLSL/Slang code like the following:
```hlsl
struct Output
{
[[vk::location(0)]]
float4 a : SV_Target0;
[[vk::location(0), vk::index(1)]]
float4 b : SV_Target1;
}
[shader("fragment")]
Output main(...) { ...}
```
can be used to set up dual-source blending on both D3D and Vulkan APIs. The output GLSL for the above will look something like:
```glsl
layout(location = 0) out vec4 a;
layout(location = 0, index = 1) out vec4 b;
void main() { ... }
```
The more or less straightforward parts of this change were:
* Added new `attribute_syntax` declarations to the stdlib, for `[[vk::location(...)]]` and `[[vk::index(...)]]`
* Added new AST node types for the new attribute cases, sharing a base class so that argument checking can be shared
* Added checks for the arguments to the new attributes in `slang-check-modifier.cpp` (eventually this kind of logic shouldn't be needed for new attributes)
* Updated GLSL emit logic so that it treats the `index`/`space` parts of a variable layout as the `location`/`index` for varying parameters.
* Updated GLSL legalization so that when it translates entry-point parameters into globals (and scalarizes structures) it handles both a binding index and space for the parameters.
* Added a cross-compilation test case to verify that the basics of the feature work
The remaining work is all in `slang-parameter-binding.cpp`.
There is some work that isn't technically related to this change (and which could be reverted if it causes problems), around the detection and handling of fragment shader outputs with `SV_Target` semantics. The basic changes (which could be backed out and then merged separately) are:
* Made the special-case `SV_Target` logic only trigger for fragment shaders (that is the only place where `SV_Target` should appear, but we weren't guarding against it)
* Made the logic to reserve a `u<N>` register for `SV_Target<N>` only trigger for D3D Shader Model 5.0 and below (since it is not required for SM 5.1 and up). This could be a breaking change for some users, but that seems unlikely.
* Fixed one test case that relied on the behavior of reserving `u0` for `SV_Target0` even though it was a SM6.0 test.
* Also added more comments to the system-value handling logic.
The more interesting changes come up starting in `processEntryPointVaryingParameterDecl()`. The basic issue is that we have so far only supported implicit layout for varying parameters on GLSL/Vulkan, but the `[[vk::location(...)]]` attribute is a form of explicit layout annotation. Rather than try to kludge something that only works in narrow cases, I instead opted to try to fix things more generally.
In `processEntryPointVaryingParameterDecl()` we now check for the `location` and `index` attributes when we are on "Khronos" targets (Vulkan/OpenGL/GLSL) and immediately add them to the variable layout being constructed if they are found. There is nothing in this logic specific to fragment-shader outputs, so this feature now applies to any varying input/output on Khronos targets.
Allowing explicit layouts creates the potential for mixing implicit and explicit layout. For example, consider:
```hlsl
struct Output
{
float4 color : COLOR;
[[vk::location(0)]] float3 normal : NORMAL;
}
```
What `location` should `color` get? Should this code be an error? There are two cases where this conundrum can come up: when working with `struct` types used for varying parameters, and the entry-point parameter list itself.
For the varying `struct` case we currently make an expedient choice. We handle fields with both implicit or explicit layotu with appropriate logic, but logic that doesn't account for the case of mixing the two. Then at the end of layout for the `struct` we issue an error if there was a mix of implicit and explicit layout (such that our results aren't likely to be valid).
For the entry point varying parameter case, things were already using a `ScopeLayoutBuilder` type (that encapsulates some logic shared between entry-point and global parameters). The entry-point-specific bits were moved out into a `SimpleScopeLayoutBuilder` and it was updated so that rather than assuming all parameters use implicit layout it does a two-phase layout approach similar to what we use for the global scope:
* First all parameters are enumerated to collect explicit bindings and mark certain ranges as "used"
* Next the parameters are enumerated again and those without explicit bindings get allocated space using a "first fit" algorithm
In principle we could extend the two-phase approach to apply to `struct` types as well, but that would be best saved for a future refactoring of some of this parameter binding logic, since I would like to exploit more of the opportunities for sharing code across the uniform/varying and struct/entry-point/global cases.
By moving the point where entry point parameters get their offsets assigned, it was necessary to move around some of the logic that removes varying parameter usage (and other things that shouldn't "leak" out of an entry point) to a different point in the entry point layout process.
While adding these various pieces does not quite enable us to support explicit bindings on entry point parameters (e.g., putting `uniform Texture2D t : register(t0)` in an entry point parameter list) or in `struct` types (e.g., explicit `packoffset` annotations on fields), it starts to provide some of the infrastructure that we'd need in order to support those cases.
Diffstat (limited to 'source/slang/slang-parameter-binding.cpp')
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 550 |
1 files changed, 431 insertions, 119 deletions
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index a6bd52122..5cd89bd95 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -1393,68 +1393,112 @@ static RefPtr<TypeLayout> processSimpleEntryPointParameter( String sn = semanticName.toLower(); RefPtr<TypeLayout> typeLayout; + + // First we check for a system-value semantic, operating + // under the assumption that *any* semantic with an `SV_` + // or `NV_` prefix is a system value. + // if (sn.startsWith("sv_") || sn.startsWith("nv_")) { - // System-value semantic. - - if (state.directionMask & kEntryPointParameterDirection_Output) + // Fragment shader color/render target outputs need to be handled + // specially, because they are declared with an `SV`-prefixed + // "system value" semantic, but in practice they are ordinary + // user-defined outputs. + // + // TODO: We should consider allowing fragment-shader outputs + // with arbitrary semantics, and simply treat them as if + // they were declared with `SV_Target`. + // + if( (state.directionMask & kEntryPointParameterDirection_Output) + && (state.stage == Stage::Fragment) + && (sn == "sv_target") ) { - // Note: I'm just doing something expedient here and detecting `SV_Target` - // outputs and claiming the appropriate register range right away. + // Note: For D3D shader models 5.0 and below, each `SV_Target<N>` + // output conflicts with UAV register `u<N>`. // - // TODO: we should really be building up some representation of all of this, - // once we've gone to the trouble of looking it all up... - if( sn == "sv_target" ) + if( isD3DTarget(context->getTargetRequest()) ) { - // TODO: construct a `ParameterInfo` we can use here so that - // overlapped layout errors get reported nicely. - - auto usedResourceSet = findUsedRangeSetForSpace(context, 0); - usedResourceSet->usedResourceRanges[int(LayoutResourceKind::UnorderedAccess)].Add(nullptr, semanticIndex, semanticIndex + semanticSlotCount); - - - // We also need to track this as an ordinary varying output from the stage, - // since that is how GLSL will want to see it. - // - typeLayout = getSimpleVaryingParameterTypeLayout( - context->layoutContext, - type, - kEntryPointParameterDirection_Output); + auto version = context->getTargetRequest()->targetProfile.GetVersion(); + if( version <= ProfileVersion::DX_5_0 ) + { + // We will address the conflict here by claiming the corresponding + // `u` register. + // + // Note: because entry point parameters get processed *before* + // registers get assigned to global-scope parameters, this + // allocation will prevent register `u<N>` from being auto-assigned + // to any global parameter. + // + // TODO: construct a `ParameterInfo` we can use here so that + // overlapped layout errors get reported nicely. + // + auto usedResourceSet = findUsedRangeSetForSpace(context, 0); + usedResourceSet->usedResourceRanges[int(LayoutResourceKind::UnorderedAccess)].Add(nullptr, semanticIndex, semanticIndex + semanticSlotCount); + } } - } - if (state.directionMask & kEntryPointParameterDirection_Input) - { - if (sn == "sv_sampleindex") - { - state.isSampleRate = true; - } + // A fragment shader output is effectively a user-defined output, + // even if it was declared with `SV_Target`. + // + typeLayout = getSimpleVaryingParameterTypeLayout( + context->layoutContext, + type, + kEntryPointParameterDirection_Output); } - - if( !typeLayout ) + else { - // If we didn't compute a special-case layout for the - // system-value parameter (e.g., because it was an - // `SV_Target` output), then create a default layout - // that consumes no input/output varying slots. - // (since system parameters are distinct from - // user-defined parameters for layout purposes) + // For a system-value parameter (that didn't match the + // `SV_Target` special case above) we create a default + // layout that consumes no input/output varying slots. + // + // The rationale here is that system parameters are distinct + // form user-defined parameters for layout purposes, and + // in particular should not be assigned `location`s on + // GLSL-based targets. // typeLayout = getSimpleVaryingParameterTypeLayout( context->layoutContext, type, 0); + + // We need to compute whether an entry point consumes + // any sample-rate inputs, and along with explicitly + // `sample`-qualified parameters, we also need to + // detect use of `SV_SampleIndex` as an input. + // + if (state.directionMask & kEntryPointParameterDirection_Input) + { + if (sn == "sv_sampleindex") + { + state.isSampleRate = true; + } + } } - // Remember the system-value semantic so that we can query it later + // For any case of a system-value semantic (including `SV_Target`) + // we record the system-value semantic so it can be queried + // via reflection. + // + // TODO: We might want to consider skipping this step for + // `SV_Target` outputs and treating them consistently as + // just user-defined outputs. + // if (varLayout) { varLayout->systemValueSemantic = semanticName; varLayout->systemValueSemanticIndex = semanticIndex; } - // TODO: add some kind of usage information for system input/output + // TODO: We might want to consider tracking some kind of usage + // information for system inputs/outputs. In particular, it + // would be good to check for and diagnose overlapping system + // value declarations. + + // TODO: We should eventually be checking that system values + // are appropriate to the stage that they appear on, and also + // map the system value semantic string over to an `enum` + // type of known/supported system value semantics. } else { @@ -1483,6 +1527,18 @@ static RefPtr<TypeLayout> processSimpleEntryPointParameter( return typeLayout; } + /// Compute layout information for an entry-point parameter `decl`. + /// + /// This function should be used for a top-level entry point varying + /// parameter or a field of a structure used for varying parameters, + /// but *not* for any recursive case that operates on a type without + /// an associated declaration (e.g., recursing on `X` when dealing + /// with a parameer of type `X[]`). + /// + /// This function is responsible for processing any atributes or + /// other modifiers on the declaration that should impact out layout + /// is computed. + /// static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl( ParameterBindingContext* context, Decl* decl, @@ -1490,13 +1546,46 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl( EntryPointParameterState const& inState, RefPtr<VarLayout> varLayout) { - SimpleSemanticInfo semanticInfo; - int semanticIndex = 0; + // One of our responsibilities when recursing through varying + // parameters is to compute the semantic name/index for each + // parameter. + // + // Semantics can either be declared per field/parameter: + // + // struct Output + // { + // float4 a : A; + // float4 b : B; + // } + // + // or they can be applied to an entire aggregate type: + // + // void entryPoint(out Output o : OUTPUT) { ... } + // + // When these both of the above cases apply to a + // leaf parameter/field, then the policy is that the + // "outer-most" semantic wins. Thus in the case above, + // `o.a` gets semantic `OUTPUT0` and `o.b` gets semantic + // `OUTPUT1`. + // By default the state we use for processing the + // parameter/field `decl` will be the state that was + // inherited from the outer context (if any). + // EntryPointParameterState state = inState; - // If there is no explicit semantic already in effect, *and* we find an explicit - // semantic on the associated declaration, then we'll use it. + // If there is already a semantic name coming from the + // outer context, we will use it, but if there is no + // outer semantic *and* the current field/parameter `decl` + // has an explicit semantic, we will use that. + // + // Note: we allocate the storage for the variables that + // will track the semantic state outside the conditional, + // so that they are in scope for the recusrivse call + // coming up. + // + SimpleSemanticInfo semanticInfo; + int semanticIndex = 0; if( !state.optSemanticName ) { if( auto semantic = decl->FindModifier<HLSLSimpleSemantic>() ) @@ -1509,6 +1598,12 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl( } } + // One of our tasks is to track whether a fragment shader + // has any sample-rate varying inputs. To that end, we + // will pass down a marker if this parameter was declared + // with the `sample` modifier, so that we can detect + // sample-rate inputs at the leaves. + // if (decl) { if (decl->FindModifier<HLSLSampleModifier>()) @@ -1517,11 +1612,89 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl( } } - // Default case: either there was an explicit semantic in effect already, - // *or* we couldn't find an explicit semantic to apply on the given - // declaration, so we will just recursive with whatever we have at - // the moment. - return processEntryPointVaryingParameter(context, type, state, varLayout); + // With the state to use for assigning semantics computed, + // we now do processing that depends on the type of + // the parameter, which may involve recursing into its + // fields. + // + // The result of this step is the type layout to use for + // our field/parameter `decl` in this context. + // + auto typeLayout = processEntryPointVaryingParameter(context, type, state, varLayout); + + // For Khronos targets (OpenGL and Vulkan), we need to process + // the `[[vk::location(...)]]` and `[[vk::index(...)]]` attributes, + // if present. + // + // TODO: In principle we should *also* be using the data from + // `SV_Target<N>` semantics as an equivalent to `location = <N>` + // when targetting Vulkan. Right now we are kind of skating by + // on the fact that people almost always declare `SV_Target`s + // in numerical order, so that our automatic assignment of + // `location`s in declaration order coincidentally matches + // the `SV_Target` order. + // + if( isKhronosTarget(context->getTargetRequest()) ) + { + if( auto locationAttr = decl->FindModifier<GLSLLocationAttribute>() ) + { + int location = locationAttr->value; + + int index = 0; + if( auto indexAttr = decl->FindModifier<GLSLIndexAttribute>() ) + { + index = indexAttr->value; + } + + // TODO: We should eventually include validation that a non-zero + // `vk::index` is only valid for fragment shader color outputs. + + // Once we've extracted the data from the attribute(s), we + // need to apply it to the `varLayout` for the parameter/field `decl`. + // + LayoutResourceKind kinds[] = { LayoutResourceKind::VaryingInput, LayoutResourceKind::VaryingOutput }; + for( auto kind : kinds ) + { + auto typeResInfo = typeLayout->FindResourceInfo(kind); + if(!typeResInfo) + continue; + + auto varResInfo = varLayout->findOrAddResourceInfo(kind); + varResInfo->index = location; + + // Note: OpenGL and Vulkan represent dual-source color blending + // differently from multiple render targets (MRT) at the source + // level. + // + // When using MRT, GLSL (and thus SPIR-V) looks like this: + // + // layout(location = 0) vec4 a; + // layout(location = 1) vec4 b; + // + // When using dual-source blending the GLSL/SPIR-V looks like: + // + // layout(location = 0) vec4 a; + // layout(location = 0, index = 1) vec4 b; + // + // Thus for a parameter of kind `VaryingOutput` when targetting + // GLSL/SPIR-V, we need a way to encode the value that was pased + // for `index` on the secondary color output. + // + // We are already using the `index` field in the `VarLayout::ResourceInfo` + // to store what GLSL/SPIR-V calls the "location," so we will + // hijack the `space` field (which is usually unused for varying + // parameters) to store the GLSL/SPIR-V "index" value. + // + varResInfo->space = index; + } + } + else if( auto indexAttr = decl->FindModifier<GLSLIndexAttribute>() ) + { + getSink(context)->diagnose(indexAttr, Diagnostics::vkIndexWithoutVkLocation, decl->getName()); + } + } + + return typeLayout; } static RefPtr<TypeLayout> processEntryPointVaryingParameter( @@ -1721,12 +1894,31 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( RefPtr<StructTypeLayout> structLayout = new StructTypeLayout(); structLayout->type = type; - // Need to recursively walk the fields of the structure now... + // We will recursively walk the fields of a `struct` type + // to compute layouts for those fields. + // + // Along the way, we may find fields with explicit layout + // annotations, along with fields that have no explicit + // layout. We will consider it an error to have a mix of + // the two. + // + // TODO: We could support a mix of implicit and explicit + // layout by performing layout on fields in two passes, + // much like is done for the global scope. This would + // complicate layout significantly for little practical + // benefit, so it is very much a "nice to have" rather + // than a "must have" feature. + // + Decl* firstExplicit = nullptr; + Decl* firstImplicit = nullptr; for( auto field : GetFields(structDeclRef) ) { RefPtr<VarLayout> fieldVarLayout = new VarLayout(); fieldVarLayout->varDecl = field; + structLayout->fields.add(fieldVarLayout); + structLayout->mapVarToLayout.Add(field.getDecl(), fieldVarLayout); + auto fieldTypeLayout = processEntryPointVaryingParameterDecl( context, field.getDecl(), @@ -1734,22 +1926,52 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( state, fieldVarLayout); - if(fieldTypeLayout) + SLANG_ASSERT(fieldTypeLayout); + if(!fieldTypeLayout) + continue; + fieldVarLayout->typeLayout = fieldTypeLayout; + + // The field needs to have offset information stored + // in `fieldVarLayout` for every kind of resource + // consumed by `fieldTypeLayout`. + // + for(auto fieldTypeResInfo : fieldTypeLayout->resourceInfos) { - fieldVarLayout->typeLayout = fieldTypeLayout; + SLANG_RELEASE_ASSERT(fieldTypeResInfo.count != 0); + auto kind = fieldTypeResInfo.kind; - for (auto rr : fieldTypeLayout->resourceInfos) - { - SLANG_RELEASE_ASSERT(rr.count != 0); + auto structTypeResInfo = structLayout->findOrAddResourceInfo(kind); - auto structRes = structLayout->findOrAddResourceInfo(rr.kind); - fieldVarLayout->findOrAddResourceInfo(rr.kind)->index = structRes->count.getFiniteValue(); - structRes->count += rr.count; + auto fieldResInfo = fieldVarLayout->FindResourceInfo(kind); + if( !fieldResInfo ) + { + if(!firstImplicit) firstImplicit = field; + + // In the implicit-layout case, we assign the field + // the next available offset after the fields that + // have preceded it. + // + fieldResInfo = fieldVarLayout->findOrAddResourceInfo(kind); + fieldResInfo->index = structTypeResInfo->count.getFiniteValue(); + structTypeResInfo->count += fieldTypeResInfo.count; + } + else + { + if(!firstExplicit) firstExplicit = field; + + // In the explicit case, the field already has offset + // information, and we just need to update the computed + // size of the `struct` type to account for the field. + // + auto fieldEndOffset = fieldResInfo->index + fieldTypeResInfo.count; + structTypeResInfo->count = maximum(structTypeResInfo->count, fieldEndOffset); } - } - structLayout->fields.add(fieldVarLayout); - structLayout->mapVarToLayout.Add(field.getDecl(), fieldVarLayout); + } + } + if( firstImplicit && firstExplicit ) + { + getSink(context)->diagnose(firstImplicit, Diagnostics::mixingImplicitAndExplicitBindingForVaryingParams, firstImplicit->getName(), firstExplicit->getName()); } return structLayout; @@ -1992,44 +2214,6 @@ struct ScopeLayoutBuilder } - // Add a "simple" parameter that cannot have any user-defined - // register or binding modifiers, so that its layout computation - // can be simplified greatly. - // - void addSimpleParameter( - RefPtr<VarLayout> varLayout) - { - // The main `addParameter` logic will deal with any ordinary/uniform data, - // and with the "pending" part of the layout. - // - addParameter(varLayout); - - // That leaves us to deal with the resource usage that isn't - // handled by `addParameter`. - // - auto paramTypeLayout = varLayout->getTypeLayout(); - for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) - { - // We need to skip ordinary/uniform data because it was - // handled by `addParameter`. - // - if(paramTypeResInfo.kind == LayoutResourceKind::Uniform) - continue; - - // Whatever resources the parameter uses, we need to - // assign the parameter's location/register/binding offset to - // be the sum of everything added so far. - // - auto scopeResInfo = m_structLayout->findOrAddResourceInfo(paramTypeResInfo.kind); - varLayout->findOrAddResourceInfo(paramTypeResInfo.kind)->index = scopeResInfo->count.getFiniteValue(); - - // We then need to add the resources consumed by the parameter - // to those consumed by the scope. - // - scopeResInfo->count += paramTypeResInfo.count; - } - } - RefPtr<VarLayout> endLayout() { // Finish computing the layout for the ordindary data (if any). @@ -2069,6 +2253,121 @@ struct ScopeLayoutBuilder } }; +// Scope layout builder specialized to the case of "simple" +// scopes (more or less everything but the global scope) +// +struct SimpleScopeLayoutBuilder : ScopeLayoutBuilder +{ + typedef ScopeLayoutBuilder Super; + + // Add a "simple" parameter that cannot have any user-defined + // register or binding modifiers, so that its layout computation + // can be simplified greatly. + // + void addSimpleParameter( + RefPtr<VarLayout> varLayout) + { + // The main `addParameter` logic will deal with any ordinary/uniform data, + // and with the "pending" part of the layout. + // + addParameter(varLayout); + + // That leaves us to deal with the resource usage that isn't + // handled by `addParameter`, which we will defer until + // `endLayout()` is called. + } + + RefPtr<VarLayout> endLayout() + { + // In order to support a mix of parameters with explicit + // and implicit layout, we will process the parameters in + // two phases. + // + // In the first phase we will collect information about + // resource ranges already claimed by parameters in the + // scope. + // + UsedRanges usedRangeSet[kLayoutResourceKindCount]; + for( auto paramVarLayout : m_structLayout->fields ) + { + auto paramTypeLayout = paramVarLayout->getTypeLayout(); + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) + { + auto kind = paramTypeResInfo.kind; + + // We will look for an explicit/existing binding in + // the parameter var layout, which would represent + // an explicit binding, and skip the parameter if + // we don't find one. + // + auto paramResInfo = paramVarLayout->FindResourceInfo(kind); + if(!paramResInfo) + continue; + + // If we found an explicit binding, then we need + // to add it to our set for tracking. + // + auto startOffset = paramResInfo->index; + auto endOffset = startOffset + paramTypeResInfo.count; + usedRangeSet[int(kind)].Add(paramVarLayout, startOffset, endOffset); + } + } + // + // Next we iterate over the parameters again, and assign + // unused ranges to all of those that didn't have ranges + // explicitly bound. + // + for( auto paramVarLayout : m_structLayout->fields ) + { + auto paramTypeLayout = paramVarLayout->getTypeLayout(); + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) + { + auto kind = paramTypeResInfo.kind; + + // We only care about parameters that are not already + // explicitly bound, so we will skip those that already + // have offset information for `kind`. + // + auto paramResInfo = paramVarLayout->FindResourceInfo(kind); + if(paramResInfo) + continue; + paramResInfo = paramVarLayout->findOrAddResourceInfo(kind); + + paramResInfo->index = usedRangeSet[int(kind)].Allocate(paramVarLayout, paramTypeResInfo.count.getFiniteValue()); + } + } + // + // Finally, we need to compute the overall resource usage of + // the scope/aggregate, so that it includes the ranges consumed + // by all of the parameters/fields. + // + for( auto paramVarLayout : m_structLayout->fields ) + { + auto paramTypeLayout = paramVarLayout->getTypeLayout(); + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) + { + auto kind = paramTypeResInfo.kind; + + auto paramResInfo = paramVarLayout->FindResourceInfo(kind); + SLANG_ASSERT(paramResInfo); + if(!paramResInfo) continue; + + auto startOffset = paramResInfo->index; + auto endOffset = startOffset + paramTypeResInfo.count; + + auto scopeResInfo = m_structLayout->findOrAddResourceInfo(paramTypeResInfo.kind); + scopeResInfo->count = maximum(scopeResInfo->count, endOffset); + } + } + + // Once we are done providing explicit offsets for all the parameters, + // we can defer to the base `ScopeLayoutBuilder` logic to decide + // whether to allocate a default constant buffer or anything like that. + // + return Super::endLayout(); + } +}; + /// Helper routine to allocate a constant buffer binding if one is needed. /// /// This function primarily exists to encapsulate the logic for allocating @@ -2112,6 +2411,19 @@ static void removePerEntryPointParameterKinds( typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam); } +static void removePerEntryPointParameterKinds( + VarLayout* varLayout) +{ + removePerEntryPointParameterKinds(varLayout->typeLayout); + + varLayout->removeResourceUsage(LayoutResourceKind::VaryingInput); + varLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput); + varLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord); + varLayout->removeResourceUsage(LayoutResourceKind::HitAttributes); + varLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam); + varLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam); +} + /// Iterate over the parameters of an entry point to compute its requirements. /// static RefPtr<EntryPointLayout> collectEntryPointParameters( @@ -2187,7 +2499,7 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // in the parameter list (e.g., a `uniform float4x4 mvp` parameter), // which is what the `ScopeLayoutBuilder` is designed to help with. // - ScopeLayoutBuilder scopeBuilder; + SimpleScopeLayoutBuilder scopeBuilder; scopeBuilder.beginLayout(context); auto paramsStructLayout = scopeBuilder.m_structLayout; paramsStructLayout->type = entryPointType; @@ -2255,26 +2567,6 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // scopeBuilder.addSimpleParameter(paramVarLayout); } - - // We don't want certain kinds of resource usage within an entry - // point to "leak" into the overall resource usage of the entry - // point and thus lead to offsetting of successive entry points. - // - // For example if we have a vertex and a fragment entry point - // in the some program, and each has one varying input, then - // the both the vertex and fragment varying outputs should have - // a location/index of zero. It would be bad if the fragment - // input (or whichever entry point comes second in the global - // ordering) started at location one, because then it wouldn't - // line up correctly with any vertex stage outputs. - // - // We handle this with a bit of a kludge, by removing the - // particular `LayoutResourceKind`s that are susceptible to - // this problem from the overall resource usage of the entry - // point. - // - removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); - entryPointLayout->parametersLayout = scopeBuilder.endLayout(); // For an entry point with a non-`void` return type, we need to process the @@ -2316,6 +2608,26 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( entryPointLayout->resultLayout = resultLayout; } + // We don't want certain kinds of resource usage within an entry + // point to "leak" into the overall resource usage of the entry + // point and thus lead to offsetting of successive entry points. + // + // For example if we have a vertex and a fragment entry point + // in the some program, and each has one varying input, then + // the both the vertex and fragment varying outputs should have + // a location/index of zero. It would be bad if the fragment + // input (or whichever entry point comes second in the global + // ordering) started at location one, because then it wouldn't + // line up correctly with any vertex stage outputs. + // + // We handle this with a bit of a kludge, by removing the + // particular `LayoutResourceKind`s that are susceptible to + // this problem from the overall resource usage of the entry + // point. + // + removePerEntryPointParameterKinds(paramsStructLayout); + removePerEntryPointParameterKinds(entryPointLayout->parametersLayout); + return entryPointLayout; } |
