diff --git a/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp b/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp index c459d4e768..caab7af301 100644 --- a/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp +++ b/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp @@ -10,6 +10,7 @@ #include "dxc/DXIL/DxilOperations.h" #include "dxc/DXIL/DxilUtil.h" +#include "dxc/DXIL/DxilConstants.h" #include "dxc/DXIL/DxilInstructions.h" #include "dxc/DXIL/DxilModule.h" #include "dxc/DxilPIXPasses/DxilPIXPasses.h" @@ -98,13 +99,32 @@ bool DxilPIXAddTidToAmplificationShaderPayload::runOnModule(Module &M) { OriginalPayloadStructPointerType->getPointerElementType(); ExpandedStruct expanded = ExpandStructType(Ctx, OriginalPayloadStructType); + unsigned expandedPayloadSizeInBytes = + (unsigned)M.getDataLayout().getTypeAllocSize( + expanded.ExpandedPayloadStructType); + if (expandedPayloadSizeInBytes > DXIL::kMaxMSASPayloadBytes) { + return false; + } + + if (OSOverride != nullptr) { + auto const *expandedLayout = M.getDataLayout().getStructLayout( + cast(expanded.ExpandedPayloadStructType)); + unsigned appendedFieldsOffsetInBytes = + (unsigned)expandedLayout->getElementOffset( + expanded.ExpandedPayloadStructType->getStructNumElements() - 3); + *OSOverride << "ExpandedPayloadSize:" + << std::to_string(expandedPayloadSizeInBytes) << "\n"; + *OSOverride << "ExpandedPayloadAppendedFieldsOffset:" + << std::to_string(appendedFieldsOffsetInBytes) << "\n"; + } llvm::IRBuilder<> B(&*I); auto *NewStructAlloca = B.CreateAlloca(expanded.ExpandedPayloadStructType, HlslOP->GetU32Const(1), "NewPayload"); - NewStructAlloca->setAlignment(4); + NewStructAlloca->setAlignment(M.getDataLayout().getABITypeAlignment( + expanded.ExpandedPayloadStructType)); auto PayloadType = llvm::dyn_cast(DispatchMesh.get_payload()->getType()); SmallVector GEPIndices; @@ -188,6 +208,8 @@ bool DxilPIXAddTidToAmplificationShaderPayload::runOnModule(Module &M) { I->removeFromParent(); delete &*I; PIXPassHelpers::EraseIfUnused(DM, OriginalDispatchMeshFn); + DM.GetDxilFunctionProps(entryFunction).ShaderProps.AS.payloadSizeInBytes = + expandedPayloadSizeInBytes; // Validation requires exactly one DispatchMesh in an AS, so we can exit // after the first one: DM.ReEmitDxilResources(); diff --git a/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp b/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp index 418c285576..77e9fcddd5 100644 --- a/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp +++ b/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp @@ -13,6 +13,7 @@ #include "dxc/DXIL/DxilOperations.h" #include "dxc/DXIL/DxilUtil.h" +#include "dxc/DXIL/DxilConstants.h" #include "dxc/DXIL/DxilInstructions.h" #include "dxc/DXIL/DxilModule.h" #include "dxc/DxilPIXPasses/DxilPIXPasses.h" @@ -71,6 +72,8 @@ class DxilPIXMeshShaderOutputInstrumentation : public ModulePass { bool m_ExpandPayload = false; uint32_t m_DispatchArgumentY = 1; uint32_t m_DispatchArgumentZ = 1; + uint32_t m_ExpandedPayloadSize = 0; + uint32_t m_ExpandedPayloadAppendedFieldsOffset = 0; struct BuilderContext { Module &M; @@ -82,7 +85,7 @@ class DxilPIXMeshShaderOutputInstrumentation : public ModulePass { SmallVector insertInstructionsToCreateDisambiguationValue( IRBuilder<> &Builder, OP *HlslOP, LLVMContext &Ctx, - StructType *originalPayloadStructType, Instruction *firstGetPayload); + unsigned appendedFieldsElementIndex, Instruction *firstGetPayload); Value *reserveDebugEntrySpace(BuilderContext &BC, uint32_t SpaceInBytes); uint32_t UAVDumpingGroundOffset(); Value *writeDwordAndReturnNewOffset(BuilderContext &BC, Value *TheOffset, @@ -95,6 +98,9 @@ void DxilPIXMeshShaderOutputInstrumentation::applyOptions(PassOptions O) { GetPassOptionBool(O, "expand-payload", &m_ExpandPayload, 0); GetPassOptionUInt32(O, "dispatchArgY", &m_DispatchArgumentY, 1); GetPassOptionUInt32(O, "dispatchArgZ", &m_DispatchArgumentZ, 1); + GetPassOptionUInt32(O, "expanded-payload-size", &m_ExpandedPayloadSize, 0); + GetPassOptionUInt32(O, "expanded-payload-offset", + &m_ExpandedPayloadAppendedFieldsOffset, 0); } uint32_t DxilPIXMeshShaderOutputInstrumentation::UAVDumpingGroundOffset() { @@ -108,7 +114,7 @@ Value *DxilPIXMeshShaderOutputInstrumentation::reserveDebugEntrySpace( // Check that the caller didn't ask for so much memory that it will // overwrite the offset counter: - assert(m_RemainingReservedSpaceInBytes < (int)CounterOffsetBeyondUsefulData); + assert(SpaceInBytes < CounterOffsetBeyondUsefulData); m_RemainingReservedSpaceInBytes = SpaceInBytes; @@ -187,7 +193,6 @@ void DxilPIXMeshShaderOutputInstrumentation::Instrument(BuilderContext &BC, } Value *GetValueFromExpandedPayload(IRBuilder<> &Builder, - StructType *originalPayloadStructType, Instruction *firstGetPayload, unsigned int offset, const char *name) { auto *DerefPointer = Builder.getInt32(0); @@ -202,7 +207,7 @@ Value *GetValueFromExpandedPayload(IRBuilder<> &Builder, SmallVector DxilPIXMeshShaderOutputInstrumentation:: insertInstructionsToCreateDisambiguationValue( IRBuilder<> &Builder, OP *HlslOP, LLVMContext &Ctx, - StructType *originalPayloadStructType, Instruction *firstGetPayload) { + unsigned appendedFieldsElementIndex, Instruction *firstGetPayload) { // When a mesh shader is called from an amplification shader, all of the // thread id values are relative to the DispatchMesh call made by @@ -213,23 +218,20 @@ SmallVector DxilPIXMeshShaderOutputInstrumentation:: SmallVector ret; Constant *Zero32Arg = HlslOP->GetU32Const(0); - bool AmplificationShaderIsActive = originalPayloadStructType != nullptr; + bool AmplificationShaderIsActive = firstGetPayload != nullptr; llvm::Value *ASDispatchMeshYCount = nullptr; llvm::Value *ASDispatchMeshZCount = nullptr; if (AmplificationShaderIsActive) { auto *ASThreadId = GetValueFromExpandedPayload( - Builder, originalPayloadStructType, firstGetPayload, - originalPayloadStructType->getStructNumElements(), "ASThreadId"); + Builder, firstGetPayload, appendedFieldsElementIndex, "ASThreadId"); ret.push_back(ASThreadId); ASDispatchMeshYCount = GetValueFromExpandedPayload( - Builder, originalPayloadStructType, firstGetPayload, - originalPayloadStructType->getStructNumElements() + 1, + Builder, firstGetPayload, appendedFieldsElementIndex + 1, "ASDispatchMeshYCount"); ASDispatchMeshZCount = GetValueFromExpandedPayload( - Builder, originalPayloadStructType, firstGetPayload, - originalPayloadStructType->getStructNumElements() + 2, + Builder, firstGetPayload, appendedFieldsElementIndex + 2, "ASDispatchMeshZCount"); } else { ret.push_back(Zero32Arg); @@ -270,6 +272,150 @@ SmallVector DxilPIXMeshShaderOutputInstrumentation:: return ret; } +static bool IsValidExpandedPayloadLayout(uint32_t ExpandedSizeInBytes, + uint32_t AppendedFieldsOffsetInBytes) { + constexpr uint32_t AppendedFieldsSizeInBytes = 3 * sizeof(uint32_t); + return AppendedFieldsOffsetInBytes % sizeof(uint32_t) == 0 && + AppendedFieldsOffsetInBytes <= DXIL::kMaxMSASPayloadBytes && + ExpandedSizeInBytes % sizeof(uint32_t) == 0 && + ExpandedSizeInBytes >= + AppendedFieldsOffsetInBytes + AppendedFieldsSizeInBytes && + ExpandedSizeInBytes <= DXIL::kMaxMSASPayloadBytes; +} + +static ExpandedStruct BuildExpandedPayloadTypeMatchingAmplificationShader( + Module &M, LLVMContext &Ctx, Type *OriginalPayloadStructType, + uint32_t ExpandedSizeInBytes, uint32_t AppendedFieldsOffsetInBytes, + unsigned *AppendedFieldsElementIndex) { + ExpandedStruct ret = {}; + auto *OriginalStructType = dyn_cast(OriginalPayloadStructType); + if (OriginalStructType == nullptr || OriginalStructType->isOpaque() || + !IsValidExpandedPayloadLayout(ExpandedSizeInBytes, + AppendedFieldsOffsetInBytes)) { + return ret; + } + + constexpr uint32_t AppendedFieldsSizeInBytes = 3 * sizeof(uint32_t); + const DataLayout &DL = M.getDataLayout(); + const StructLayout *OriginalLayout = DL.getStructLayout(OriginalStructType); + auto *Int32Type = Type::getInt32Ty(Ctx); + const unsigned OriginalElementCount = OriginalStructType->getNumElements(); + + // Try the natural layout before a packed fallback. + const bool PackedCandidates[] = {false, true}; + for (bool Packed : PackedCandidates) { + SmallVector Elements; + for (unsigned i = 0; i < OriginalElementCount; ++i) { + Elements.push_back(OriginalStructType->getElementType(i)); + } + + Elements.push_back(Int32Type); + Elements.push_back(Int32Type); + Elements.push_back(Int32Type); + uint64_t UnpaddedOffsetInBytes = + DL.getStructLayout(StructType::get(Ctx, Elements, Packed)) + ->getElementOffset(OriginalElementCount); + Elements.resize(OriginalElementCount); + if (UnpaddedOffsetInBytes > AppendedFieldsOffsetInBytes) { + continue; + } + + unsigned AppendedIndex = OriginalElementCount; + uint64_t MidPaddingInBytes = + AppendedFieldsOffsetInBytes - UnpaddedOffsetInBytes; + if (MidPaddingInBytes != 0) { + Elements.push_back( + ArrayType::get(Int32Type, MidPaddingInBytes / sizeof(uint32_t))); + ++AppendedIndex; + } + Elements.push_back(Int32Type); + Elements.push_back(Int32Type); + Elements.push_back(Int32Type); + uint32_t TailPaddingInBytes = ExpandedSizeInBytes - + AppendedFieldsOffsetInBytes - + AppendedFieldsSizeInBytes; + if (TailPaddingInBytes != 0) { + Elements.push_back( + ArrayType::get(Int32Type, TailPaddingInBytes / sizeof(uint32_t))); + } + + StructType *Candidate = StructType::get(Ctx, Elements, Packed); + const StructLayout *CandidateLayout = DL.getStructLayout(Candidate); + // Verify the candidate preserves both payload layouts. + if (DL.getTypeAllocSize(Candidate) != ExpandedSizeInBytes || + CandidateLayout->getElementOffset(AppendedIndex) != + AppendedFieldsOffsetInBytes) { + continue; + } + + bool OriginalFieldsUnmoved = true; + for (unsigned i = 0; i < OriginalElementCount; ++i) { + if (CandidateLayout->getElementOffset(i) != + OriginalLayout->getElementOffset(i)) { + OriginalFieldsUnmoved = false; + break; + } + } + if (!OriginalFieldsUnmoved) { + continue; + } + + *AppendedFieldsElementIndex = AppendedIndex; + ret.ExpandedPayloadStructType = + StructType::create(Ctx, Elements, "PIX_AS2MS_Expanded_Type", Packed); + ret.ExpandedPayloadStructPtrType = + ret.ExpandedPayloadStructType->getPointerTo(); + return ret; + } + + return ret; +} + +static ExpandedStruct +SynthesizeExpandedPayloadType(LLVMContext &Ctx, uint32_t ExpandedSizeInBytes, + uint32_t AppendedFieldsOffsetInBytes) { + ExpandedStruct ret = {}; + if (!IsValidExpandedPayloadLayout(ExpandedSizeInBytes, + AppendedFieldsOffsetInBytes)) { + return ret; + } + + constexpr uint32_t AppendedFieldsSizeInBytes = 3 * sizeof(uint32_t); + auto *Int32Type = Type::getInt32Ty(Ctx); + auto *OpaqueOriginalPayloadType = + ArrayType::get(Int32Type, AppendedFieldsOffsetInBytes / sizeof(uint32_t)); + SmallVector Elements{OpaqueOriginalPayloadType, Int32Type, + Int32Type, Int32Type}; + uint32_t TailPaddingInBytes = ExpandedSizeInBytes - + AppendedFieldsOffsetInBytes - + AppendedFieldsSizeInBytes; + if (TailPaddingInBytes != 0) { + Elements.push_back( + ArrayType::get(Int32Type, TailPaddingInBytes / sizeof(uint32_t))); + } + + ret.ExpandedPayloadStructType = + StructType::create(Ctx, Elements, "PIX_AS2MS_Expanded_Type"); + ret.ExpandedPayloadStructPtrType = + ret.ExpandedPayloadStructType->getPointerTo(); + return ret; +} + +static bool OutputSignatureElementIsSigned(DxilModule &DM, Value *OutputSigId) { + auto *SigIdConstant = dyn_cast(OutputSigId); + if (SigIdConstant == nullptr) { + return false; + } + const DxilSignature &OutputSignature = DM.GetOutputSignature(); + uint64_t SigId = SigIdConstant->getLimitedValue(); + if (SigId >= OutputSignature.GetElements().size()) { + return false; + } + return OutputSignature.GetElement(static_cast(SigId)) + .GetCompType() + .IsSIntTy(); +} + bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { DxilModule &DM = M.GetOrCreateDxilModule(); LLVMContext &Ctx = M.getContext(); @@ -277,6 +423,7 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { Type *OriginalPayloadStructType = nullptr; ExpandedStruct expanded = {}; + unsigned AppendedFieldsElementIndex = 0; Instruction *FirstNewStructGetMeshPayload = nullptr; if (m_ExpandPayload) { Instruction *getMeshPayloadInstructions = nullptr; @@ -298,37 +445,64 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { } } - if (OriginalPayloadStructType == nullptr) { - // If the application used no payload, then we won't attempt to add one. - // TODO: Is there a credible use case with no AS->MS payload? - // PIX bug #35288335 - return false; - } - - if (expanded.ExpandedPayloadStructPtrType == nullptr) { - expanded = ExpandStructType(Ctx, OriginalPayloadStructType); - } + if (OriginalPayloadStructType != nullptr) { + if (m_ExpandedPayloadSize != 0) { + expanded = BuildExpandedPayloadTypeMatchingAmplificationShader( + M, Ctx, OriginalPayloadStructType, m_ExpandedPayloadSize, + m_ExpandedPayloadAppendedFieldsOffset, &AppendedFieldsElementIndex); + if (expanded.ExpandedPayloadStructPtrType == nullptr && + OSOverride != nullptr) { + *OSOverride << "MeshPayloadExpansionFailed\n"; + } + } else { + expanded = ExpandStructType(Ctx, OriginalPayloadStructType); + AppendedFieldsElementIndex = + OriginalPayloadStructType->getStructNumElements(); + unsigned expandedPayloadSizeInBytes = + (unsigned)M.getDataLayout().getTypeAllocSize( + expanded.ExpandedPayloadStructType); + if (expandedPayloadSizeInBytes > DXIL::kMaxMSASPayloadBytes) { + if (OSOverride != nullptr) { + *OSOverride << "MeshPayloadExpansionFailed\n"; + } + expanded = {}; + } + } - if (getMeshPayloadInstructions != nullptr) { - llvm::Function *OriginalGetMeshPayloadFunction = - cast(getMeshPayloadInstructions)->getCalledFunction(); + if (expanded.ExpandedPayloadStructPtrType != nullptr) { + llvm::Function *OriginalGetMeshPayloadFunction = + cast(getMeshPayloadInstructions)->getCalledFunction(); - Function *DxilFunc = HlslOP->GetOpFunc( - OP::OpCode::GetMeshPayload, expanded.ExpandedPayloadStructPtrType); - Constant *opArg = - HlslOP->GetU32Const((unsigned)OP::OpCode::GetMeshPayload); - IRBuilder<> Builder(getMeshPayloadInstructions); - Value *args[] = {opArg}; - Instruction *payload = Builder.CreateCall(DxilFunc, args); + Function *DxilFunc = HlslOP->GetOpFunc( + OP::OpCode::GetMeshPayload, expanded.ExpandedPayloadStructPtrType); + Constant *opArg = + HlslOP->GetU32Const((unsigned)OP::OpCode::GetMeshPayload); + IRBuilder<> Builder(getMeshPayloadInstructions); + Value *args[] = {opArg}; + Instruction *payload = Builder.CreateCall(DxilFunc, args); - if (FirstNewStructGetMeshPayload == nullptr) { FirstNewStructGetMeshPayload = payload; + ReplaceAllUsesOfInstructionWithNewValueAndDeleteInstruction( + getMeshPayloadInstructions, payload, + expanded.ExpandedPayloadStructType); + PIXPassHelpers::EraseIfUnused(DM, OriginalGetMeshPayloadFunction); + } + } else if (m_ExpandedPayloadSize != 0) { + expanded = SynthesizeExpandedPayloadType( + Ctx, m_ExpandedPayloadSize, m_ExpandedPayloadAppendedFieldsOffset); + if (expanded.ExpandedPayloadStructPtrType != nullptr) { + AppendedFieldsElementIndex = 1; + IRBuilder<> Builder(dxilutil::FirstNonAllocaInsertionPt( + PIXPassHelpers::GetEntryFunction(DM))); + Function *DxilFunc = HlslOP->GetOpFunc( + OP::OpCode::GetMeshPayload, expanded.ExpandedPayloadStructPtrType); + Constant *opArg = + HlslOP->GetU32Const((unsigned)OP::OpCode::GetMeshPayload); + Value *args[] = {opArg}; + FirstNewStructGetMeshPayload = Builder.CreateCall(DxilFunc, args); + } else if (OSOverride != nullptr) { + *OSOverride << "MeshPayloadExpansionFailed\n"; } - - ReplaceAllUsesOfInstructionWithNewValueAndDeleteInstruction( - getMeshPayloadInstructions, payload, - expanded.ExpandedPayloadStructType); - PIXPassHelpers::EraseIfUnused(DM, OriginalGetMeshPayloadFunction); } } @@ -347,11 +521,11 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { PIXPassHelpers::GetEntryFunction(DM)); IRBuilder<> Builder(firstInsertionPt); m_threadUniquifier = insertInstructionsToCreateDisambiguationValue( - Builder, HlslOP, Ctx, nullptr, nullptr); + Builder, HlslOP, Ctx, 0, nullptr); } else { IRBuilder<> Builder(FirstNewStructGetMeshPayload->getNextNode()); m_threadUniquifier = insertInstructionsToCreateDisambiguationValue( - Builder, HlslOP, Ctx, cast(OriginalPayloadStructType), + Builder, HlslOP, Ctx, AppendedFieldsElementIndex, FirstNewStructGetMeshPayload); } @@ -371,6 +545,7 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { Call->getOperand(1), Call->getOperand(2), Call->getOperand(3), Call->getOperand(4)); } + PIXPassHelpers::EraseIfUnused(DM, F); struct OutputType { Type *type; @@ -413,7 +588,11 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { CoercedValue = BC2.Builder.CreateCast(Instruction::ZExt, HalfInt, Type::getInt32Ty(Ctx)); } else if (Overload.tag == int16ValueIndicator) { - CoercedValue = BC2.Builder.CreateCast(Instruction::ZExt, CoercedValue, + Instruction::CastOps ExtensionKind = + OutputSignatureElementIsSigned(DM, Call->getOperand(1)) + ? Instruction::SExt + : Instruction::ZExt; + CoercedValue = BC2.Builder.CreateCast(ExtensionKind, CoercedValue, Type::getInt32Ty(Ctx)); } @@ -428,6 +607,13 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { PIXPassHelpers::EraseIfUnused(DM, StoreVertexOutputFunction); } + if (expanded.ExpandedPayloadStructType != nullptr) { + DM.GetDxilFunctionProps(PIXPassHelpers::GetEntryFunction(DM)) + .ShaderProps.MS.payloadSizeInBytes = + (unsigned)M.getDataLayout().getTypeAllocSize( + expanded.ExpandedPayloadStructType); + } + DM.ReEmitDxilResources(); return true; diff --git a/tools/clang/test/HLSLFileCheck/pix/AddThreadIdWhenMSPayloadIsUnused.hlsl b/tools/clang/test/HLSLFileCheck/pix/AddThreadIdWhenMSPayloadIsUnused.hlsl index 080e6f7265..1d639890c0 100644 --- a/tools/clang/test/HLSLFileCheck/pix/AddThreadIdWhenMSPayloadIsUnused.hlsl +++ b/tools/clang/test/HLSLFileCheck/pix/AddThreadIdWhenMSPayloadIsUnused.hlsl @@ -1,7 +1,15 @@ // RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192 | %FileCheck %s -// CHECK-NOT: mul i32 %ThreadIdX, 3 -// CHECK-NOT: mul i32 +// CHECK-NOT: getMeshPayload +// CHECK: %GroupIdX = call i32 @dx.op.groupId.i32(i32 94, i32 0) +// CHECK: %GroupIdY = call i32 @dx.op.groupId.i32(i32 94, i32 1) +// CHECK: %GroupIdZ = call i32 @dx.op.groupId.i32(i32 94, i32 2) +// CHECK: mul i32 %GroupIdY, 1 +// CHECK: add i32 %GroupIdZ, +// CHECK: mul i32 %GroupIdX, 1 +// CHECK: %PIX_DebugUAV_Handle = call %dx.types.Handle @dx.op.createHandleFromBinding +// CHECK: @dx.op.atomicBinOp.i32 +// CHECK-NOT: getMeshPayload struct PSInput { diff --git a/tools/clang/test/HLSLFileCheck/pix/MeshOutputSignedInt16IsSignExtended.hlsl b/tools/clang/test/HLSLFileCheck/pix/MeshOutputSignedInt16IsSignExtended.hlsl new file mode 100644 index 0000000000..8fce00c403 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/MeshOutputSignedInt16IsSignExtended.hlsl @@ -0,0 +1,35 @@ +// RUN: %dxc -EMSMain -Tms_6_6 -enable-16bit-types %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,UAVSize=8192 | %FileCheck %s -check-prefixes=CHECK,NOZEROEXTENSION + +// CHECK: call void @dx.op.bufferStore.i32(i32 69, %dx.types.Handle %{{[^,]+}}, i32 %{{[^,]+}}, i32 undef, i32 -1, i32 undef, i32 undef, i32 undef, i8 1) +// CHECK: call void @dx.op.storeVertexOutput.i16(i32 171, i32 1, i32 0, i8 0, i16 -1, i32 %{{[^)]+}}) +// CHECK: call void @dx.op.bufferStore.i32(i32 69, %dx.types.Handle %{{[^,]+}}, i32 %{{[^,]+}}, i32 undef, i32 7, i32 undef, i32 undef, i32 undef, i8 1) +// CHECK: call void @dx.op.storeVertexOutput.i16(i32 171, i32 2, i32 0, i8 0, i16 7, i32 %{{[^)]+}}) +// CHECK: call void @dx.op.bufferStore.i32(i32 69, %dx.types.Handle %{{[^,]+}}, i32 %{{[^,]+}}, i32 undef, i32 48128, i32 undef, i32 undef, i32 undef, i8 1) +// CHECK: call void @dx.op.storeVertexOutput.f16(i32 171, i32 3, i32 0, i8 0, half 0xHBC00, i32 %{{[^)]+}}) +// NOZEROEXTENSION-NOT: i32 65535 + +struct PSInput +{ + float4 position : SV_POSITION; + int16_t signedValue : SIGNEDVALUE; + uint16_t unsignedValue : UNSIGNEDVALUE; + half halfValue : HALFVALUE; +}; + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[3], + out indices uint3 tris[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4(0, 0, 0, 1); + verts[tid].signedValue = -1; + verts[tid].unsignedValue = 7; + verts[tid].halfValue = -1.0h; + if (tid == 0) + { + tris[0] = uint3(0, 1, 2); + } +} diff --git a/tools/clang/test/HLSLFileCheck/pix/MeshPayloadExpansionAgreesWithAmplificationShader.hlsl b/tools/clang/test/HLSLFileCheck/pix/MeshPayloadExpansionAgreesWithAmplificationShader.hlsl new file mode 100644 index 0000000000..46c22443be --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/MeshPayloadExpansionAgreesWithAmplificationShader.hlsl @@ -0,0 +1,55 @@ +// RUN: %dxc -EASMain -Tas_6_6 %s | %opt -S -hlsl-dxil-PIX-add-tid-to-as-payload,dispatchArgY=1,dispatchArgZ=1 | %FileCheck %s -check-prefixes=AMPLIFICATION +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,dispatchArgY=1,dispatchArgZ=1,UAVSize=8192,expanded-payload-size=28,expanded-payload-offset=16 | %FileCheck %s -check-prefixes=MESH + +// AMPLIFICATION: ExpandedPayloadSize:28 +// AMPLIFICATION: ExpandedPayloadAppendedFieldsOffset:16 +// AMPLIFICATION: %PIX_AS2MS_Expanded_Type = type { [4 x i32], i32, i32, i32 } +// MESH: %PIX_AS2MS_Expanded_Type = type <{ i64, i32, [1 x i32], i32, i32, i32 }> +// MESH: [[PAYLOAD:%[0-9]+]] = call %PIX_AS2MS_Expanded_Type* @dx.op.getMeshPayload.PIX_AS2MS_Expanded_Type(i32 170) +// MESH: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 3 +// MESH: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 4 +// MESH: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 5 +// MESH: getelementptr inbounds %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 1 +// MESH: = !{{{![0-9]+}}, i32 3, i32 1, i32 2, i32 28} + +struct AmplificationPayload +{ + uint4 values; +}; + +struct MeshPayload +{ + uint64_t alignmentAnchor; + uint xOffsetSelector; +}; + +struct PSInput +{ + float4 position : SV_POSITION; + uint selector : SELECTOR; +}; + +[numthreads(3, 1, 1)] +void ASMain(uint gid : SV_GroupID, uint tid : SV_GroupThreadID) +{ + AmplificationPayload payload; + payload.values = uint4(0, 0, tid, 0); + DispatchMesh(1, 1, 1, payload); +} + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in uint tid : SV_GroupThreadID, + in payload MeshPayload payload, + out vertices PSInput verts[3], + out indices uint3 tris[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4(0, 0, 0, 1); + verts[tid].selector = 100 + payload.xOffsetSelector; + if (tid == 0) + { + tris[0] = uint3(0, 1, 2); + } +} diff --git a/tools/clang/test/HLSLFileCheck/pix/MeshShaderWithoutEmitIndicesLeavesNoDeadDeclaration.hlsl b/tools/clang/test/HLSLFileCheck/pix/MeshShaderWithoutEmitIndicesLeavesNoDeadDeclaration.hlsl new file mode 100644 index 0000000000..0d3a36644a --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/MeshShaderWithoutEmitIndicesLeavesNoDeadDeclaration.hlsl @@ -0,0 +1,25 @@ +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,UAVSize=8192 | %FileCheck %s + +// CHECK-NOT: @dx.op.emitIndices +// CHECK: %PIX_DebugUAV_Handle = call %dx.types.Handle @dx.op.createHandleFromBinding +// CHECK: call void @dx.op.bufferStore.i32 +// CHECK: call void @dx.op.storeVertexOutput.f32 +// CHECK-NOT: @dx.op.emitIndices +// CHECK-NOT: declare void @dx.op.storeVertexOutput.i16 +// CHECK-NOT: declare void @dx.op.storeVertexOutput.i32 +// CHECK-NOT: declare void @dx.op.storeVertexOutput.f16 + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(4, 1, 1)] +void MSMain( + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[4]) +{ + SetMeshOutputCounts(4, 0); + verts[tid].position = float4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeRejectsUnusableLayout.hlsl b/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeRejectsUnusableLayout.hlsl new file mode 100644 index 0000000000..0cb8a41641 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeRejectsUnusableLayout.hlsl @@ -0,0 +1,40 @@ +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=16,expanded-payload-offset=6 | %FileCheck %s +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=18,expanded-payload-offset=4 | %FileCheck %s +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=12,expanded-payload-offset=4 | %FileCheck %s +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=16388,expanded-payload-offset=4 | %FileCheck %s +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=16,expanded-payload-offset=4294967292 | %FileCheck %s + +// CHECK: MeshPayloadExpansionFailed +// CHECK-NOT: %PIX_AS2MS_Expanded_Type +// CHECK-NOT: getMeshPayload +// CHECK: %GroupIdX = call i32 @dx.op.groupId.i32(i32 94, i32 0) +// CHECK: %GroupIdY = call i32 @dx.op.groupId.i32(i32 94, i32 1) +// CHECK: %GroupIdZ = call i32 @dx.op.groupId.i32(i32 94, i32 2) +// CHECK: %PIX_DebugUAV_Handle = call %dx.types.Handle @dx.op.createHandleFromBinding +// CHECK: call void @dx.op.bufferStore.i32 +// CHECK: call void @dx.op.storeVertexOutput.f32 +// CHECK-NOT: %PIX_AS2MS_Expanded_Type +// CHECK-NOT: getMeshPayload + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +struct Payload +{ + uint value; +}; + +[outputtopology("triangle")] +[numthreads(4, 1, 1)] +void MSMain( + in payload Payload payload, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[4], + out indices uint3 triangles[2]) +{ + SetMeshOutputCounts(4, 2); + verts[tid].position = float4(0, 0, 0, 0); + triangles[tid % 2] = uint3(0, tid + 1, tid + 2); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeShapes.hlsl b/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeShapes.hlsl new file mode 100644 index 0000000000..4e1427cc3f --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeShapes.hlsl @@ -0,0 +1,45 @@ +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=16,expanded-payload-offset=4 | %FileCheck %s -check-prefixes=EXACTFIT +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=32,expanded-payload-offset=4 | %FileCheck %s -check-prefixes=TAILPADDING +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=12,expanded-payload-offset=0 | %FileCheck %s -check-prefixes=EMPTYPAYLOAD +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=28,expanded-payload-offset=16 | %FileCheck %s -check-prefixes=MISMATCHEDLAYOUT +// RUN: %dxc -EMSMain -Tms_6_6 %s | %opt -S -hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1,UAVSize=8192,expanded-payload-size=16384,expanded-payload-offset=16368 | %FileCheck %s -check-prefixes=MAXPAYLOAD + +// EXACTFIT: %PIX_AS2MS_Expanded_Type = type { [1 x i32], i32, i32, i32 } +// EXACTFIT: [[PAYLOAD:%[0-9]+]] = call %PIX_AS2MS_Expanded_Type* @dx.op.getMeshPayload.PIX_AS2MS_Expanded_Type(i32 170) +// EXACTFIT: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 1 +// EXACTFIT: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 2 +// EXACTFIT: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[PAYLOAD]], i32 0, i32 3 +// EXACTFIT: = !{{{![0-9]+}}, i32 4, i32 2, i32 2, i32 16} +// TAILPADDING: %PIX_AS2MS_Expanded_Type = type { [1 x i32], i32, i32, i32, [4 x i32] } +// TAILPADDING: = !{{{![0-9]+}}, i32 4, i32 2, i32 2, i32 32} +// EMPTYPAYLOAD: %PIX_AS2MS_Expanded_Type = type { [0 x i32], i32, i32, i32 } +// EMPTYPAYLOAD: [[EMPTY:%[0-9]+]] = call %PIX_AS2MS_Expanded_Type* @dx.op.getMeshPayload.PIX_AS2MS_Expanded_Type(i32 170) +// EMPTYPAYLOAD: getelementptr %PIX_AS2MS_Expanded_Type, %PIX_AS2MS_Expanded_Type* [[EMPTY]], i32 0, i32 1 +// EMPTYPAYLOAD: = !{{{![0-9]+}}, i32 4, i32 2, i32 2, i32 12} +// MISMATCHEDLAYOUT: %PIX_AS2MS_Expanded_Type = type { [4 x i32], i32, i32, i32 } +// MISMATCHEDLAYOUT: = !{{{![0-9]+}}, i32 4, i32 2, i32 2, i32 28} +// MAXPAYLOAD: %PIX_AS2MS_Expanded_Type = type { [4092 x i32], i32, i32, i32, [1 x i32] } +// MAXPAYLOAD: = !{{{![0-9]+}}, i32 4, i32 2, i32 2, i32 16384} + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +struct Payload +{ + uint value; +}; + +[outputtopology("triangle")] +[numthreads(4, 1, 1)] +void MSMain( + in payload Payload payload, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[4], + out indices uint3 triangles[2]) +{ + SetMeshOutputCounts(4, 2); + verts[tid].position = float4(0, 0, 0, 0); + triangles[tid % 2] = uint3(0, tid + 1, tid + 2); +} diff --git a/tools/clang/unittests/HLSL/PixTest.cpp b/tools/clang/unittests/HLSL/PixTest.cpp index e169cf8b48..55fd61ce23 100644 --- a/tools/clang/unittests/HLSL/PixTest.cpp +++ b/tools/clang/unittests/HLSL/PixTest.cpp @@ -116,6 +116,12 @@ class PixTest : public ::testing::Test { TEST_METHOD(CompileDebugDisasmPDB) TEST_METHOD(AddToASPayload) + TEST_METHOD(AddToASPayload_TailPadding) + TEST_METHOD(AddToASPayload_NearLimitPayloadIsNotExpanded) + TEST_METHOD(MeshShaderOutput_UnreadPayloadStillInstruments) + TEST_METHOD(MeshShaderOutput_NearLimitPayloadSkipsExpansion) + TEST_METHOD(Validation_MeshShaderOutput_And_AmplificationPayload) + TEST_METHOD(AddToASPayload_ExpandedAllocaKeepsTheNaturalAlignment) TEST_METHOD(AddToASGroupSharedPayload) TEST_METHOD(AddToASGroupSharedPayload_MeshletCullSample) TEST_METHOD(SignatureModification_Empty) @@ -901,14 +907,22 @@ class PixTest : public ::testing::Test { size_t index, const char *name); PassOutput RunShaderAccessTrackingPass( IDxcBlob *blob, const wchar_t *config = L"U0:0:10i0;U0:1:2i0;.0;0;0."); + CComPtr RunDxilPIXAddTidToAmplificationShaderPayloadPass( + IDxcBlob *blob, std::string *outputText = nullptr); CComPtr - RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob); - CComPtr RunDxilPIXMeshShaderOutputPass(IDxcBlob *blob); + RunDxilPIXMeshShaderOutputPass(IDxcBlob *blob, + std::wstring const &additionalOptions = L"", + std::string *outputText = nullptr); CComPtr RunDxilPIXDXRInvocationsLog(IDxcBlob *blob, unsigned maxNumEntriesInLog = 24); PassOutput RunDxilNonUniformResourceIndexInstrumentation(IDxcBlob *blob, std::string &outputText); + void + VerifyDeclaredPayloadSizeMatchesExpandedStruct(IDxcBlob *optimizedModule); + void + VerifyPayloadWasNotExpandedAndPayloadSizeIs(IDxcBlob *optimizedModule, + unsigned expectedPayloadSize); void TestNuriCase(const char *source, const wchar_t *target, uint32_t expectedResult); void TestPixUAVCase(char const *hlsl, wchar_t const *model, @@ -1224,24 +1238,29 @@ PassOutput PixTest::RunShaderAccessTrackingPass(IDxcBlob *blob, return ret; } -CComPtr PixTest::RunDxilPIXMeshShaderOutputPass(IDxcBlob *blob) { +CComPtr +PixTest::RunDxilPIXMeshShaderOutputPass(IDxcBlob *blob, + std::wstring const &additionalOptions, + std::string *outputText) { CComPtr dxil = FindModule(DFCC_ShaderDebugInfoDXIL, blob); CComPtr pOptimizer; VERIFY_SUCCEEDED( m_dllSupport.CreateInstance(CLSID_DxcOptimizer, &pOptimizer)); std::vector Options; Options.push_back(L"-opt-mod-passes"); - Options.push_back(L"-hlsl-dxil-pix-meshshader-output-instrumentation,expand-" - L"payload=1,UAVSize=8192"); + std::wstring passOption = + L"-hlsl-dxil-pix-meshshader-output-instrumentation,expand-payload=1," + L"UAVSize=8192"; + passOption += additionalOptions; + Options.push_back(passOption.c_str()); CComPtr pOptimizedModule; CComPtr pText; VERIFY_SUCCEEDED(pOptimizer->RunOptimizer( dxil, Options.data(), Options.size(), &pOptimizedModule, &pText)); - std::string outputText; - if (pText->GetBufferSize() != 0) { - outputText = reinterpret_cast(pText->GetBufferPointer()); + if (outputText != nullptr) { + *outputText = BlobToUtf8(pText); } return pOptimizedModule; @@ -1313,8 +1332,8 @@ PassOutput PixTest::RunDxilNonUniformResourceIndexInstrumentation( return result; } -CComPtr -PixTest::RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob) { +CComPtr PixTest::RunDxilPIXAddTidToAmplificationShaderPayloadPass( + IDxcBlob *blob, std::string *outputText) { CComPtr dxil = FindModule(DFCC_ShaderDebugInfoDXIL, blob); CComPtr pOptimizer; VERIFY_SUCCEEDED( @@ -1329,9 +1348,51 @@ PixTest::RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob) { VERIFY_SUCCEEDED(pOptimizer->RunOptimizer( dxil, Options.data(), Options.size(), &pOptimizedModule, &pText)); + if (outputText != nullptr) { + *outputText = BlobToUtf8(pText); + } + return pOptimizedModule; } +void PixTest::VerifyDeclaredPayloadSizeMatchesExpandedStruct( + IDxcBlob *optimizedModule) { + CComPtr container = + pix_test::WrapInNewContainer(m_dllSupport, optimizedModule); + ModuleAndHangersOn moduleEtc(container); + DxilModule &DM = moduleEtc.GetDxilModule(); + llvm::Module *M = DM.GetModule(); + llvm::StructType *expandedType = M->getTypeByName("PIX_AS2MS_Expanded_Type"); + VERIFY_IS_NOT_NULL(expandedType); + + unsigned allocSize = + (unsigned)M->getDataLayout().getTypeAllocSize(expandedType); + const DxilFunctionProps &props = + DM.GetDxilFunctionProps(DM.GetEntryFunction()); + unsigned declaredSize = props.IsAS() + ? props.ShaderProps.AS.payloadSizeInBytes + : props.ShaderProps.MS.payloadSizeInBytes; + VERIFY_ARE_EQUAL(allocSize, declaredSize); +} + +void PixTest::VerifyPayloadWasNotExpandedAndPayloadSizeIs( + IDxcBlob *optimizedModule, unsigned expectedPayloadSize) { + CComPtr container = + pix_test::WrapInNewContainer(m_dllSupport, optimizedModule); + ModuleAndHangersOn moduleEtc(container); + DxilModule &DM = moduleEtc.GetDxilModule(); + llvm::Module *M = DM.GetModule(); + llvm::StructType *expandedType = M->getTypeByName("PIX_AS2MS_Expanded_Type"); + VERIFY_IS_NULL(expandedType); + + const DxilFunctionProps &props = + DM.GetDxilFunctionProps(DM.GetEntryFunction()); + unsigned declaredSize = props.IsAS() + ? props.ShaderProps.AS.payloadSizeInBytes + : props.ShaderProps.MS.payloadSizeInBytes; + VERIFY_ARE_EQUAL(expectedPayloadSize, declaredSize); +} + static bool HasDeclaration(const std::string &disassembly, const std::string &functionName); static std::string FindDeclarationLine(const std::string &disassembly, @@ -1387,6 +1448,7 @@ void MSMain( auto asOutput = RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); VERIFY_IS_FALSE(HasDeclarationLine(Disassemble(asOutput), originalDispatchMeshDeclaration)); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(asOutput); auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); const std::string originalGetMeshPayloadDeclaration = @@ -1403,6 +1465,308 @@ void MSMain( HasDeclaration(meshDisassembly, "dx.op.storeVertexOutput.i16")); VERIFY_IS_FALSE( HasDeclaration(meshDisassembly, "dx.op.storeVertexOutput.f16")); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(msOutput); +} + +TEST_F(PixTest, AddToASPayload_TailPadding) { + const char *hlsl = R"( +struct MyPayload +{ + double d; + float f1; + float f2; +}; + +[numthreads(1, 1, 1)] +void ASMain(uint gid : SV_GroupID) +{ + MyPayload payload; + payload.d = (double)gid; + payload.f1 = (float)gid / 4.f; + payload.f2 = (float)gid * 4.f; + DispatchMesh(1, 1, 1, payload); +} + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in payload MyPayload small, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[3], + out indices uint3 triangles[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4(small.f1, small.f2, (float)small.d, 0); + triangles[0] = uint3(0, 1, 2); +} +)"; + + auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); + auto asOutput = RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(asOutput); + + auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); + auto msOutput = RunDxilPIXMeshShaderOutputPass(ms); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(msOutput); +} + +TEST_F(PixTest, AddToASPayload_NearLimitPayloadIsNotExpanded) { + const char *hlsl = R"( +struct NearLimitPayload +{ + uint values[4094]; +}; + +[numthreads(1, 1, 1)] +void ASMain() +{ + NearLimitPayload payload; + payload.values[0] = 1; + DispatchMesh(1, 1, 1, payload); +} +)"; + + auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); + auto asOutput = RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + VerifyPayloadWasNotExpandedAndPayloadSizeIs(asOutput, + 4094 * sizeof(uint32_t)); +} + +TEST_F(PixTest, MeshShaderOutput_UnreadPayloadStillInstruments) { + const char *hlsl = R"( +struct UnreadPayload +{ + double d; + float f1; + float f2; +}; + +[numthreads(1, 1, 1)] +void ASMain(uint gid : SV_GroupID) +{ + UnreadPayload payload; + payload.d = (double)gid; + payload.f1 = (float)gid; + payload.f2 = (float)gid; + DispatchMesh(1, 1, 1, payload); +} + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in payload UnreadPayload payload, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[3], + out indices uint3 triangles[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4((float)tid, 0, 0, 1); + triangles[0] = uint3(0, 1, 2); +} +)"; + + auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); + auto msWithoutPayloadLayout = RunDxilPIXMeshShaderOutputPass(ms); + const std::string disassemblyWithoutPayloadLayout = + Disassemble(msWithoutPayloadLayout); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + disassemblyWithoutPayloadLayout.find("GroupIdX")); + VERIFY_ARE_NOT_EQUAL(std::string::npos, disassemblyWithoutPayloadLayout.find( + "PIX_DebugUAV_Handle")); + VERIFY_ARE_EQUAL(std::string::npos, disassemblyWithoutPayloadLayout.find( + "PIX_AS2MS_Expanded_Type")); + VerifyInstrumentedModuleIsValid(msWithoutPayloadLayout, + "mesh shader output instrumentation"); + + auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); + std::string asPassOutput; + auto asOutput = + RunDxilPIXAddTidToAmplificationShaderPayloadPass(as, &asPassOutput); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(asOutput); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + asPassOutput.find("ExpandedPayloadSize:32")); + VERIFY_ARE_NOT_EQUAL( + std::string::npos, + asPassOutput.find("ExpandedPayloadAppendedFieldsOffset:16")); + + auto msOutput = RunDxilPIXMeshShaderOutputPass( + ms, L",expanded-payload-size=32,expanded-payload-offset=16"); + VerifyDeclaredPayloadSizeMatchesExpandedStruct(msOutput); + const std::string disassembly = Disassemble(msOutput); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + disassembly.find("PIX_DebugUAV_Handle")); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + disassembly.find("PIX_AS2MS_Expanded_Type")); + VerifyInstrumentedModuleIsValid(msOutput, + "mesh shader output instrumentation"); +} + +TEST_F(PixTest, MeshShaderOutput_NearLimitPayloadSkipsExpansion) { + const char *hlsl = R"( +struct NearLimitPayload +{ + uint values[4094]; +}; + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void MSMain( + in payload NearLimitPayload payload, + out vertices PSInput verts[3], + out indices uint3 triangles[1]) +{ + SetMeshOutputCounts(3, 1); + verts[0].position = float4((float)payload.values[0], 0, 0, 1); + verts[1].position = float4(0, 1, 0, 1); + verts[2].position = float4(0, 0, 1, 1); + triangles[0] = uint3(0, 1, 2); +} +)"; + + auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); + auto msOutput = RunDxilPIXMeshShaderOutputPass(ms); + VerifyPayloadWasNotExpandedAndPayloadSizeIs(msOutput, + 4094 * sizeof(uint32_t)); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + Disassemble(msOutput).find("PIX_DebugUAV_Handle")); + + const char *readPayloadHlsl = R"( +struct Payload +{ + uint value; +}; + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in payload Payload payload, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[3], + out indices uint3 triangles[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4((float)payload.value, 0, 0, 1); + triangles[0] = uint3(0, 1, 2); +} +)"; + + auto readPayload = + Compile(m_dllSupport, readPayloadHlsl, L"ms_6_6", {}, L"MSMain"); + std::string passOutput; + auto incompatibleLayoutOutput = RunDxilPIXMeshShaderOutputPass( + readPayload, L",expanded-payload-size=16,expanded-payload-offset=6", + &passOutput); + VERIFY_ARE_NOT_EQUAL(std::string::npos, + passOutput.find("MeshPayloadExpansionFailed")); + VerifyPayloadWasNotExpandedAndPayloadSizeIs(incompatibleLayoutOutput, + sizeof(uint32_t)); + VERIFY_ARE_NOT_EQUAL( + std::string::npos, + Disassemble(incompatibleLayoutOutput).find("getMeshPayload")); + VerifyInstrumentedModuleIsValid(incompatibleLayoutOutput, + "mesh shader output instrumentation"); +} + +TEST_F(PixTest, Validation_MeshShaderOutput_And_AmplificationPayload) { + const char *hlsl = R"( +struct MyPayload +{ + double d; + float f1; + float f2; +}; + +[numthreads(1, 1, 1)] +void ASMain(uint gid : SV_GroupID) +{ + MyPayload payload; + payload.d = (double)gid; + payload.f1 = (float)gid / 4.f; + payload.f2 = (float)gid * 4.f; + DispatchMesh(1, 1, 1, payload); +} + +struct PSInput +{ + float4 position : SV_POSITION; +}; + +[outputtopology("triangle")] +[numthreads(3, 1, 1)] +void MSMain( + in payload MyPayload small, + in uint tid : SV_GroupThreadID, + out vertices PSInput verts[3], + out indices uint3 triangles[1]) +{ + SetMeshOutputCounts(3, 1); + verts[tid].position = float4(small.f1, small.f2, 0, 0); + triangles[0] = uint3(0, 1, 2); +} +)"; + + auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); + auto asOutput = RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + VerifyInstrumentedModuleIsValid(asOutput, + "amplification shader payload expansion"); + + auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); + auto msOutput = RunDxilPIXMeshShaderOutputPass(ms); + VerifyInstrumentedModuleIsValid(msOutput, + "mesh shader output instrumentation"); +} + +TEST_F(PixTest, AddToASPayload_ExpandedAllocaKeepsTheNaturalAlignment) { + const char *hlsl = R"( +struct MyPayload +{ + double d; + float f; +}; + +[numthreads(1, 1, 1)] +void ASMain(uint gid : SV_GroupID) +{ + MyPayload payload; + payload.d = (double)gid; + payload.f = (float)gid; + DispatchMesh(1, 1, 1, payload); +} +)"; + + auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); + auto asOutput = RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + auto lines = Tokenize(Disassemble(asOutput), "\n"); + bool foundAlloca = false; + for (auto const &line : lines) { + if (line.find("alloca") == std::string::npos || + line.find("NewPayload") == std::string::npos) { + continue; + } + foundAlloca = true; + VERIFY_ARE_NOT_EQUAL(std::string::npos, line.find("align 8")); + } + VERIFY_IS_TRUE(foundAlloca); } unsigned FindOrAddVSInSignatureElementForInstanceOrVertexID( hlsl::DxilSignature &InputSignature, hlsl::DXIL::SemanticKind semanticKind); @@ -4544,9 +4908,9 @@ Texture2D tex[] : register(t0); float4 main(float2 uv : TEXCOORD0) : SV_TARGET { uint i = uv.x + uv.y; - Texture2D dynResTex = + Texture2D dynResTex = ResourceDescriptorHeap[i]; - SamplerState dynResSampler = + SamplerState dynResSampler = SamplerDescriptorHeap[i]; return dynResTex.Sample(dynResSampler, uv); })x"; @@ -4556,9 +4920,9 @@ Texture2D tex[] : register(t0); float4 main(float2 uv : TEXCOORD0) : SV_TARGET { uint i = uv.x + uv.y; - Texture2D dynResTex = + Texture2D dynResTex = ResourceDescriptorHeap[NonUniformResourceIndex(i)]; - SamplerState dynResSampler = + SamplerState dynResSampler = SamplerDescriptorHeap[NonUniformResourceIndex(i)]; return dynResTex.Sample(dynResSampler, uv); })x"; diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index b22ec3ebfa..666ec1c5d5 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -7090,6 +7090,8 @@ def add_pass(name, type_name, doc, opts): {"n": "UAVSize", "t": "int", "c": 1}, {"n": "dispatchArgY", "t": "int", "c": 1}, {"n": "dispatchArgZ", "t": "int", "c": 1}, + {"n": "expanded-payload-size", "t": "int", "c": 1}, + {"n": "expanded-payload-offset", "t": "int", "c": 1}, ], ) add_pass(