From 56e11698c312d91875458a1101c12d9441977eda Mon Sep 17 00:00:00 2001 From: Damyan Pepper Date: Tue, 25 Aug 2026 08:09:53 -0700 Subject: [PATCH] [PIX] Fix mesh shader instrumentation PIX instruments an amplification shader by expanding its payload to carry a group identifier. The mesh shader must agree on the resulting layout. When the two cannot be reconciled, the pass applies a locally derived layout that is known to disagree. The instrumented pair then disagrees about where each payload field lives. A mesh shader whose payload is never read is skipped, so its output cannot be instrumented at all. A signed 16-bit output is zero-extended instead of sign-extended. Unused EmitIndices declarations stay in the module, and the validator refuses it. The reservation assertion checks a value that is already zero, so it does not catch a caller that asks for enough space to overwrite the offset counter. The amplification shader reports the size and offset of the expanded payload through the expanded-payload-size and expanded-payload-offset options. The mesh shader reconstructs its payload type from that report. When reconstruction fails, the pass reports the failure and leaves the mesh payload unchanged. A mismatched pair produces records PIX cannot interpret, so there is no fallback layout. The pass does not expand a payload that is already near the size limit. If the PIX coordinator does not forward the layout of the amplification shader, the mesh shader leaves its payload unchanged. That is an integration item for PIX. Assisted-by: Copilot Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 886bcfe8-1fdc-4b99-ac8f-2947a66178f4 --- ...lPIXAddTidToAmplificationShaderPayload.cpp | 24 +- ...DxilPIXMeshShaderOutputInstrumentation.cpp | 266 ++++++++++-- .../pix/AddThreadIdWhenMSPayloadIsUnused.hlsl | 12 +- .../MeshOutputSignedInt16IsSignExtended.hlsl | 35 ++ ...xpansionAgreesWithAmplificationShader.hlsl | 55 +++ ...outEmitIndicesLeavesNoDeadDeclaration.hlsl | 25 ++ ...sizedPayloadTypeRejectsUnusableLayout.hlsl | 40 ++ .../pix/SynthesizedPayloadTypeShapes.hlsl | 45 ++ tools/clang/unittests/HLSL/PixTest.cpp | 392 +++++++++++++++++- utils/hct/hctdb.py | 2 + 10 files changed, 839 insertions(+), 57 deletions(-) create mode 100644 tools/clang/test/HLSLFileCheck/pix/MeshOutputSignedInt16IsSignExtended.hlsl create mode 100644 tools/clang/test/HLSLFileCheck/pix/MeshPayloadExpansionAgreesWithAmplificationShader.hlsl create mode 100644 tools/clang/test/HLSLFileCheck/pix/MeshShaderWithoutEmitIndicesLeavesNoDeadDeclaration.hlsl create mode 100644 tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeRejectsUnusableLayout.hlsl create mode 100644 tools/clang/test/HLSLFileCheck/pix/SynthesizedPayloadTypeShapes.hlsl 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(