diff options
| -rw-r--r-- | source/core/core.natvis | 30 | ||||
| -rw-r--r-- | source/slang/ir-entry-point-uniforms.cpp | 2 | ||||
| -rw-r--r-- | source/slang/ir-insts.h | 13 | ||||
| -rw-r--r-- | source/slang/ir-specialize.cpp | 473 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 39 | ||||
| -rw-r--r-- | source/slang/ir.h | 3 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param-in-struct.slang | 127 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param-in-struct.slang.expected.txt | 4 |
8 files changed, 633 insertions, 58 deletions
diff --git a/source/core/core.natvis b/source/core/core.natvis index 91fdbb49b..9d3f52839 100644 --- a/source/core/core.natvis +++ b/source/core/core.natvis @@ -3,41 +3,41 @@ <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="Slang::String"> - <DisplayString>{((char*) (buffer.pointer+1)),s}</DisplayString> - <StringView>((char*) (buffer.pointer+1)),s</StringView> + <DisplayString>{((char*) (m_buffer.pointer+1)),s}</DisplayString> + <StringView>((char*) (m_buffer.pointer+1)),s</StringView> </Type> <Type Name="Slang::ArrayView<*>"> - <DisplayString>{{ size={_count} }}</DisplayString> + <DisplayString>{{ size={m_count} }}</DisplayString> <Expand> - <Item Name="[size]">_count</Item> + <Item Name="[size]">m_count</Item> <ArrayItems> - <Size>_count</Size> - <ValuePointer>_buffer</ValuePointer> + <Size>m_count</Size> + <ValuePointer>m_buffer</ValuePointer> </ArrayItems> </Expand> </Type> <Type Name="Slang::List<*>"> - <DisplayString>{{ size={_count} }}</DisplayString> + <DisplayString>{{ size={m_count} }}</DisplayString> <Expand> - <Item Name="[size]">_count</Item> - <Item Name="[capacity]">bufferSize</Item> + <Item Name="[size]">m_count</Item> + <Item Name="[capacity]">m_capacity</Item> <ArrayItems> - <Size>_count</Size> - <ValuePointer>buffer</ValuePointer> + <Size>m_count</Size> + <ValuePointer>m_buffer</ValuePointer> </ArrayItems> </Expand> </Type> <Type Name="Slang::Array<*,*>"> - <DisplayString>{{ size={_count} }}</DisplayString> + <DisplayString>{{ size={m_count} }}</DisplayString> <Expand> - <Item Name="[size]">_count</Item> + <Item Name="[size]">m_count</Item> <ArrayItems> - <Size>_count</Size> - <ValuePointer>_buffer</ValuePointer> + <Size>m_count</Size> + <ValuePointer>m_buffer</ValuePointer> </ArrayItems> </Expand> </Type> diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp index 6ee05611d..5c7cdb5b4 100644 --- a/source/slang/ir-entry-point-uniforms.cpp +++ b/source/slang/ir-entry-point-uniforms.cpp @@ -347,6 +347,8 @@ struct MoveEntryPointUniformParametersToGlobalScope // param->removeAndDeallocate(); } + + fixUpFuncType(func); } // We need to be able to determine if a parameter is logically diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h index 5970e2b36..0dc4dbbac 100644 --- a/source/slang/ir-insts.h +++ b/source/slang/ir-insts.h @@ -919,6 +919,19 @@ struct IRBuilder UInt slotArgCount, IRInst* const* slotArgs); + IRInst* emitWrapExistential( + IRType* type, + IRInst* value, + UInt slotArgCount, + IRUse const* slotArgs) + { + List<IRInst*> slotArgVals; + for(UInt ii = 0; ii < slotArgCount; ++ii) + slotArgVals.add(slotArgs[ii].get()); + + return emitWrapExistential(type, value, slotArgCount, slotArgVals.getBuffer()); + } + IRUndefined* emitUndefined(IRType* type); diff --git a/source/slang/ir-specialize.cpp b/source/slang/ir-specialize.cpp index 57d14c11a..b57d2b58f 100644 --- a/source/slang/ir-specialize.cpp +++ b/source/slang/ir-specialize.cpp @@ -98,6 +98,9 @@ struct SpecializationContext // whether generic, existential, etc. // List<IRInst*> workList; + HashSet<IRInst*> workListSet; + + HashSet<IRInst*> cleanInsts; void addToWorkList( IRInst* inst) @@ -112,7 +115,14 @@ struct SpecializationContext return; } + if(workListSet.Contains(inst)) + return; + workList.add(inst); + workListSet.Add(inst); + cleanInsts.Remove(inst); + + addUsersToWorkList(inst); } // When a transformation makes a change to an instruction, @@ -367,6 +377,7 @@ struct SpecializationContext case kIROp_Specialize: case kIROp_lookup_interface_method: case kIROp_ExtractExistentialType: + case kIROp_BindExistentialsType: break; } } @@ -431,6 +442,13 @@ struct SpecializationContext maybeSpecializeLoad(as<IRLoad>(inst)); break; + case kIROp_FieldExtract: + maybeSpecializeFieldExtract(as<IRFieldExtract>(inst)); + break; + case kIROp_FieldAddress: + maybeSpecializeFieldAddress(as<IRFieldAddress>(inst)); + break; + case kIROp_BindExistentialsType: maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst)); break; @@ -566,12 +584,18 @@ struct SpecializationContext // addToWorkList(module->getModuleInst()); + while(workList.getCount() != 0) + { + // We will then iterate until our work list goes dry. // while(workList.getCount() != 0) { IRInst* inst = workList.getLast(); + workList.removeLast(); + workListSet.Remove(inst); + cleanInsts.Add(inst); // For each instruction we process, we want to perform // a few steps. @@ -610,6 +634,10 @@ struct SpecializationContext } } + addDirtyInstsToWorkListRec(module->getModuleInst()); + + } + // Once the work list has gone dry, we should have the invariant // that there are no `specialize` instructions inside of non-generic // functions that in turn reference a generic type/function, *except* @@ -617,6 +645,19 @@ struct SpecializationContext // which case we wouldn't want to specialize it anyway. } + void addDirtyInstsToWorkListRec(IRInst* inst) + { + if( !cleanInsts.Contains(inst) ) + { + addToWorkList(inst); + } + + for(auto child = inst->getLastChild(); child; child = child->getPrevInst()) + { + addDirtyInstsToWorkListRec(child); + } + } + // Given a `call` instruction in the IR, we need to detect the case // where the callee has some interface-type parameter(s) and at the // call site it is statically clear what concrete type(s) the arguments @@ -719,6 +760,19 @@ struct SpecializationContext auto witnessTable = makeExistential->getWitnessTable(); key.vals.add(witnessTable); } + else if( auto wrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = wrapExistential->getWrappedValue(); + auto valType = val->getFullType(); + key.vals.add(valType); + + UInt slotOperandCount = wrapExistential->getSlotOperandCount(); + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + auto slotOperand = wrapExistential->getSlotOperand(ii); + key.vals.add(slotOperand); + } + } else { SLANG_UNEXPECTED("missing case for existential argument"); @@ -770,6 +824,11 @@ struct SpecializationContext auto val = makeExistential->getWrappedValue(); newArgs.add(val); } + else if( auto wrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = wrapExistential->getWrappedValue(); + newArgs.add(val); + } else { SLANG_UNEXPECTED("missing case for existential argument"); @@ -838,6 +897,13 @@ struct SpecializationContext if(as<IRMakeExistential>(inst)) return true; + // A `wrapExistential(v, T0,w0, T1, w1, ...)` instruction + // is just a generalization of `makeExistential`, so it + // can apply in the same cases. + // + if(as<IRWrapExistential>(inst)) + return true; + // If we start to specialize functions that take arrays // of existentials as input, we will need a strategy to // determine arguments suitable for use in specializing @@ -904,7 +970,66 @@ struct SpecializationContext // IRInst* replacementVal = nullptr; - if( !isExistentialType(oldParam->getDataType()) ) + // The trickier case is when we have an existential-type + // parameter, because we need to extract out the concrete + // type that is coming from the call site. + // + if( auto oldMakeExistential = as<IRMakeExistential>(arg) ) + { + // In this case, the `arg` is `makeExistential(val, witnessTable)` + // and we know that the specialized call site will just be + // passing in `val`. + // + auto val = oldMakeExistential->getWrappedValue(); + auto witnessTable = oldMakeExistential->getWitnessTable(); + + // Our specialized function needs to take a parameter with the + // same type as `val`, to match the call site(s) that will be + // created. + // + auto valType = val->getFullType(); + auto newParam = builder->createParam(valType); + newParams.add(newParam); + + // Within the body of the function we cannot just use `val` + // directly, because the existing code expects an existential + // value, including its witness table. + // + // Therefore we will create a `makeExistential(newParam, witnessTable)` + // in the body of the new function and use *that* as the replacement + // value for the original parameter (since it will have the + // correct existential type, and stores the right witness table). + // + auto newMakeExistential = builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable); + newBodyInsts.add(newMakeExistential); + replacementVal = newMakeExistential; + } + else if( auto oldWrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = oldWrapExistential->getWrappedValue(); + auto valType = val->getFullType(); + + auto newParam = builder->createParam(valType); + newParams.add(newParam); + + // Within the body of the function we cannot just use `val` + // directly, because the existing code expects an existential + // value, including its witness table. + // + // Therefore we will create a `makeExistential(newParam, witnessTable)` + // in the body of the new function and use *that* as the replacement + // value for the original parameter (since it will have the + // correct existential type, and stores the right witness table). + // + auto newWrapExistential = builder->emitWrapExistential( + oldParam->getFullType(), + newParam, + oldWrapExistential->getSlotOperandCount(), + oldWrapExistential->getSlotOperands()); + newBodyInsts.add(newWrapExistential); + replacementVal = newWrapExistential; + } + else { // For parameters that don't have an existential type, // there is nothing interesting to do. The new function @@ -915,47 +1040,6 @@ struct SpecializationContext newParams.add(newParam); replacementVal = newParam; } - else - { - // The trickier case is when we have an existential-type - // parameter, because we need to extract out the concrete - // type that is coming from the call site. - // - if( auto oldMakeExistential = as<IRMakeExistential>(arg) ) - { - // In this case, the `arg` is `makeExistential(val, witnessTable)` - // and we know that the specialized call site will just be - // passing in `val`. - // - auto val = oldMakeExistential->getWrappedValue(); - auto witnessTable = oldMakeExistential->getWitnessTable(); - - // Our specialized function needs to take a parameter with the - // same type as `val`, to match the call site(s) that will be - // created. - // - auto valType = val->getFullType(); - auto newParam = builder->createParam(valType); - newParams.add(newParam); - - // Within the body of the function we cannot just use `val` - // directly, because the existing code expects an existential - // value, including its witness table. - // - // Therefore we will create a `makeExistential(newParam, witnessTable)` - // in the body of the new function and use *that* as the replacement - // value for the original parameter (since it will have the - // correct existential type, and stores the right witness table). - // - auto newMakeExistential = builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable); - newBodyInsts.add(newMakeExistential); - replacementVal = newMakeExistential; - } - else - { - SLANG_UNEXPECTED("missing case for existential argument"); - } - } // Whatever replacement value was constructed, we need to // register it as the replacement for the original parameter. @@ -1207,6 +1291,246 @@ struct SpecializationContext } } + UInt calcExistentialBoxSlotCount(IRType* type) + { + top: + if( as<IRExistentialBoxType>(type) ) + { + return 2; + } + else if( auto ptrType = as<IRPtrTypeBase>(type) ) + { + type = ptrType->getValueType(); + goto top; + } + else if( auto ptrLikeType = as<IRPointerLikeType>(type) ) + { + type = ptrLikeType->getElementType(); + goto top; + } + else if( auto structType = as<IRStructType>(type) ) + { + UInt count = 0; + for( auto field : structType->getFields() ) + { + count += calcExistentialBoxSlotCount(field->getFieldType()); + } + return count; + } + else + { + return 0; + } + } + + void maybeSpecializeFieldExtract(IRFieldExtract* inst) + { + auto baseArg = inst->getBase(); + auto fieldKey = inst->getField(); + + if( auto wrapInst = as<IRWrapExistential>(baseArg) ) + { + // We have `getField(wrapExistential(val, ...), fieldKey)` + // + auto val = wrapInst->getWrappedValue(); + + // We know what type we are expected to produce. + // + auto resultType = inst->getFullType(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(inst); + + // We'd *like* to replace this instruction with + // `wrapExistential(getField(val, fieldKey), ...)` instead, since that + // will enable subsequent specializations. + // + // To do that, we need to figure out: + // + // 1. What type that inner `getField` would return (what + // is the type of the `fieldKey` field in `val`?) + // + // 2. Which of the existential slot operands in `...` there + // actually apply to the given field. + // + + // To determine these things, we need the type of + // `val` to be a structure type so that we can look + // up the field corresponding to `fieldKey`. + // + auto valType = val->getDataType(); + auto valStructType = as<IRStructType>(valType); + if(!valStructType) + return; + + UInt slotOperandOffset = 0; + + IRStructField* foundField = nullptr; + for( auto valField : valStructType->getFields() ) + { + if( valField->getKey() == fieldKey ) + { + foundField = valField; + break; + } + + slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType()); + } + + if(!foundField) + return; + + auto foundFieldType = foundField->getFieldType(); + + List<IRInst*> slotOperands; + UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType); + + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii)); + } + + auto newGetField = builder.emitFieldExtract( + foundFieldType, + val, + fieldKey); + + auto newWrapExistentialInst = builder.emitWrapExistential( + resultType, + newGetField, + slotOperandCount, + slotOperands.getBuffer()); + + addUsersToWorkList(inst); + inst->replaceUsesWith(newWrapExistentialInst); + inst->removeAndDeallocate(); + } + } + + + void maybeSpecializeFieldAddress(IRFieldAddress* inst) + { + auto baseArg = inst->getBase(); + auto fieldKey = inst->getField(); + + if( auto wrapInst = as<IRWrapExistential>(baseArg) ) + { + // We have `getFieldAddr(wrapExistential(val, ...), fieldKey)` + // + auto val = wrapInst->getWrappedValue(); + + // We know what type we are expected to produce. + // + auto resultType = inst->getFullType(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(inst); + + // We'd *like* to replace this instruction with + // `wrapExistential(getFieldAddr(val, fieldKey), ...)` instead, since that + // will enable subsequent specializations. + // + // To do that, we need to figure out: + // + // 1. What type that inner `getFieldAddr` would return (what + // is the type of the `fieldKey` field in `val`?) + // + // 2. Which of the existential slot operands in `...` there + // actually apply to the given field. + // + + // To determine these things, we need the type of + // `val` to be a (pointer to a) structure type so that we can look + // up the field corresponding to `fieldKey`. + // + auto valType = tryGetPointedToType(&builder, val->getDataType()); + if(!valType) + return; + + auto valStructType = as<IRStructType>(valType); + if(!valStructType) + return; + + UInt slotOperandOffset = 0; + + IRStructField* foundField = nullptr; + for( auto valField : valStructType->getFields() ) + { + if( valField->getKey() == fieldKey ) + { + foundField = valField; + break; + } + + slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType()); + } + + if(!foundField) + return; + + auto foundFieldType = foundField->getFieldType(); + + List<IRInst*> slotOperands; + UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType); + + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii)); + } + + auto newGetFieldAddr = builder.emitFieldAddress( + builder.getPtrType(foundFieldType), + val, + fieldKey); + + auto newWrapExistentialInst = builder.emitWrapExistential( + resultType, + newGetFieldAddr, + slotOperandCount, + slotOperands.getBuffer()); + + addUsersToWorkList(inst); + inst->replaceUsesWith(newWrapExistentialInst); + inst->removeAndDeallocate(); + } + } + + UInt calcExistentialTypeParamSlotCount(IRType* type) + { + top: + if( as<IRInterfaceType>(type) ) + { + return 2; + } + else if( auto ptrType = as<IRPtrTypeBase>(type) ) + { + type = ptrType->getValueType(); + goto top; + } + else if( auto ptrLikeType = as<IRPointerLikeType>(type) ) + { + type = ptrLikeType->getElementType(); + goto top; + } + else if( auto structType = as<IRStructType>(type) ) + { + UInt count = 0; + for( auto field : structType->getFields() ) + { + count += calcExistentialTypeParamSlotCount(field->getFieldType()); + } + return count; + } + else + { + return 0; + } + } + + Dictionary<IRSimpleSpecializationKey, IRStructType*> existentialSpecializedStructs; + void maybeSpecializeBindExistentialsType(IRBindExistentialsType* type) { auto baseType = type->getBaseType(); @@ -1253,17 +1577,82 @@ struct SpecializationContext baseElementType, slotOperandCount, type->getExistentialArgs()); + addToWorkList(wrappedElementType); auto newPtrLikeType = builder.getType( basePtrLikeType->op, 1, &wrappedElementType); + addToWorkList(newPtrLikeType); addUsersToWorkList(type); type->replaceUsesWith(newPtrLikeType); type->removeAndDeallocate(); return; } + else if( auto baseStructType = as<IRStructType>(baseType) ) + { + // In order to bind a `struct` type we will generate + // a new specialized `struct` type on demand and then + // cache and re-use it. + // + // We don't want to start specializing here unless + // all the operand types (and witness tables) we + // will be specializing to are themselves fully + // specialized, so that we can be sure that we + // have a unique type. + // + if( !areAllOperandsFullySpecialized(type) ) + return; + + // Now we we check to see if we've already created + // a specialized struct type or not. + // + IRSimpleSpecializationKey key; + key.vals.add(baseStructType); + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + key.vals.add(type->getExistentialArg(ii)); + } + + IRStructType* newStructType = nullptr; + if( !existentialSpecializedStructs.TryGetValue(key, newStructType) ) + { + builder.setInsertBefore(baseStructType); + newStructType = builder.createStructType(); + + auto fieldSlotArgs = type->getExistentialArgs(); + + for( auto oldField : baseStructType->getFields() ) + { + // TODO: we need to figure out which of the specialization arguments + // apply to this field... + + auto oldFieldType = oldField->getFieldType(); + auto fieldSlotArgCount = calcExistentialTypeParamSlotCount(oldFieldType); + + auto newFieldType = builder.getBindExistentialsType( + oldFieldType, + fieldSlotArgCount, + fieldSlotArgs); + + addToWorkList(newFieldType); + + fieldSlotArgs += fieldSlotArgCount; + + builder.createStructField(newStructType, oldField->getKey(), newFieldType); + } + + existentialSpecializedStructs.Add(key, newStructType); + addToWorkList(newStructType); + } + + addUsersToWorkList(type); + type->replaceUsesWith(newStructType); + type->removeAndDeallocate(); + return; + + } } // The handling of specialization for global generic type diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 150a5d544..5015fda9d 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -636,6 +636,33 @@ namespace Slang block->insertAtEnd(this); } + void fixUpFuncType(IRFunc* func) + { + SLANG_ASSERT(func); + + auto irModule = func->getModule(); + SLANG_ASSERT(irModule); + + SharedIRBuilder sharedBuilder; + sharedBuilder.module = irModule; + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilder; + + builder.setInsertBefore(func); + + List<IRType*> paramTypes; + for(auto param : func->getParams()) + { + paramTypes.add(param->getFullType()); + } + + auto resultType = func->getResultType(); + + auto funcType = builder.getFuncType(paramTypes, resultType); + builder.setDataType(func, funcType); + } + // bool isTerminatorInst(IROp op) @@ -1828,6 +1855,9 @@ namespace Slang UInt slotArgCount, IRInst* const* slotArgs) { + if(slotArgCount == 0) + return (IRType*) baseType; + // If we are trying to bind an interface type, then // we will go ahead and simplify the instruction // away impmediately. @@ -1859,6 +1889,9 @@ namespace Slang UInt slotArgCount, IRUse const* slotArgUses) { + if(slotArgCount == 0) + return (IRType*) baseType; + List<IRInst*> slotArgs; for( UInt ii = 0; ii < slotArgCount; ++ii ) { @@ -2091,6 +2124,9 @@ namespace Slang UInt slotArgCount, IRInst* const* slotArgs) { + if(slotArgCount == 0) + return value; + // If we are wrapping a single concrete value into // an interface type, then this is really a `makeExistential` // @@ -3978,13 +4014,14 @@ namespace Slang void IRInst::insertAfter(IRInst* other) { SLANG_ASSERT(other); - + removeFromParent(); _insertAt(other, other->getNextInst(), other->getParent()); } void IRInst::insertAtEnd(IRInst* newParent) { SLANG_ASSERT(newParent); + removeFromParent(); _insertAt(newParent->getLastDecorationOrChild(), nullptr, newParent); } diff --git a/source/slang/ir.h b/source/slang/ir.h index d4497ec91..61cae5847 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -1075,6 +1075,9 @@ struct IRFunc : IRGlobalValueWithParams IR_LEAF_ISA(Func) }; + /// Adjust the type of an IR function based on its parameter list. +void fixUpFuncType(IRFunc* func); + // A generic is akin to a function, but is conceptually executed // before runtime, to specialize the code nested within. // diff --git a/tests/compute/interface-shader-param-in-struct.slang b/tests/compute/interface-shader-param-in-struct.slang new file mode 100644 index 000000000..2ffc70c36 --- /dev/null +++ b/tests/compute/interface-shader-param-in-struct.slang @@ -0,0 +1,127 @@ +// interface-shader-param-in-struct.slang + +// This test puts interface-type shader parameters +// inside of structure types to make sure that works + +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute +//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 +//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute + +// A lot of the setup is the same as for `interface-shader-param`, +// so look there if you want the comments. + +interface IRandomNumberGenerator +{ + [mutating] + int randomInt(); +} + +interface IRandomNumberGenerationStrategy +{ + associatedtype Generator : IRandomNumberGenerator; + Generator makeGenerator(int seed); +} + +interface IModifier +{ + int modify(int val); +} + +int test( + int seed, + IRandomNumberGenerationStrategy inStrategy, + IModifier modifier) +{ + let strategy = inStrategy; + var generator = strategy.makeGenerator(seed); + let unused = generator.randomInt(); + let val = generator.randomInt(); + let modifiedVal = modifier.modify(val); + return modifiedVal; +} + + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out +RWStructuredBuffer<int> gOutputBuffer; + +cbuffer C +{ + IRandomNumberGenerationStrategy gStrategy; +} + +struct Stuff +{ + IModifier modifier; + int extra; +} + +[numthreads(4, 1, 1)] +void computeMain( +//TEST_INPUT:cbuffer(data=[256]):dxbinding(0),glbinding(2) + uniform Stuff stuff, + + uint3 dispatchThreadID : SV_DispatchThreadID) +{ + let tid = dispatchThreadID.x; + + let inputVal : int = tid; + let outputVal = test(inputVal, gStrategy, stuff.modifier) + + stuff.extra*stuff.extra; + + gOutputBuffer[tid] = outputVal; +} + +// Okay, now we get to the part that is unique starting +// in this test: we add data to the concrete types +// that we will use as parameters. + +struct MyStrategy : IRandomNumberGenerationStrategy +{ + RWStructuredBuffer<int> globalSeeds; + + struct Generator : IRandomNumberGenerator + { + int state; + + [mutating] + int randomInt() + { + return state++; + } + } + + Generator makeGenerator(int seed) + { + Generator generator = { globalSeeds[seed] }; + return generator; + } +} + +struct MyModifier : IModifier +{ + RWStructuredBuffer<int> localModifiers; + + int modify(int val) + { + return val ^ localModifiers[val & 3]; + } +} + +//TEST_INPUT: globalExistentialType MyStrategy +//TEST_INPUT: entryPointExistentialType MyModifier + +// The concrete types we plug in for `gStrategy` and `modifier` +// have buffer resources in them, so we need to assign them +// data. The registers/bindings for these parameters will +// always come after all other shader parameters, and their +// relative order will match the relative order of their +// declarations in the global order that Slang uses for +// assigning bindings (all globals before all entry point parameters). +// +// Here's the data for `gStrategy`: +// +//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1) +// +// Here's the data for `stuff.modifier`: +// +//TEST_INPUT:ubuffer(data=[16 32 64 128], stride=4):dxbinding(2),glbinding(3) diff --git a/tests/compute/interface-shader-param-in-struct.slang.expected.txt b/tests/compute/interface-shader-param-in-struct.slang.expected.txt new file mode 100644 index 000000000..e962124e1 --- /dev/null +++ b/tests/compute/interface-shader-param-in-struct.slang.expected.txt @@ -0,0 +1,4 @@ +10042 +10083 +10025 +10029 |
