diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-11-21 08:15:33 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-11-21 08:15:33 -0800 |
| commit | 9bb11b69a08c66e2857f439837e2253658aed9a4 (patch) | |
| tree | dd089ff2e60c3074e4a65422acb083c6bf94e67e /source | |
| parent | 7f97f35ee51f829e0b33a2916e651d727a5c51fa (diff) | |
Add support for unbounded arrays as shader parameters (#725)
* Add support for unbounded arrays as shader parameters
With this change, Slang shaders can use unbounded-size arrays as parameters, e.g.:
```hlsl
Texture2D t[] : register(t3, space2);
SamplerState s[];
```
As shown in the above example, Slang supports both explicit `register` declarations on unbounded-size arrays and also implicit binding.
When doing automatic parmaeter binding, Slang will allocate a full register space to an unbounded-size array of textures/smaplers, starting at register zero.
Note that for the Vulkan target, an array of descriptors of any size (including unbounded size) consumes only a single `bindign`, so much of this logic is specific to D3D targets.
Details on the changes made:
* The single biggest change is a new `LayoutSize` type that is used to store a value that can either be a finite unsigned integer or a dedicated "infinite" value (which is stored as the all-bits-set `-1` value). This is used in places where a size could either be a finite value or an "unbounded" value, to both try to make standard math robust against the infinite case, and also to force code to deal with both the finite and infinite cases more explicitly when they care about the difference.
* The public API was documented so that unbounded-size arrays report their size as `-1`. We should probably change this function to return a signed value instead of `size_t`, but that would technically be a source-breaking change, so we want to make sure we stage it appropriately.
* The code that invokes fxc was updated so that it passes the appropriate flag to enable unbounded arrays of descriptors. I haven't looked yet at whether dxc needs such a flag, so there may need to be a follow-on change to add that.
* The logic in the `UsedRanges::Add` method for tracking what registers have been claimed was rewritten because the previous version had some subtle bugs. The new version includes more detailed comments that attempt to explain why I think the new logic works.
* The top-level logic for auto-assigning bindings to parameters has been overhauled to deal with the fact that a parameter that needs "infinite" amounts of a resource should be claiming a full register space for those resources instead. Whenever a parameter allocates any register spaces we want them all to be contiguous, so we have a loop that counts the requirements and allocates the spaces before we go along and dole them out.
* When computing the layout for an array type, we need to carefully deal with unbounded-size arrays. In the case of an unbounded array of a "simple" resource type (e.g., `Texture2D[]`), we opt to expose the type layout as consuming an infinite number of the appropriate register, while in the case of a complex type (say, a `struct` with two texture fields), we need to instead allocate whole spaces for those fields. The logic here is more subtle than I would like, and interacts with the existing code that "adjusts" the element type of an array in order to make standard indexing math Just Work.
* Similarly, when a `struct` type has unbounded-array fields, then we need to transform any field with infinite register requirements to instead consume a space in the resulting aggregate type. This case is comparatively easier than the array case.
* The test case for unbounded arrays covers both explicit and implicit bindings, and also the case of an unbounded array over a `struct` type (it does not cover the case of a `struct` contianing unbounded arrays, so that will need to be added later). For this test we are both validation the output reflection data and that we produce the same code as fxc (with explicit bindings in the fxc case).
* The reflection test app was modified to use the new API contract and detect when a parameter consumes `SLANG_UNBOUNDED_SIZE` resources.
* Fixup: ensure unbounded size is defined at right bit width
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/compiler.cpp | 13 | ||||
| -rw-r--r-- | source/slang/parameter-binding.cpp | 462 | ||||
| -rw-r--r-- | source/slang/reflection.cpp | 19 | ||||
| -rw-r--r-- | source/slang/type-layout.cpp | 318 | ||||
| -rw-r--r-- | source/slang/type-layout.h | 170 |
5 files changed, 785 insertions, 197 deletions
diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 0f0f3c915..cab6355c1 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -419,6 +419,19 @@ namespace Slang break; } + // Some of the `D3DCOMPILE_*` constants aren't available in all + // versions of `d3dcompiler.h`, so we define them here just in case + #ifndef D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES + #define D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES (1 << 20) + #endif + + #ifndef D3DCOMPILE_ALL_RESOURCES_BOUND + #define D3DCOMPILE_ALL_RESOURCES_BOUND (1 << 21) + #endif + + flags |= D3DCOMPILE_ENABLE_STRICTNESS; + flags |= D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES; + ID3DBlob* codeBlob; ID3DBlob* diagnosticsBlob; HRESULT hr = compileFunc( diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp index e156c9690..bc0f5b760 100644 --- a/source/slang/parameter-binding.cpp +++ b/source/slang/parameter-binding.cpp @@ -48,45 +48,165 @@ static bool rangesOverlap(UsedRange const& x, UsedRange const& y) struct UsedRanges { + // The `ranges` array maintains a sorted list of `UsedRange` + // objects such that the `end` of a range is <= the `begin` + // of any range that comes after it. + // + // The values covered by each `[begin,end)` range are marked + // as used, and anything not in such an interval is implicitly + // free. + // + // TODO: if it ever starts to matter for performance, we + // could encode this information as a tree instead of an array. + // List<UsedRange> ranges; // Add a range to the set, either by extending - // an existing range, or by adding a new one... + // existing range(s), or by adding a new one. // // If we find that the new range overlaps with // an existing range for a *different* parameter // then we return that parameter so that the // caller can issue an error. - ParameterInfo* Add(UsedRange const& range) + // + ParameterInfo* Add(UsedRange range) { + // The invariant on entry to this + // function is that the `ranges` array + // is sorted and no two entries in the + // array intersect. We must preserve + // that property as a postcondition. + // + // The other postcondition is that the + // interval covered by the input `range` + // must be marked as consumed. + + // We will try track any parameter associated + // with an overlapping range that doesn't + // match the parameter on `range`, so that + // the compiler can issue useful diagnostics. + // ParameterInfo* newParam = range.parameter; ParameterInfo* existingParam = nullptr; - for (auto& rr : ranges) + + // A clever algorithm might use a binary + // search to identify the first entry in `ranges` + // that might overlap `range`, but we are going + // to settle for being less clever for now, in + // the hopes that we can at least be correct. + // + // Note: we are going to iterate over `ranges` + // using indices, because we may actually modify + // the array as we go. + // + Int rangeCount = ranges.Count(); + for(Int rr = 0; rr < rangeCount; ++rr) { - if (rangesOverlap(rr, range) - && rr.parameter - && rr.parameter != newParam) + auto existingRange = ranges[rr]; + + // The invariant on entry to each loop + // iteration will be that `range` does + // *not* intersect any preceding entry + // in the array. + // + // Note that this invariant might be + // true only because we modified + // `range` along the way. + // + // If `range` does not intertsect `existingRange` + // then our invariant will be trivially + // true for the next iteration. + // + if(!rangesOverlap(existingRange, range)) { - // there was an overlap! - existingParam = rr.parameter; + continue; } - } - for (auto& rr : ranges) - { - if (rr.begin == range.end) + // We now know that `range` and `existingRange` + // intersect. The first thing to do + // is to check if we have a parameter + // associated with `existingRange`, so + // that we can use it for emitting diagnostics + // about the overlap: + // + if( existingRange.parameter + && existingRange.parameter != newParam) { - rr.begin = range.begin; - return existingParam; + // There was an overlap with a range that + // had a parameter specified, so we will + // use that parameter in any subsequent + // diagnostics. + // + existingParam = existingRange.parameter; } - else if (rr.end == range.begin) + + // Before we can move on in our iteration, + // we need to re-establish our invariant by modifying + // `range` so that it doesn't overlap with `existingRange`. + // Of course we also want to end up with a correct + // result for the overall operation, so we can't just + // throw away intervals. + // + // We first note that if `range` starts before `existingRange`, + // then the interval from `range.begin` to `existingRange.begin` + // needs to be accounted for in the final result. Furthermore, + // the interval `[range.begin, existingRange.begin)` could not + // intersect with any range already in the `ranges` array, + // because it comes strictly before `existingRange`, and our + // invariant says there is no intersection with preceding ranges. + // + if(range.begin < existingRange.begin) { - rr.end = range.end; - return existingParam; + UsedRange prefix; + prefix.begin = range.begin; + prefix.end = existingRange.begin; + prefix.parameter = range.parameter; + ranges.Add(prefix); } + // + // Now we know that the interval `[range.begin, existingRange.begin)` + // is claimed, if it exists, and clearly the interval + // `[existingRange.begin, existingRange.end)` is already claimed, + // so the only interval left to consider would be + // `[existingRange.end, range.end)`, if it is non-empty. + // That range might intersect with others in the array, so + // we will need to continue iterating to deal with that + // possibility. + // + range.begin = existingRange.end; + + // If the range would be empty, then of course we have nothing + // left to do. + // + if(range.begin >= range.end) + break; + + // Otherwise, have can be sure that `range` now comes + // strictly *after* `existingRange`, and thus our invariant + // is preserved. + } + + // If we manage to exit the loop, then we have resolved + // an intersection with existing entries - possibly by + // adding some new entries. + // + // If the `range` we are left with is still non-empty, + // then we should go ahead and add it. + // + if(range.begin < range.end) + { + ranges.Add(range); } - ranges.Add(range); + + // Any ranges that got added along the way might not + // be in the proper sorted order, so we'll need to + // sort the array to restore our global invariant. + // ranges.Sort(); + + // We end by returning an overlapping parameter that + // we found along the way, if any. + // return existingParam; } @@ -99,6 +219,15 @@ struct UsedRanges return Add(range); } + ParameterInfo* Add(ParameterInfo* param, UInt begin, LayoutSize end) + { + UsedRange range; + range.parameter = param; + range.begin = begin; + range.end = end.isFinite() ? end.getFiniteValue() : UInt(-1); + return Add(range); + } + bool contains(UInt index) { for (auto rr : ranges) @@ -152,7 +281,7 @@ struct ParameterBindingInfo { size_t space; size_t index; - size_t count; + LayoutSize count; }; enum @@ -1295,7 +1424,7 @@ static void addExplicitParameterBinding( RefPtr<ParameterInfo> parameterInfo, VarDeclBase* varDecl, LayoutSemanticInfo const& semanticInfo, - UInt count, + LayoutSize count, RefPtr<UsedRangeSet> usedRangeSet = nullptr) { auto kind = semanticInfo.kind; @@ -1384,10 +1513,10 @@ static void addExplicitParameterBindings_HLSL( // of the given kind. auto typeRes = typeLayout->FindResourceInfo(kind); - int count = 0; + LayoutSize count = 0; if (typeRes) { - count = (int) typeRes->count; + count = typeRes->count; } else { @@ -1463,7 +1592,7 @@ static void addExplicitParameterBindings_GLSL( auto count = resInfo->count; semanticInfo.kind = kind; - addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, int(count), usedRangeSet); + addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count, usedRangeSet); } // Given a single parameter, collect whatever information we have on @@ -1505,96 +1634,203 @@ static void completeBindingsForParameter( auto firstVarLayout = parameterInfo->varLayouts.First(); auto firstTypeLayout = firstVarLayout->typeLayout; + // We need to deal with allocation of full register spaces first, + // since that is the most complicated bit of logic. + // + // We will compute how many full register spaces the parameter + // needs to allocate, across all the kinds of resources it + // consumes, so that we can allocate a contiguous range of + // spaces. + // + UInt spacesToAllocateCount = 0; for(auto typeRes : firstTypeLayout->resourceInfos) { - // Did we already apply some explicit binding information - // for this resource kind? auto kind = typeRes.kind; + + // We want to ignore resource kinds for which the user + // has specified an explicit binding, since those won't + // go into our contiguously allocated range. + // auto& bindingInfo = parameterInfo->bindingInfo[(int)kind]; if( bindingInfo.count != 0 ) { - // If things have already been bound, our work is done. continue; } - auto count = typeRes.count; - - // We need to special-case the scenario where - // a parameter wants to claim an entire register - // space to itself (for a parameter block), since - // that can't be handled like other resources. - if (kind == LayoutResourceKind::RegisterSpace) + // Now we inspect the kind of resource to figure out + // its space requirements: + // + switch( kind ) { - // We need to snag a register space of our own. - - UInt space = allocateUnusedSpaces(context, count); + default: + // An unbounded-size array will need its own space. + // + if( typeRes.count.isInfinite() ) + { + spacesToAllocateCount++; + } + break; - bindingInfo.count = count; - bindingInfo.index = space; + case LayoutResourceKind::RegisterSpace: + // If the parameter consumes any full spaces (e.g., it + // is a `struct` type with one or more unbounded arrays + // for fields), then we will include those spaces in + // our allocaiton. + // + // We assume/require here that we never end up needing + // an unbounded number of spaces. + // TODO: we should enforce that somewhere with an error. + // + spacesToAllocateCount += typeRes.count.getFiniteValue(); + break; - // TODO: what should we store as the "space" for - // an allocation of register spaces? Either zero - // or `space` makes sense, but it isn't clear - // which is a better choice. - bindingInfo.space = 0; + case LayoutResourceKind::Uniform: + // We want to ignore uniform data for this calculation, + // since any uniform data in top-level shader parameters + // needs to go into a global constant buffer. + // + break; - continue; + case LayoutResourceKind::GenericResource: + // This is more of a marker case, and shouldn't ever + // need a space allocated to it. + break; } - else if (kind == LayoutResourceKind::GenericResource) + } + + // If we compute that the parameter needs some number of full + // spaces allocated to it, then we will go ahead and allocate + // contiguous spaces here. + // + UInt firstAllocatedSpace = 0; + if(spacesToAllocateCount) + { + firstAllocatedSpace = allocateUnusedSpaces(context, spacesToAllocateCount); + } + + // We'll then dole the allocated spaces (if any) out to the resource + // categories that need them. + // + UInt currentAllocatedSpace = firstAllocatedSpace; + + for(auto typeRes : firstTypeLayout->resourceInfos) + { + // Did we already apply some explicit binding information + // for this resource kind? + auto kind = typeRes.kind; + auto& bindingInfo = parameterInfo->bindingInfo[(int)kind]; + if( bindingInfo.count != 0 ) { - bindingInfo.space = 0; - bindingInfo.count = 1; - bindingInfo.index = 0; + // If things have already been bound, our work is done. + // + // TODO: it would be good to handle the case where a + // binding specified a space, but not an offset/index + // for some kind of resource. + // continue; } - // Auto-generated bindings will all go in the same space, - // which was allocated up front. - // - // We don't currently worry about running out of room in - // this space; if the user declares enough parameters - // to overflow the range then we will have other problems - // on our hands. - // - // The one case that might seem like a challenge is unsized - // arrays, since these conceptually require a (countably) - // infinite register range. - // - // This turns out not to be a problem that this code - // needs to handle, for two reasons: - // - // 1) In the D3D case, an unbounded-size array should be - // computed to require one (or more) whole register spaces, - // and so we'd end up in the `RegisterSpace` case above. + auto count = typeRes.count; + + // Certain resource kinds require special handling. // - // 2) In the Vulkan case, an unbounded-size array of - // resources still uses only a single binding, so we - // won't run out of space. + // Note: This `switch` statement should have a `case` for + // all of the special cases above that affect the computation of + // `spacesToAllocateCount`. // - UInt space = context->shared->defaultSpace; - - RefPtr<UsedRangeSet> usedRangeSet; - switch (kind) + switch( kind ) { - default: - usedRangeSet = findUsedRangeSetForSpace(context, space); - break; + case LayoutResourceKind::RegisterSpace: + { + // The parameter's type needs to consume some number of whole + // register spaces, and we have already allocated a contiguous + // range of spaces above. + // + // As always, we can't handle the case of a parameter that needs + // an infinite number of spaces. + // + SLANG_ASSERT(count.isFinite()); + bindingInfo.count = count; + + // We will use the spaces we've allocated, and bump + // the variable tracking the "current" space by + // the number of spaces consumed. + // + bindingInfo.index = currentAllocatedSpace; + currentAllocatedSpace += count.getFiniteValue(); + + // TODO: what should we store as the "space" for + // an allocation of register spaces? Either zero + // or `space` makes sense, but it isn't clear + // which is a better choice. + bindingInfo.space = 0; - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit); + continue; + } + + case LayoutResourceKind::GenericResource: + { + // `GenericResource` is somewhat confusingly named, + // but simply indicates that the type of this parameter + // in some way depends on a generic parameter that has + // not been bound to a concrete value, so that asking + // specific questions about its resource usage isn't + // really possible. + // + bindingInfo.space = 0; + bindingInfo.count = 1; + bindingInfo.index = 0; + continue; + } + + case LayoutResourceKind::Uniform: + // TODO: we don't currently handle global-scope uniform parameters. break; } - bindingInfo.count = count; - bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(parameterInfo, (int) count); + // At this point, we know the parameter consumes some resource + // (e.g., D3D `t` registers or Vulkan `binding`s), and the user + // didn't specify an explicit binding, so we will have to + // assign one for them. + // + // If we are consuming an infinite amount of the given resource + // (e.g., an unbounded array of `Texure2D` requires an infinite + // number of `t` regisers in D3D), then we will go ahead + // and assign a full space: + // + if( count.isInfinite() ) + { + bindingInfo.count = count; + bindingInfo.index = 0; + bindingInfo.space = currentAllocatedSpace; + currentAllocatedSpace++; + } + else + { + // If we have a finite amount of resources, then + // we will go ahead and allocate from the "default" + // space. - bindingInfo.space = space; - } + UInt space = context->shared->defaultSpace; - if (firstTypeLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) - { + RefPtr<UsedRangeSet> usedRangeSet; + switch (kind) + { + default: + usedRangeSet = findUsedRangeSetForSpace(context, space); + break; + + case LayoutResourceKind::VertexInput: + case LayoutResourceKind::FragmentOutput: + usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit); + break; + } + bindingInfo.count = count; + bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(parameterInfo, count.getFiniteValue()); + + bindingInfo.space = space; + } } // At this point we should have explicit binding locations chosen for @@ -2055,7 +2291,7 @@ static RefPtr<TypeLayout> processEntryPointParameter( SLANG_RELEASE_ASSERT(rr.count != 0); auto structRes = structLayout->findOrAddResourceInfo(rr.kind); - fieldVarLayout->findOrAddResourceInfo(rr.kind)->index = structRes->count; + fieldVarLayout->findOrAddResourceInfo(rr.kind)->index = structRes->count.getFiniteValue(); structRes->count += rr.count; } } @@ -2073,7 +2309,7 @@ static RefPtr<TypeLayout> processEntryPointParameter( // so we can find the index of this generic param decl in the list genParamTypeLayout->type = type; genParamTypeLayout->paramIndex = findGenericParam(context->shared->programLayout->globalGenericParams, globalGenericParam.getDecl()); - genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count++; + genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count += 1; return genParamTypeLayout; } else @@ -2176,7 +2412,7 @@ static void collectEntryPointParameters( for (auto rr : paramTypeLayout->resourceInfos) { auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind); - paramVarLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count; + paramVarLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue(); entryPointRes->count += rr.count; } @@ -2208,7 +2444,7 @@ static void collectEntryPointParameters( for (auto rr : resultTypeLayout->resourceInfos) { auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind); - resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count; + resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue(); entryPointRes->count += rr.count; } } @@ -2489,7 +2725,7 @@ void generateParameterBindings( // Does the field have any uniform data? auto layoutInfo = firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform); - size_t uniformSize = layoutInfo ? layoutInfo->count : 0; + LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0; if( uniformSize != 0 ) { // Make sure uniform fields get laid out properly... @@ -2498,13 +2734,13 @@ void generateParameterBindings( uniformSize, firstVarLayout->typeLayout->uniformAlignment); - size_t uniformOffset = globalScopeRules->AddStructField( + LayoutSize uniformOffset = globalScopeRules->AddStructField( &structLayoutInfo, fieldInfo); for( auto& varLayout : parameterInfo->varLayouts ) { - varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset; + varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); } } @@ -2612,27 +2848,29 @@ RefPtr<ProgramLayout> specializeProgramLayout( // To recover layout context, we skip generic resources in the first pass if (varLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) continue; - SLANG_ASSERT(varLayout->resourceInfos.Count() == varLayout->typeLayout->resourceInfos.Count()); - auto uniformInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform); - auto tUniformInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform); - if (uniformInfo) + + if (auto uniformInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform)) { anyUniforms = true; - SLANG_ASSERT(tUniformInfo); - structLayoutInfo.size = Math::Max(structLayoutInfo.size, uniformInfo->index + tUniformInfo->count); + + if( auto tUniformInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) ) + { + structLayoutInfo.size = maximum(structLayoutInfo.size, uniformInfo->index + tUniformInfo->count); + } } - for (UInt i = 0; i < varLayout->resourceInfos.Count(); i++) + for( auto resInfo : varLayout->resourceInfos ) { - auto resInfo = varLayout->resourceInfos[i]; - auto tresInfo = varLayout->typeLayout->FindResourceInfo(resInfo.kind); - SLANG_ASSERT(tresInfo); - auto usedRangeSet = findUsedRangeSetForSpace(&context, resInfo.space); - markSpaceUsed(&context, resInfo.space); - usedRangeSet->usedResourceRanges[(int)resInfo.kind].Add( - nullptr, // we don't need to track parameter info here - resInfo.index, - resInfo.index + tresInfo->count); + if( auto tresInfo = varLayout->typeLayout->FindResourceInfo(resInfo.kind) ) + { + auto usedRangeSet = findUsedRangeSetForSpace(&context, resInfo.space); + markSpaceUsed(&context, resInfo.space); + usedRangeSet->usedResourceRanges[(int)resInfo.kind].Add( + nullptr, // we don't need to track parameter info here + resInfo.index, + resInfo.index + tresInfo->count); + } } + structLayout->fields[varId] = varLayout; varLayoutMapping[varLayout] = varLayout; } @@ -2658,8 +2896,8 @@ RefPtr<ProgramLayout> specializeProgramLayout( layoutContext.with(constantBufferRules), newType); auto layoutInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform); - size_t uniformSize = layoutInfo ? layoutInfo->count : 0; - if (uniformSize) + LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0; + if (uniformSize != 0) { if (globalCBufferInfo.kind == LayoutResourceKind::None) { @@ -2689,10 +2927,10 @@ RefPtr<ProgramLayout> specializeProgramLayout( UniformLayoutInfo fieldInfo( uniformSize, newTypeLayout->uniformAlignment); - size_t uniformOffset = layoutContext.getRulesFamily()->getConstantBufferRules()->AddStructField( + LayoutSize uniformOffset = layoutContext.getRulesFamily()->getConstantBufferRules()->AddStructField( &structLayoutInfo, fieldInfo); - newVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset; + newVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); anyUniforms = true; } structLayout->fields[varId] = newVarLayout; diff --git a/source/slang/reflection.cpp b/source/slang/reflection.cpp index 6e635a8ce..73b616377 100644 --- a/source/slang/reflection.cpp +++ b/source/slang/reflection.cpp @@ -513,6 +513,17 @@ SLANG_API SlangReflectionType* spReflectionTypeLayout_GetType(SlangReflectionTyp return (SlangReflectionType*) typeLayout->type.Ptr(); } +namespace +{ + static size_t getReflectionSize(LayoutSize size) + { + if(size.isFinite()) + return size.getFiniteValue(); + + return SLANG_UNBOUNDED_SIZE; + } +} + SLANG_API size_t spReflectionTypeLayout_GetSize(SlangReflectionTypeLayout* inTypeLayout, SlangParameterCategory category) { auto typeLayout = convert(inTypeLayout); @@ -521,7 +532,7 @@ SLANG_API size_t spReflectionTypeLayout_GetSize(SlangReflectionTypeLayout* inTyp auto info = typeLayout->FindResourceInfo(LayoutResourceKind(category)); if(!info) return 0; - return info->count; + return getReflectionSize(info->count); } SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_GetFieldByIndex(SlangReflectionTypeLayout* inTypeLayout, unsigned index) @@ -558,10 +569,10 @@ SLANG_API size_t spReflectionTypeLayout_GetElementStride(SlangReflectionTypeLayo auto elementTypeLayout = arrayTypeLayout->elementTypeLayout; auto info = elementTypeLayout->FindResourceInfo(LayoutResourceKind(category)); if(!info) return 0; - return info->count; + return getReflectionSize(info->count); } - // An import special case, though, is Vulkan descriptor-table slots, + // An important special case, though, is Vulkan descriptor-table slots, // where an entire array will use a single `binding`, so that the // effective stride is zero: case SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT: @@ -1210,5 +1221,5 @@ SLANG_API size_t spReflection_getGlobalConstantBufferSize(SlangReflection* inPro auto structLayout = getGlobalStructLayout(program); auto uniform = structLayout->FindResourceInfo(LayoutResourceKind::Uniform); if (!uniform) return 0; - return uniform->count; + return getReflectionSize(uniform->count); } diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp index e3b89a831..7fc6320cc 100644 --- a/source/slang/type-layout.cpp +++ b/source/slang/type-layout.cpp @@ -16,6 +16,15 @@ size_t RoundToAlignment(size_t offset, size_t alignment) return offset + (alignment - remainder); } +LayoutSize RoundToAlignment(LayoutSize offset, size_t alignment) +{ + // An infinite size is assumed to be maximally aligned. + if(offset.isInfinite()) + return LayoutSize::infinite(); + + return RoundToAlignment(offset.getFiniteValue(), alignment); +} + static size_t RoundUpToPowerOfTwo( size_t value ) { // TODO(tfoley): I know this isn't a fast approach @@ -66,9 +75,10 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl } } - SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, size_t elementCount) override + SimpleArrayLayoutInfo GetArrayLayout( SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { - size_t stride = elementInfo.size; + SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); + auto stride = elementInfo.size.getFiniteValue(); SimpleArrayLayoutInfo arrayInfo; arrayInfo.kind = elementInfo.kind; @@ -109,7 +119,7 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl return structInfo; } - size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override + LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override { // Skip zero-size fields if(fieldInfo.size == 0) @@ -117,7 +127,7 @@ struct DefaultLayoutRulesImpl : SimpleLayoutRulesImpl ioStructInfo->alignment = std::max(ioStructInfo->alignment, fieldInfo.alignment); ioStructInfo->size = RoundToAlignment(ioStructInfo->size, fieldInfo.alignment); - size_t fieldOffset = ioStructInfo->size; + LayoutSize fieldOffset = ioStructInfo->size; ioStructInfo->size += fieldInfo.size; return fieldOffset; } @@ -136,7 +146,7 @@ struct DefaultConstantBufferLayoutRulesImpl : DefaultLayoutRulesImpl // be a multiple of 16 bytes. // // HLSL agrees. - SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, size_t elementCount) override + SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) override { if(elementInfo.kind == LayoutResourceKind::Uniform) { @@ -171,7 +181,9 @@ static SimpleLayoutInfo getGLSLVectorLayout( SimpleLayoutInfo elementInfo, size_t elementCount) { SLANG_RELEASE_ASSERT(elementInfo.kind == LayoutResourceKind::Uniform); - auto size = elementInfo.size * elementCount; + SLANG_RELEASE_ASSERT(elementInfo.size.isFinite()); + + auto size = elementInfo.size.getFiniteValue() * elementCount; SimpleLayoutInfo vectorInfo( LayoutResourceKind::Uniform, size, @@ -193,7 +205,7 @@ struct Std140LayoutRulesImpl : GLSLConstantBufferLayoutRulesImpl struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl { // Can't let a `struct` field straddle a register (16-byte) boundary - size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override + LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) override { // Skip zero-size fields if(fieldInfo.size == 0) @@ -202,8 +214,8 @@ struct HLSLConstantBufferLayoutRulesImpl : DefaultConstantBufferLayoutRulesImpl ioStructInfo->alignment = std::max(ioStructInfo->alignment, fieldInfo.alignment); ioStructInfo->size = RoundToAlignment(ioStructInfo->size, fieldInfo.alignment); - size_t fieldOffset = ioStructInfo->size; - size_t fieldSize = fieldInfo.size; + LayoutSize fieldOffset = ioStructInfo->size; + LayoutSize fieldSize = fieldInfo.size; // Would this field cross a 16-byte boundary? auto registerSize = 16; @@ -690,19 +702,30 @@ TypeLayoutContext getInitialLayoutContextForTarget(TargetRequest* targetReq) } -static int GetElementCount(RefPtr<IntVal> val) +static LayoutSize GetElementCount(RefPtr<IntVal> val) { + // Lack of a size indicates an unbounded array. + if(!val) + return LayoutSize::infinite(); + if (auto constantVal = val.As<ConstantIntVal>()) { - return (int) constantVal->value; + return LayoutSize(LayoutSize::RawValue(constantVal->value)); } else if( auto varRefVal = val.As<GenericParamIntVal>() ) { - // TODO(tfoley): do something sensible in this case + // TODO: We want to treat the case where the number of + // elements in an array depends on a generic parameter + // much like the case where the number of elements is + // unbounded, *but* we can't just blindly do that because + // an API might disallow unbounded arrays in various + // cases where a generic bound might work (because + // any concrete specialization will have a finite bound...) + // return 0; } SLANG_UNEXPECTED("unhandled integer literal kind"); - return 0; + UNREACHABLE_RETURN(LayoutSize(0)); } bool IsResourceKind(LayoutResourceKind kind) @@ -721,7 +744,7 @@ bool IsResourceKind(LayoutResourceKind kind) SimpleLayoutInfo GetSimpleLayoutImpl( SimpleLayoutInfo info, - RefPtr<Type> type, + RefPtr<Type> type, LayoutRulesImpl* rules, RefPtr<TypeLayout>* outTypeLayout) { @@ -944,7 +967,10 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout( newResInfo->space = oldResInfo.space; if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind)) { - newResInfo->index += offsetResInfo->count; + // We should not be trying to offset things by an infinite amount, + // since that would leave all the indices undefined. + SLANG_RELEASE_ASSERT(offsetResInfo->count.isFinite()); + newResInfo->index += offsetResInfo->count.getFiniteValue(); } } @@ -1017,7 +1043,7 @@ createParameterGroupTypeLayout( // Make sure that we allocate resource usage for the // parameter block itself. - if( parameterGroupInfo.size ) + if( parameterGroupInfo.size != 0 ) { containerTypeLayout->addResourceUsage( parameterGroupInfo.kind, @@ -1121,7 +1147,8 @@ createParameterGroupTypeLayout( auto elementVarResInfo = elementVarLayout->findOrAddResourceInfo(kind); if( auto containerTypeResInfo = containerTypeLayout->FindResourceInfo(kind) ) { - elementVarResInfo->index += containerTypeResInfo->count; + SLANG_RELEASE_ASSERT(containerTypeResInfo->count.isFinite()); + elementVarResInfo->index += containerTypeResInfo->count.getFiniteValue(); } } typeLayout->elementVarLayout = elementVarLayout; @@ -1460,9 +1487,11 @@ bool doesResourceRequireAdjustmentForArrayOfStructs(LayoutResourceKind kind) // // and then we expect `foo_b` to get `register(t8)`, rather // than `register(t1)`. -static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( +// +static RefPtr<TypeLayout> maybeAdjustLayoutForArrayElementType( RefPtr<TypeLayout> originalTypeLayout, - UInt elementCount) + LayoutSize elementCount, + UInt& ioAdditionalSpacesNeeded) { // We will start by looking for cases that we can reject out // of hand. @@ -1494,7 +1523,8 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( auto originalInnerElementTypeLayout = originalArrayTypeLayout->elementTypeLayout; auto adjustedInnerElementTypeLayout = maybeAdjustLayoutForArrayElementType( originalInnerElementTypeLayout, - elementCount); + elementCount, + ioAdditionalSpacesNeeded); // If nothing needed to be changed on the inner element type, // then we are done. @@ -1516,7 +1546,8 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( auto originalInnerElementTypeLayout = originalParameterGroupTypeLayout->elementVarLayout->typeLayout; auto adjustedInnerElementTypeLayout = maybeAdjustLayoutForArrayElementType( originalInnerElementTypeLayout, - elementCount); + elementCount, + ioAdditionalSpacesNeeded); // If nothing needed to be changed on the inner element type, // then we are done. @@ -1537,20 +1568,37 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( if(fieldCount == 0) return originalTypeLayout; - // TODO: we could try to special-case a `struct` type with a single - // field that needs no adjustment, just to avoid some extra allocation. - RefPtr<StructTypeLayout> adjustedStructTypeLayout = new StructTypeLayout(); copyTypeLayoutFields(adjustedStructTypeLayout, originalStructTypeLayout); + // If the array type adjustment forces us to give a whole space to + // one or more fields, then we'll need to carefully compute the space + // index for each field as we go. + // + LayoutSize nextSpaceIndex = 0; + Dictionary<RefPtr<VarLayout>, RefPtr<VarLayout>> mapOriginalFieldToAdjusted; for( auto originalField : originalStructTypeLayout->fields ) { - // Compute the adjusted type for the field auto originalFieldTypeLayout = originalField->typeLayout; + + LayoutSize originalFieldSpaceCount = 0; + if(auto resInfo = originalFieldTypeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) + originalFieldSpaceCount = resInfo->count; + + // Compute the adjusted type for the field + UInt fieldAdditionalSpaces = 0; auto adjustedFieldTypeLayout = maybeAdjustLayoutForArrayElementType( originalFieldTypeLayout, - elementCount); + elementCount, + fieldAdditionalSpaces); + + LayoutSize adjustedFieldSpaceCount = originalFieldSpaceCount + fieldAdditionalSpaces; + + LayoutSize spaceOffsetForField = nextSpaceIndex; + nextSpaceIndex += adjustedFieldSpaceCount; + + ioAdditionalSpacesNeeded += fieldAdditionalSpaces; // Create an adjusted field variable, that is mostly // a clone of the original field (just with our @@ -1559,16 +1607,31 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( copyVarLayoutFields(adjustedField, originalField); adjustedField->typeLayout = adjustedFieldTypeLayout; - // Finally we get down to the real meat of the change, - // which is that the field offsets for any resource-type - // fields need to be "adjusted" which amounts to just - // multiplying them by the element count of the array. - - for( auto& resInfo : adjustedField->resourceInfos ) + // We will now walk through the resource usage for + // the adjusted field, and try to figure out what + // to do with it all. + // + for(auto& resInfo : adjustedField->resourceInfos ) { if( doesResourceRequireAdjustmentForArrayOfStructs(resInfo.kind) ) { - resInfo.index *= elementCount; + if(elementCount.isFinite()) + { + // If the array size is finite, then the field's index/offset + // is just going to be strided by the array size since we + // are effectively doing AoS to SoA conversion. + // + resInfo.index *= elementCount.getFiniteValue(); + } + else + { + // If we are making an unbounded array, then a `struct` + // field with resource type will turn into its own space, + // and it will start at regsiter zero in that space. + // + resInfo.index = 0; + resInfo.space = spaceOffsetForField.getFiniteValue(); + } } } @@ -1592,8 +1655,16 @@ static RefPtr<TypeLayout>maybeAdjustLayoutForArrayElementType( } else { - // If the leaf type layout isn't some kind of aggregate, - // then we can just bail out here. + // In the leaf case, we must have a field that used up some resource + // that requires adjustment. Because there is no sub-structure to work + // with, we can just return the type layout as-is, but we also want + // to make a note that this value should consume an additional register + // space *if* the element count is unbounded. + if( elementCount.isInfinite() ) + { + ioAdditionalSpacesNeeded++; + } + return originalTypeLayout; } } @@ -1822,12 +1893,27 @@ SimpleLayoutInfo GetLayoutImpl( arrayType->baseType.Ptr(), outTypeLayout ? &elementTypeLayout : nullptr); - // For layout purposes, we treat an unsized array as an array of zero elements. + // To a first approximation, an array will usually be laid out + // by taking the element's type layout and laying out `elementCount` + // copies of it. There are of course many details that make + // this simplistic version of things not quite work. + // + // An important complication to deal with is the possibility of + // having "unbounded" arrays, which don't specify a size.' + // The layout rules for these vary heavily by resource kind and API. // - // TODO: Longer term we are going to need to be careful to include some indication - // that a type has logically "infinite" size in some resource kind. In particular - // this affects how we would allocate space for parameter binding purposes. - auto elementCount = arrayType->ArrayLength ? GetElementCount(arrayType->ArrayLength) : 0; + + auto elementCount = GetElementCount(arrayType->ArrayLength); + + // + // We can compute the uniform storage layout of an array using + // the rules for the target API. + // + // TODO: ensure that this does something reasonable with the unbounded + // case, or else issue an error message that the target doesn't + // support unbounded types. + // + auto arrayUniformInfo = rules->GetArrayLayout( elementInfo, elementCount).getUniformLayout(); @@ -1837,54 +1923,117 @@ SimpleLayoutInfo GetLayoutImpl( RefPtr<ArrayTypeLayout> typeLayout = new ArrayTypeLayout(); *outTypeLayout = typeLayout; - // If we construct an array over an aggregate type that contains - // resource fields, we may need to adjust the layout we create - // for the element type to - RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType( - elementTypeLayout, - elementCount); - + // Some parts of the array type layout object are easy to fill in: typeLayout->type = type; - typeLayout->originalElementTypeLayout = elementTypeLayout; - typeLayout->elementTypeLayout = adjustedElementTypeLayout; typeLayout->rules = rules; - + typeLayout->originalElementTypeLayout = elementTypeLayout; typeLayout->uniformAlignment = arrayUniformInfo.alignment; typeLayout->uniformStride = arrayUniformInfo.elementStride; typeLayout->addResourceUsage(LayoutResourceKind::Uniform, arrayUniformInfo.size); - // translate element-type resources into array-type resources + // + // The tricky part in constructing an array type layout comes when + // the element type is (or nests) a structure with resource-type + // fields, because in that case we need to perform AoS-to-SoA + // conversion as part of computing the final type layout, and + // we also need to pre-compute an "adjusted" element type + // layout that accounts for the striding that happens with + // resource-type contents. + // + // This complication is only made worse when we have to deal with + // unbounded-size arrays over such element types, since those + // resource-type fields will each end up consuming a full space + // in the resulting layout. + // + // The `maybeAdjustLayoutForArrayElementType` computes an "adjusted" + // type layout for the element type which takes the array stride into + // acount. If it returns the same type layout that was passed in, + // then that means no adjustement took place. + // + // The `additionalSpacesNeededForAdjustedElementType` variable counts + // the number of additional register spaces that were consumed, + // in the case of an unbounded array. + // + UInt additionalSpacesNeededForAdjustedElementType = 0; + RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType( + elementTypeLayout, + elementCount, + additionalSpacesNeededForAdjustedElementType); + + typeLayout->elementTypeLayout = adjustedElementTypeLayout; + + // We will now iterate over the resources consumed by the element + // type to compute how they contribute to the resource usage + // of the overall array type. + // for( auto elementResourceInfo : elementTypeLayout->resourceInfos ) { // The uniform case was already handled above if( elementResourceInfo.kind == LayoutResourceKind::Uniform ) continue; + LayoutSize arrayResourceCount = 0; + // In almost all cases, the resources consumed by an array // will be its element count times the resources consumed - // by its element type. The one exception to this is - // arrays of resources in Vulkan GLSL, where an entire array + // by its element type. + // + // The first exception to this is arrays of resources when + // compiling to GLSL for Vulkan, where an entire array // only consumes a single descriptor-table slot. // - // Note: We extend this logic to arbitrary arrays-of-structs, - // under the assumption that downstream legalization will - // turn those into scalarized structs-of-arrays and this - // logic will work out. - UInt arrayResourceCount = 0; if (elementResourceInfo.kind == LayoutResourceKind::DescriptorTableSlot) { arrayResourceCount = elementResourceInfo.count; } + // + // The next big exception is when we are forming an unbounded-size + // array and the element type got "adjusted," because that means + // the array type will need to allocate full spaces for any resource-type + // fields in the element type. + // + // Note: we carefully carve things out so that the case of a simple + // array of resources does *not* lead to the element type being adjusted, + // so that this logic doesn't trigger and we instead handle it with + // the default logic below. + // + else if( + elementCount.isInfinite() + && adjustedElementTypeLayout != elementTypeLayout + && doesResourceRequireAdjustmentForArrayOfStructs(elementResourceInfo.kind) ) + { + // We want to ignore resource types consumed by the element type + // that need adjustement if the array size is infinite, since + // we will be allocating whole spaces for that part of the + // element's resource usage. + } else { arrayResourceCount = elementResourceInfo.count * elementCount; } - + + // Now that we've computed how the resource usage of the element type + // should contribute to the resource usage of the array, we can + // add in that resource usage. + // typeLayout->addResourceUsage( elementResourceInfo.kind, arrayResourceCount); } + + // The loop above to compute the resource usage of the array from its + // element type ignored any resource-type fields in an unbounded-size + // array if they would have been allocated as full register spaces. + // Those same fields were counted in `additionalSpacesNeededForAdjustedElementType`, + // and need to be added into the total resource usage for the array + // if we skipped them as part of the loop (which happens when + // we detect that the element type layout had been "adjusted"). + // + if( adjustedElementTypeLayout != elementTypeLayout ) + { + typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, additionalSpacesNeededForAdjustedElementType); + } } return arrayUniformInfo; } @@ -1903,6 +2052,14 @@ SimpleLayoutInfo GetLayoutImpl( *outTypeLayout = typeLayout; } + // The layout of a `struct` type is computed in the somewhat + // obvious fashion by keeping a running counter of the resource + // usage for each kind of resource, and then for a field that + // uses a given resource, assigning it the current offset and + // then bumping the offset by the field size. In the case of + // uniform data we also need to deal with alignment and other + // detailed layout rules. + UniformLayoutInfo info = rules->BeginStructLayout(); for (auto field : GetFields(structDeclRef)) @@ -1922,7 +2079,7 @@ SimpleLayoutInfo GetLayoutImpl( // This means that the code to generate final // declarations needs to *also* eliminate zero-size // fields to be safe... - size_t uniformOffset = info.size; + LayoutSize uniformOffset = info.size; if(fieldInfo.size != 0) { uniformOffset = rules->AddStructField(&info, fieldInfo); @@ -1942,7 +2099,7 @@ SimpleLayoutInfo GetLayoutImpl( // Set up uniform offset information, if there is any uniform data in the field if( fieldTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) ) { - fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset; + fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); } // Add offset information for any other resource kinds @@ -1958,10 +2115,39 @@ SimpleLayoutInfo GetLayoutImpl( // The field will need offset information for this kind auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind); - // Check how many slots of the given kind have already been added to the type - auto structTypeResourceInfo = typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind); - fieldResourceInfo->index = structTypeResourceInfo->count; - structTypeResourceInfo->count += fieldTypeResourceInfo.count; + // It is possible for a `struct` field to use an unbounded array + // type, and in the D3D case that would consume an unbounded number + // of registers. What is more, a single `struct` could have multiple + // such fields, or ordinary resource fields after an unbounded field. + // + // We handle this case by allocating a distinct register space for + // any field that consumes an unbounded amount of registers. + // + if( fieldTypeResourceInfo.count.isInfinite() ) + { + // We need to add one register space to own the storage for this field. + // + auto structTypeSpaceResourceInfo = typeLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace); + auto spaceOffset = structTypeSpaceResourceInfo->count; + structTypeSpaceResourceInfo->count += 1; + + // The field itself will record itself as having a zero offset into + // the chosen space. + // + fieldResourceInfo->space = spaceOffset.getFiniteValue(); + fieldResourceInfo->index = 0; + } + else + { + // In the case where the field consumes a finite number of slots, we + // can simply set its offset/index to the number of such slots consumed + // so far, and then increment the number of slots consumed by the + // `struct` type itself. + // + auto structTypeResourceInfo = typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind); + fieldResourceInfo->index = structTypeResourceInfo->count.getFiniteValue(); + structTypeResourceInfo->count += fieldTypeResourceInfo.count; + } } } } @@ -1989,7 +2175,7 @@ SimpleLayoutInfo GetLayoutImpl( genParamTypeLayout->type = type; genParamTypeLayout->paramIndex = findGenericParam(context.targetReq->layout->globalGenericParams, genParamTypeLayout->getGlobalGenericParamDecl()); genParamTypeLayout->rules = rules; - genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count++; + genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count += 1; *outTypeLayout = genParamTypeLayout; } return info; diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h index 7f662bdee..32ee41784 100644 --- a/source/slang/type-layout.h +++ b/source/slang/type-layout.h @@ -36,12 +36,152 @@ enum class LayoutRulesFamily }; #endif +// A "size" that can either be a simple finite size or +// the special case of an infinite/unbounded size. +// +struct LayoutSize +{ + typedef size_t RawValue; + + LayoutSize() + : raw(0) + {} + + LayoutSize(RawValue size) + : raw(size) + { + SLANG_ASSERT(size != RawValue(-1)); + } + + static LayoutSize infinite() + { + LayoutSize result; + result.raw = RawValue(-1); + return result; + } + + bool isInfinite() const { return raw == RawValue(-1); } + + bool isFinite() const { return raw != RawValue(-1); } + RawValue getFiniteValue() const { SLANG_ASSERT(isFinite()); return raw; } + + bool operator==(LayoutSize that) const + { + return raw == that.raw; + } + + bool operator!=(LayoutSize that) const + { + return raw != that.raw; + } + + void operator+=(LayoutSize right) + { + if( isInfinite() ) {} + else if( right.isInfinite() ) + { + *this = LayoutSize::infinite(); + } + else + { + *this = LayoutSize(raw + right.raw); + } + } + + void operator*=(LayoutSize right) + { + // Deal with zero first, so that anything (even the "infinite" value) times zero is zero. + if( raw == 0 ) + { + return; + } + + if( right.raw == 0 ) + { + raw = 0; + return; + } + + // Next we deal with infinite cases, so that infinite times anything non-zero is infinite + if( isInfinite() ) + { + return; + } + + if( right.isInfinite() ) + { + *this = LayoutSize::infinite(); + return; + } + + // Finally deal with the case where both sides are finite + *this = LayoutSize(raw * right.raw); + } + + void operator-=(RawValue right) + { + if( isInfinite() ) {} + else + { + *this = LayoutSize(raw - right); + } + } + + void operator/=(RawValue right) + { + if( isInfinite() ) {} + else + { + *this = LayoutSize(raw / right); + } + } + RawValue raw; +}; + +inline LayoutSize operator+(LayoutSize left, LayoutSize right) +{ + LayoutSize result(left); + result += right; + return result; +} + +inline LayoutSize operator*(LayoutSize left, LayoutSize right) +{ + LayoutSize result(left); + result *= right; + return result; +} + +inline LayoutSize operator-(LayoutSize left, LayoutSize::RawValue right) +{ + LayoutSize result(left); + result -= right; + return result; +} + +inline LayoutSize operator/(LayoutSize left, LayoutSize::RawValue right) +{ + LayoutSize result(left); + result /= right; + return result; +} + +inline LayoutSize maximum(LayoutSize left, LayoutSize right) +{ + if(left.isInfinite() || right.isInfinite()) + return LayoutSize::infinite(); + + return LayoutSize(Math::Max( + left.getFiniteValue(), + right.getFiniteValue())); +} + // Layout appropriate to "just memory" scenarios, // such as laying out the members of a constant buffer. struct UniformLayoutInfo { - size_t size; - size_t alignment; + LayoutSize size; + size_t alignment; UniformLayoutInfo() : size(0) @@ -49,8 +189,8 @@ struct UniformLayoutInfo {} UniformLayoutInfo( - size_t size, - size_t alignment) + LayoutSize size, + size_t alignment) : size(size) , alignment(alignment) {} @@ -68,9 +208,9 @@ struct UniformArrayLayoutInfo : UniformLayoutInfo {} UniformArrayLayoutInfo( - size_t size, - size_t alignment, - size_t elementStride) + LayoutSize size, + size_t alignment, + size_t elementStride) : UniformLayoutInfo(size, alignment) , elementStride(elementStride) {} @@ -86,7 +226,7 @@ struct SimpleLayoutInfo LayoutResourceKind kind; // How many resources of that kind? - size_t size; + LayoutSize size; // only useful in the uniform case size_t alignment; @@ -104,7 +244,7 @@ struct SimpleLayoutInfo , alignment(uniformInfo.alignment) {} - SimpleLayoutInfo(LayoutResourceKind kind, size_t size, size_t alignment=1) + SimpleLayoutInfo(LayoutResourceKind kind, LayoutSize size, size_t alignment=1) : kind(kind) , size(size) , alignment(alignment) @@ -168,7 +308,7 @@ public: LayoutResourceKind kind = LayoutResourceKind::None; // How many registers of the above kind did we use? - UInt count; + LayoutSize count; }; List<ResourceInfo> resourceInfos; @@ -207,7 +347,7 @@ public: findOrAddResourceInfo(info.kind)->count += info.count; } - void addResourceUsage(LayoutResourceKind kind, UInt count) + void addResourceUsage(LayoutResourceKind kind, LayoutSize count) { ResourceInfo info; info.kind = kind; @@ -507,7 +647,7 @@ struct SimpleLayoutRulesImpl virtual SimpleLayoutInfo GetScalarLayout(BaseType baseType) = 0; // Get size and alignment for an array of elements - virtual SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, size_t elementCount) = 0; + virtual SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) = 0; // Get layout for a vector or matrix type virtual SimpleLayoutInfo GetVectorLayout(SimpleLayoutInfo elementInfo, size_t elementCount) = 0; @@ -517,7 +657,7 @@ struct SimpleLayoutRulesImpl virtual UniformLayoutInfo BeginStructLayout() = 0; // Add a field to a `struct` type, and return the offset for the field - virtual size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) = 0; + virtual LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) = 0; // End layout for a struct, and finalize its size/alignment. virtual void EndStructLayout(UniformLayoutInfo* ioStructInfo) = 0; @@ -542,7 +682,7 @@ struct LayoutRulesImpl return simpleRules->GetScalarLayout(baseType); } - SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, size_t elementCount) + SimpleArrayLayoutInfo GetArrayLayout(SimpleLayoutInfo elementInfo, LayoutSize elementCount) { return simpleRules->GetArrayLayout(elementInfo, elementCount); } @@ -562,7 +702,7 @@ struct LayoutRulesImpl return simpleRules->BeginStructLayout(); } - size_t AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) + LayoutSize AddStructField(UniformLayoutInfo* ioStructInfo, UniformLayoutInfo fieldInfo) { return simpleRules->AddStructField(ioStructInfo, fieldInfo); } |
