diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-03-08 16:24:02 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-03-08 16:24:02 -0800 |
| commit | 4f94dd46a2d885e570814dd14a5e46f8e0814802 (patch) | |
| tree | 55f22605d5532b8fc30b0d3691d8ba3994d41da8 /source/slang/ir-bind-existentials.cpp | |
| parent | 281c67b8d92899f462695fe75a26467743a497e8 (diff) | |
Improve support for interfaces as shader parameters (#886)
* Improve support for interfaces as shader parameters
This change adds two main things over the existing support:
1. It is now possible to plug in concrete types that actually contain (uniform/ordinary) fields for the existential type parameters introduced by interface-type shader parameters. The `interface-shader-param2.slang` test shows that this works.
2. There is a limited amount of support for doing correct layout computation and generating output code that matches that layout, so that interface and ordinary-type fields can be interleaved to a limited extent. The `interface-shader-param3.slang` test confirms this behavior.
There are several moving pieces in the change.
* When it comes to terminology, we try to draw a more clear distinction between existial type parameters/arguments and existential/object value parametes/arguments. A simple way to look at it is that an `IFoo[3]` shader parameter introduces a single existential type parameter (so that a concrete type argument like `SomeThing` can be plugged in for the `IFoo`) but introduces three existential object/value parameters (to represent the concrete values for the array elements).
* At the IR level, we support a few new operations. A `BindExistentialsType` can take a type that is not itself an interface/existential type but which depends on interfaces/existentials (e.g., `ConstantBuffer<IFoo>`) and plug in the concrete types to be used for its existential type slots.
* Then a `wrapExistentials` instruction can take a type with all the existentials plugged in (possibly by `BindExistentialsType`) and wrap it into a value of the existential-using type (e.g., turn `ConstantBuffer<SomeThing>` into a `ConstantBuffer<IFoo>`).
* The IR passes for doing generic/existential specialization have been updated to be able to desugar uses of these new operations just enough so that a `ConstantBuffer<IFoo>` can be used.
* When we specialize an IR parameter of an interface type like `IFoo` based on a concrete type `SomeThing`, we turn the parameter into an `ExistentialBox<SomeThing>` to reflect the fact that we are conceptually referring to `SomeThing` indirectly (it shouldn't be factored into the layout of its surrounding type).
* Parameter binding was updated so that it passes along the bound existential type arguments in a `Program` or `EntryPoint` to type layout, so that we can take them into account. The type layout code needs to do a little work to pass the appropriate range of arguments along to sub-fields when computing layout for aggregate types.
* Type layout was updated to have a notion of "pending" items, which represent the concrete types of data that are logically being referenced by existential value slots. The basic idea is that these values aren't included in the layout of a type by default, but then they get "flushed" to come after all the non-existential-related data in a constant buffer, parameter block, etc.
* The logic for computing a parameter group (`ConstantBuffer` or `ParameterBlock`) layout was updated to always "flush" the pending items on the element type of the group, so that the resource usage of specialized existential slots would be taken into account.
* The type legalization pass has been adapted so that we can derive two different passes from it. One does resource-type legalization (which is all that the original pass did). The new pass uses the same basic machinery to legalize `ExistentialBox<T>` types by moving them out of their containing type(s), and then turning them into ordinary variables/parameters of type `T`.
Big things missing from this change include:
- Nothing is making sure that "pending" items at the global or entry-point level will get proper registers/bindings allocated to them. For the uniform case, all that matters in the current compiler is that we declare them in the right order in the output HLSL/GLSL, but for resources to be supported we will need to compute this layout information and start associating it with the existential/interface-type fields.
- Nothing is being done to support `BindExistentials<S, ...>` where `S` is a `struct` type that might have existential-type fields (or nested fields...). Eventually we need to desugar a type like this into a fresh `struct` type that has the same field keys as `S`, but with fields replaced by suitable `BindExistentials` as needed. (The hard part of this would seem to be computing which slots go to which fields). As a practial matter, this missing feature means that interface-type members of `cbuffer` declarations won't work.
The current tests carefully avoid both of these problems. They don't declare any buffer/texture fields in the concrete types, and they don't make use of `cbuffer` declarations or `ConstantBuffer`s over structure types with interface-type fields.
* fixup: add override to methods
* fixup: typos
Diffstat (limited to 'source/slang/ir-bind-existentials.cpp')
| -rw-r--r-- | source/slang/ir-bind-existentials.cpp | 128 |
1 files changed, 54 insertions, 74 deletions
diff --git a/source/slang/ir-bind-existentials.cpp b/source/slang/ir-bind-existentials.cpp index 7188e2503..f87a7d233 100644 --- a/source/slang/ir-bind-existentials.cpp +++ b/source/slang/ir-bind-existentials.cpp @@ -198,7 +198,7 @@ struct BindExistentialSlots // We only care about parameters that are associated // with one or more existential slots. // - auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::ExistentialSlot); + auto resInfo = varLayout->FindResourceInfo(LayoutResourceKind::ExistentialTypeParam); if(!resInfo) return; @@ -208,7 +208,7 @@ struct BindExistentialSlots // UInt firstSlot = resInfo->index; UInt slotCount = 0; - if(auto typeResInfo = varLayout->getTypeLayout()->FindResourceInfo(LayoutResourceKind::ExistentialSlot)) + if(auto typeResInfo = varLayout->getTypeLayout()->FindResourceInfo(LayoutResourceKind::ExistentialTypeParam)) slotCount = UInt(typeResInfo->count.getFiniteValue()); // At this point we know that the parameter consumes @@ -266,12 +266,9 @@ struct BindExistentialSlots // We are going to alter the type of the // given `inst` based on information in - // the `slotArgs`, but the exact kind - // of modification will depend on the - // original type of `inst`. + // the `slotArgs`. auto fullType = inst->getFullType(); - auto type = inst->getDataType(); SharedIRBuilder sharedBuilder; sharedBuilder.session = module->getSession(); @@ -280,81 +277,64 @@ struct BindExistentialSlots IRBuilder builder; builder.sharedBuilder = &sharedBuilder; - // The easy case is when the `type` of `inst` - // is directly an interface type. + // Every argument that is filling an existential + // type param/slot comprises both a type and + // a witness table, so the total number of operands + // is twice the number of slots we are filling. // - if( auto interfaceType = as<IRInterfaceType>(type) ) - { - // An intereface-type parameter will use a - // single slot, which consits of a pair of - // operands. - // - // The first operand is the concrete type - // we want to plug in. - // - auto newType = (IRType*) slotArgs[0].get(); - - // The second operand is a witness that - // the concrete type conforms to the interface - // used for the original parameter. - // - auto newWitnessTable = slotArgs[1].get(); + UInt slotOperandCount = slotCount*2; + List<IRInst*> slotOperands; + for(UInt ii = 0; ii < slotOperandCount; ++ii) + slotOperands.Add(slotArgs[ii].get()); + + // We are going to create a proxy type that represents + // the results of plugging all the information + // from the existential slots into the original type. + // + auto newType = builder.getBindExistentialsType( + fullType, + slotOperandCount, + slotOperands.Buffer()); - // We are going to replace the (interface) type of - // the parameter with the new (concrete) type. - // - builder.setDataType(inst, newType); + // We will replace the type of the original parameter + // with the new proxy type. + // + builder.setDataType(inst, newType); - // Next we want to replace all uses of `inst` (which - // expect a value of its old type) with a fresh - // `makeExistential(...)` instruction that refers to - // `inst` with its new type. - // - // Note: we make a copy of the list of uses for `inst` - // before going through and replacing them, because - // during the replacement we make *more* uses of `inst`, - // as an operand to the `makeExistential` instructions. - // We only want to replace the old uses, and not the - // new ones we'll be making. - // - List<IRUse*> usesToReplace; - for(auto use = inst->firstUse; use; use = use->nextUse ) - usesToReplace.Add(use); + // Next we want to replace all uses of `inst` (which + // expect a value of its old type) with a fresh + // `wrapExistential(...)` instruction that refers to + // `inst` with its new type. + // + // Note: we make a copy of the list of uses for `inst` + // before going through and replacing them, because + // during the replacement we make *more* uses of `inst`, + // as an operand to the `makeExistential` instructions. + // We only want to replace the old uses, and not the + // new ones we'll be making. + // + List<IRUse*> usesToReplace; + for(auto use = inst->firstUse; use; use = use->nextUse ) + usesToReplace.Add(use); - // Now we can loop over our list of uses and replace each. - // - for(auto use : usesToReplace) - { - // First we emit a `makeExisential` right before the - // use site. - // - builder.setInsertBefore(use->getUser()); - auto newVal = builder.emitMakeExistential( - fullType, - inst, - newWitnessTable); - - // Second we make the use site point at the new - // value instead. - // - use->set(newVal); - } - } - else + // Now we can loop over our list of uses and replace each. + // + for(auto use : usesToReplace) { - // TODO: We eventually need to handle cases where there - // are: - // - // * Arrays over existential types; e.g.: `IFoo[3]` - // - // * Structs with existential-type fields. - // - // * Constant buffers or other "containers" over existentials; e.g., `ConstantBuffer<IFoo>` + // First we emit a `makeExisential` right before the + // use site. // - // * Nested combinations of the above; e.g., a `ConstantBuffer` - // of a struct with a field that is an array of `IFoo`. + builder.setInsertBefore(use->getUser()); + auto newVal = builder.emitWrapExistential( + fullType, + inst, + slotOperandCount, + slotOperands.Buffer()); + + // Second we make the use site point at the new + // value instead. // - SLANG_UNIMPLEMENTED_X("shader parameters with nested existentials"); + use->set(newVal); } } }; |
