diff --git a/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp b/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp index 227cc890a7..4eb9ba281e 100644 --- a/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp +++ b/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp @@ -12,6 +12,7 @@ #include "dxc/DXIL/DxilModule.h" #include "dxc/DXIL/DxilOperations.h" +#include "dxc/DXIL/DxilTypeSystem.h" #include "dxc/DxilPIXPasses/DxilPIXPasses.h" #include "dxc/HLSL/DxilGenerationPass.h" #include "dxc/HLSL/DxilSpanAllocator.h" @@ -108,52 +109,78 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { const hlsl::DxilSignature &OutputSignature = DM.GetOutputSignature(); - Function *FloatOutputFunction = - HlslOP->GetOpFunc(DXIL::OpCode::StoreOutput, Type::getFloatTy(Ctx)); - Function *IntOutputFunction = - HlslOP->GetOpFunc(DXIL::OpCode::StoreOutput, Type::getInt32Ty(Ctx)); + // dx.op.storeOutput has four legal overloads: f16, f32, i16 and i32. + // A min16float or min16int SV_Target lowers through the f16 or i16 form, + // as does a native half or int16_t target under -enable-16bit-types. + const std::array OverloadTypes{ + Type::getHalfTy(Ctx), Type::getFloatTy(Ctx), Type::getInt16Ty(Ctx), + Type::getInt32Ty(Ctx)}; - bool hasFloatOutputs = false; - bool hasIntOutputs = false; + std::array OutputFunctions{}; + size_t ActiveOverload = OverloadTypes.size(); - visitOutputInstructionCallers( - FloatOutputFunction, OutputSignature, HlslOP, - [&hasFloatOutputs](CallInst *) { hasFloatOutputs = true; }); + for (size_t OverloadIndex = 0; OverloadIndex < OverloadTypes.size(); + ++OverloadIndex) { + OutputFunctions[OverloadIndex] = HlslOP->GetOpFunc( + DXIL::OpCode::StoreOutput, OverloadTypes[OverloadIndex]); - visitOutputInstructionCallers( - IntOutputFunction, OutputSignature, HlslOP, - [&hasIntOutputs](CallInst *) { hasIntOutputs = true; }); + bool HasTargetZeroStores = false; + visitOutputInstructionCallers( + OutputFunctions[OverloadIndex], OutputSignature, HlslOP, + [&HasTargetZeroStores](CallInst *) { HasTargetZeroStores = true; }); + + if (HasTargetZeroStores) { + // visitOutputInstructionCallers filters on SemanticKind::Target with + // GetSemanticStartIndex() == 0, so at most one overload writes + // SV_Target0. + DXASSERT(ActiveOverload == OverloadTypes.size(), + "Only one storeOutput overload can write SV_Target0"); + ActiveOverload = OverloadIndex; + } + } + + // GetOpFunc materialises each overload declaration on demand. Any + // overload with no callers must be erased before the pass returns; the + // validator rejects a module carrying an unused dx.op declaration. + struct EraseUnusedOutputFunctionsOnExit { + hlsl::DxilModule &DM; + std::array &OutputFunctions; + ~EraseUnusedOutputFunctionsOnExit() { + for (Function *OutputFunction : OutputFunctions) { + PIXPassHelpers::EraseIfUnused(DM, OutputFunction); + } + } + } EraseUnusedOutputFunctions{DM, OutputFunctions}; - if (!hasFloatOutputs && !hasIntOutputs) { - PIXPassHelpers::EraseIfUnused(DM, FloatOutputFunction); - PIXPassHelpers::EraseIfUnused(DM, IntOutputFunction); + if (ActiveOverload == OverloadTypes.size()) { return false; } - // Otherwise, we assume the shader outputs only one or the other (because the - // 0th RTV can't have a mixed type) - DXASSERT(!hasFloatOutputs || !hasIntOutputs, - "Only one or the other type of output: float or int"); + // Replacement values must match the store's own overload type. + llvm::Type *const OutputValueType = OverloadTypes[ActiveOverload]; + const bool IsFloatOutput = OutputValueType->isFloatingPointTy(); std::array ReplacementColors; switch (Mode) { case FromLiteralConstant: { - if (hasFloatOutputs) { - ReplacementColors[0] = HlslOP->GetFloatConst(Red); - ReplacementColors[1] = HlslOP->GetFloatConst(Green); - ReplacementColors[2] = HlslOP->GetFloatConst(Blue); - ReplacementColors[3] = HlslOP->GetFloatConst(Alpha); - } - if (hasIntOutputs) { - ReplacementColors[0] = HlslOP->GetI32Const(static_cast(Red)); - ReplacementColors[1] = HlslOP->GetI32Const(static_cast(Green)); - ReplacementColors[2] = HlslOP->GetI32Const(static_cast(Blue)); - ReplacementColors[3] = HlslOP->GetI32Const(static_cast(Alpha)); + const std::array Channels{Red, Green, Blue, Alpha}; + for (size_t ChannelIndex = 0; ChannelIndex < Channels.size(); + ++ChannelIndex) { + ReplacementColors[ChannelIndex] = + IsFloatOutput + ? ConstantFP::get(OutputValueType, Channels[ChannelIndex]) + : ConstantInt::get(OutputValueType, + static_cast(static_cast( + Channels[ChannelIndex])), + /*isSigned*/ true); } } break; case FromConstantBuffer: { + // A float4 constant buffer row is 16 bytes wide. + constexpr unsigned int ConstantColorCBufferSizeInBytes = 4 * sizeof(float); + // Setup a constant buffer with a single float4 in it: SmallVector Elements{ Type::getFloatTy(Ctx), Type::getFloatTy(Ctx), Type::getFloatTy(Ctx), @@ -162,13 +189,31 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { llvm::StructType::create(Elements, "PIX_ConstantColorCB_Type"); std::unique_ptr pCBuf = llvm::make_unique(); pCBuf->SetGlobalName("PIX_ConstantColorCBName"); - pCBuf->SetGlobalSymbol(UndefValue::get(CBStructTy)); + // The global symbol and HLSL type must be pointers to the struct so + // ValidateCBuffer can reach the annotation. + pCBuf->SetGlobalSymbol(UndefValue::get(CBStructTy->getPointerTo())); + pCBuf->SetHLSLType(CBStructTy->getPointerTo()); pCBuf->SetID(static_cast(DM.GetCBuffers().size())); pCBuf->SetSpaceID( (unsigned int)-2); // This is the reserved-for-tools register space pCBuf->SetLowerBound(0); pCBuf->SetRangeSize(1); - pCBuf->SetSize(4); + pCBuf->SetSize(ConstantColorCBufferSizeInBytes); + + auto *StructAnnotation = DM.GetTypeSystem().GetStructAnnotation(CBStructTy); + if (StructAnnotation == nullptr) { + StructAnnotation = DM.GetTypeSystem().AddStructAnnotation(CBStructTy); + StructAnnotation->SetCBufferSize(ConstantColorCBufferSizeInBytes); + static const char *const ComponentNames[] = {"r", "g", "b", "a"}; + for (unsigned int ComponentIndex = 0; ComponentIndex < 4; + ++ComponentIndex) { + auto &FieldAnnotation = + StructAnnotation->GetFieldAnnotation(ComponentIndex); + FieldAnnotation.SetCBufferOffset(ComponentIndex * sizeof(float)); + FieldAnnotation.SetCompType(hlsl::DXIL::ComponentType::F32); + FieldAnnotation.SetFieldName(ComponentNames[ComponentIndex]); + } + } Instruction *entryPointInstruction = &*(PIXPassHelpers::GetEntryFunction(DM)->begin()->begin()); @@ -188,9 +233,12 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { #define PIX_CONSTANT_VALUE "PIX_Constant_Color_Value" // Insert the Buffer load instruction: - Function *CBLoad = HlslOP->GetOpFunc( - OP::OpCode::CBufferLoadLegacy, - hasFloatOutputs ? Type::getFloatTy(Ctx) : Type::getInt32Ty(Ctx)); + // The tools constant buffer is always four 32-bit components; PIX + // uploads that layout. + llvm::Type *const CBufferComponentType = + IsFloatOutput ? Type::getFloatTy(Ctx) : Type::getInt32Ty(Ctx); + Function *CBLoad = + HlslOP->GetOpFunc(OP::OpCode::CBufferLoadLegacy, CBufferComponentType); Constant *OpArg = HlslOP->GetU32Const((unsigned)OP::OpCode::CBufferLoadLegacy); Value *ResourceHandle = callCreateHandle; @@ -207,54 +255,47 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { Builder.CreateExtractValue(loadLegacy, 2, PIX_CONSTANT_VALUE "2"); ReplacementColors[3] = Builder.CreateExtractValue(loadLegacy, 3, PIX_CONSTANT_VALUE "3"); + + // Narrow the loaded components to a 16-bit output overload. + if (OutputValueType != CBufferComponentType) { + static const char *const NarrowedNames[] = { + PIX_CONSTANT_VALUE "Narrowed0", PIX_CONSTANT_VALUE "Narrowed1", + PIX_CONSTANT_VALUE "Narrowed2", PIX_CONSTANT_VALUE "Narrowed3"}; + for (size_t ChannelIndex = 0; ChannelIndex < ReplacementColors.size(); + ++ChannelIndex) { + ReplacementColors[ChannelIndex] = + IsFloatOutput + ? Builder.CreateFPTrunc(ReplacementColors[ChannelIndex], + OutputValueType, + NarrowedNames[ChannelIndex]) + : Builder.CreateTrunc(ReplacementColors[ChannelIndex], + OutputValueType, + NarrowedNames[ChannelIndex]); + } + } } break; default: assert(false); - return 0; + return false; } bool Modified = false; - // The StoreOutput function can store either a float or an integer, depending - // on the intended output render-target resource view. - if (hasFloatOutputs) { - visitOutputInstructionCallers( - FloatOutputFunction, OutputSignature, HlslOP, - [&ReplacementColors, &Modified](CallInst *CallInstruction) { - Modified = true; - // The output column is the channel (red, green, blue or alpha) within - // the output pixel - Value *OutputColumnOperand = CallInstruction->getOperand( - hlsl::DXIL::OperandIndex::kStoreOutputColOpIdx); - ConstantInt *OutputColumnConstant = - cast(OutputColumnOperand); - APInt OutputColumn = OutputColumnConstant->getValue(); - CallInstruction->setOperand( - hlsl::DXIL::OperandIndex::kStoreOutputValOpIdx, - ReplacementColors[*OutputColumn.getRawData()]); - }); - } - - if (hasIntOutputs) { - visitOutputInstructionCallers( - IntOutputFunction, OutputSignature, HlslOP, - [&ReplacementColors, &Modified](CallInst *CallInstruction) { - Modified = true; - // The output column is the channel (red, green, blue or alpha) within - // the output pixel - Value *OutputColumnOperand = CallInstruction->getOperand( - hlsl::DXIL::OperandIndex::kStoreOutputColOpIdx); - ConstantInt *OutputColumnConstant = - cast(OutputColumnOperand); - APInt OutputColumn = OutputColumnConstant->getValue(); - CallInstruction->setOperand( - hlsl::DXIL::OperandIndex::kStoreOutputValOpIdx, - ReplacementColors[*OutputColumn.getRawData()]); - }); - } - - PIXPassHelpers::EraseIfUnused(DM, FloatOutputFunction); - PIXPassHelpers::EraseIfUnused(DM, IntOutputFunction); + visitOutputInstructionCallers( + OutputFunctions[ActiveOverload], OutputSignature, HlslOP, + [&ReplacementColors, &Modified](CallInst *CallInstruction) { + Modified = true; + // The output column is the channel (red, green, blue or alpha) within + // the output pixel + Value *OutputColumnOperand = CallInstruction->getOperand( + hlsl::DXIL::OperandIndex::kStoreOutputColOpIdx); + ConstantInt *OutputColumnConstant = + cast(OutputColumnOperand); + APInt OutputColumn = OutputColumnConstant->getValue(); + CallInstruction->setOperand( + hlsl::DXIL::OperandIndex::kStoreOutputValOpIdx, + ReplacementColors[*OutputColumn.getRawData()]); + }); return Modified; } diff --git a/lib/DxilPIXPasses/DxilReduceMSAAToSingleSample.cpp b/lib/DxilPIXPasses/DxilReduceMSAAToSingleSample.cpp index 01f06605a5..9cb685c647 100644 --- a/lib/DxilPIXPasses/DxilReduceMSAAToSingleSample.cpp +++ b/lib/DxilPIXPasses/DxilReduceMSAAToSingleSample.cpp @@ -13,6 +13,7 @@ #include "dxc/DXIL/DxilInstructions.h" #include "dxc/DXIL/DxilModule.h" +#include "dxc/DXIL/DxilResourceProperties.h" #include "dxc/DxilPIXPasses/DxilPIXPasses.h" #include "dxc/HLSL/DxilGenerationPass.h" @@ -34,50 +35,72 @@ class DxilReduceMSAAToSingleSample : public ModulePass { bool runOnModule(Module &M) override; }; -bool DxilReduceMSAAToSingleSample::runOnModule(Module &M) { - DxilModule &DM = M.GetOrCreateDxilModule(); +static bool IsMultisampledSRVHandle(Value *TextureHandle, DxilModule &DM) { + auto *TextureHandleInst = dyn_cast(TextureHandle); + if (!TextureHandleInst) + return false; + + if (OP::IsDxilOpFuncCallInst(TextureHandleInst, OP::OpCode::CreateHandle)) { + DxilInst_CreateHandle CreateHandle(TextureHandleInst); + if (!isa(CreateHandle.get_rangeId())) + return false; + + if (static_cast( + CreateHandle.get_resourceClass_val()) != DXIL::ResourceClass::SRV) + return false; + + unsigned RangeId = + cast(CreateHandle.get_rangeId())->getLimitedValue(); + auto Resource = DM.GetSRV(RangeId); + return Resource.GetKind() == DXIL::ResourceKind::Texture2DMS || + Resource.GetKind() == DXIL::ResourceKind::Texture2DMSArray; + } - LLVMContext &Ctx = M.getContext(); - OP *HlslOP = DM.GetOP(); + // SM 6.6 handles carry the resource kind in the annotateHandle + // properties operand. + if (OP::IsDxilOpFuncCallInst(TextureHandleInst, OP::OpCode::AnnotateHandle)) { + DxilInst_AnnotateHandle AnnotateHandle(TextureHandleInst); + DxilResourceProperties ResourceProperties = + resource_helper::loadPropsFromAnnotateHandle(AnnotateHandle, + *DM.GetShaderModel()); + return ResourceProperties.getResourceClass() == DXIL::ResourceClass::SRV && + (ResourceProperties.getResourceKind() == + DXIL::ResourceKind::Texture2DMS || + ResourceProperties.getResourceKind() == + DXIL::ResourceKind::Texture2DMSArray); + } - // FP16 type doesn't have its own identity, and is covered by float type... + return false; +} - auto TextureLoadOverloads = std::vector{ - Type::getFloatTy(Ctx), Type::getInt16Ty(Ctx), Type::getInt32Ty(Ctx)}; +bool DxilReduceMSAAToSingleSample::runOnModule(Module &M) { + DxilModule &DM = M.GetOrCreateDxilModule(); + OP *HlslOP = DM.GetOP(); bool Modified = false; - for (const auto &Overload : TextureLoadOverloads) { + // Iterate every materialised TextureLoad overload; the 16-bit form + // lowers Texture2DMS.Load. + for (const auto &TextureLoadOverload : + HlslOP->GetOpFuncList(DXIL::OpCode::TextureLoad)) { + Function *TexLoadFunction = TextureLoadOverload.second; + if (!TexLoadFunction) + continue; - Function *TexLoadFunction = - HlslOP->GetOpFunc(DXIL::OpCode::TextureLoad, Overload); - auto TexLoadFunctionUses = TexLoadFunction->uses(); - - for (auto FI = TexLoadFunctionUses.begin(); - FI != TexLoadFunctionUses.end();) { + for (auto FI = TexLoadFunction->use_begin(); + FI != TexLoadFunction->use_end();) { auto &FunctionUse = *FI++; - auto FunctionUser = FunctionUse.getUser(); - auto instruction = cast(FunctionUser); - DxilInst_TextureLoad LoadInstruction(instruction); - auto TextureHandle = LoadInstruction.get_srv(); - auto TextureHandleInst = cast(TextureHandle); - DxilInst_CreateHandle createHandle(TextureHandleInst); - // Dynamic rangeId is not supported - if (isa(createHandle.get_rangeId())) { - unsigned rangeId = - cast(createHandle.get_rangeId())->getLimitedValue(); - if (static_cast( - createHandle.get_resourceClass_val()) == - DXIL::ResourceClass::SRV) { - auto Resource = DM.GetSRV(rangeId); - if (Resource.GetKind() == DXIL::ResourceKind::Texture2DMS || - Resource.GetKind() == DXIL::ResourceKind::Texture2DMSArray) { - // "2" is the mip-level/sample-index operand index: - // https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/DXIL.rst#textureload - instruction->setOperand(2, HlslOP->GetI32Const(0)); - Modified = true; - } - } + auto *InstructionUser = dyn_cast(FunctionUse.getUser()); + if (!InstructionUser) + continue; + + DxilInst_TextureLoad LoadInstruction(InstructionUser); + if (!LoadInstruction) + continue; + + if (IsMultisampledSRVHandle(LoadInstruction.get_srv(), DM)) { + LoadInstruction.set_mipLevelOrSampleCount(HlslOP->GetI32Const(0)); + Modified = true; } } } diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorhalf.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalf.hlsl new file mode 100644 index 0000000000..e7129392ca --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalf.hlsl @@ -0,0 +1,19 @@ +// RUN: %dxc -enable-16bit-types -Emain -Tps_6_2 %s | %opt -S -hlsl-dxil-constantColor,constant-red=0.5,constant-green=0.25,constant-blue=0.125,constant-alpha=1 | %FileCheck %s + +// A native half SV_Target lowers to dx.op.storeOutput.f16. + +// The override values are 0.5, 0.25, 0.125 and 1.0 as half: +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half 0xH3800) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half 0xH3400) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half 0xH3000) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half 0xH3C00) + +// Unused storeOutput overloads must not remain as external declarations. +// CHECK-NOT: declare void @dx.op.storeOutput.f32 +// CHECK-NOT: declare void @dx.op.storeOutput.i16 +// CHECK-NOT: declare void @dx.op.storeOutput.i32 + +[RootSignature("")] +half4 main() : SV_Target { + return half4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfFromCB.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfFromCB.hlsl new file mode 100644 index 0000000000..5638a098ee --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfFromCB.hlsl @@ -0,0 +1,36 @@ +// RUN: %dxc -enable-16bit-types -Emain -Tps_6_2 %s | %opt -S -hlsl-dxil-constantColor,mod-mode=1 | %FileCheck %s + +// From-constant-buffer mode against a native half SV_Target0. The tools +// constant buffer is four 32-bit components; loaded values narrow to half. + +// CB return type is f32: +// CHECK: %dx.types.CBufRet.f32 = type { float, float, float, float } + +// Create handle: +// CHECK: %PIX_Constant_Color_CB_Handle = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 0, i1 false) + +// Load the row: +// CHECK: %PIX_Constant_Color_Value = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %PIX_Constant_Color_CB_Handle, i32 0) + +// Extract components: +// CHECK: %PIX_Constant_Color_Value0 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 0 +// CHECK: %PIX_Constant_Color_Value1 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 1 +// CHECK: %PIX_Constant_Color_Value2 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 2 +// CHECK: %PIX_Constant_Color_Value3 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 3 + +// Narrow to half: +// CHECK: %PIX_Constant_Color_ValueNarrowed0 = fptrunc float %PIX_Constant_Color_Value0 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed1 = fptrunc float %PIX_Constant_Color_Value1 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed2 = fptrunc float %PIX_Constant_Color_Value2 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed3 = fptrunc float %PIX_Constant_Color_Value3 to half + +// Store SV_Target0: +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half %PIX_Constant_Color_ValueNarrowed0) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half %PIX_Constant_Color_ValueNarrowed1) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half %PIX_Constant_Color_ValueNarrowed2) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half %PIX_Constant_Color_ValueNarrowed3) + +[RootSignature("")] +half4 main() : SV_Target { + return half4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRT.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRT.hlsl new file mode 100644 index 0000000000..5d4462c463 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRT.hlsl @@ -0,0 +1,29 @@ +// RUN: %dxc -enable-16bit-types -Emain -Tps_6_2 %s | %opt -S -hlsl-dxil-constantColor | %FileCheck %s + +// MRT: RTV0 is half4, RTV1 is float4. The override applies to SV_Target0 +// only. Default constant colour is 1.0 (0xH3C00 as half). + +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half 0xH3C00) + +// RTV1 stays unchanged: +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 0, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 1, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 2, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 3, float 0.000000e+00) + +struct RTOut +{ + half4 h : SV_Target; + float4 c : SV_Target1; +}; + +[RootSignature("")] +RTOut main() { + RTOut rtOut; + rtOut.h = half4(0, 0, 0, 0); + rtOut.c = float4(0.f, 0.f, 0.f, 0.f); + return rtOut; +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRTOnRTV1.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRTOnRTV1.hlsl new file mode 100644 index 0000000000..f8e625001e --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorhalfMRTOnRTV1.hlsl @@ -0,0 +1,33 @@ +// RUN: %dxc -enable-16bit-types -Emain -Tps_6_2 %s | %opt -S -hlsl-dxil-constantColor | %FileCheck %s + +// MRT: RTV0 is float4, RTV1 is half4. The override applies to SV_Target0 +// only; RTV1 stays unchanged. + +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 0, float 1.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 1, float 1.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 2, float 1.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 0, i32 0, i8 3, float 1.000000e+00) + +// RTV1 stays 0xH0000 (half 0.0): +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 1, i32 0, i8 0, half 0xH0000) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 1, i32 0, i8 1, half 0xH0000) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 1, i32 0, i8 2, half 0xH0000) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 1, i32 0, i8 3, half 0xH0000) + +// Unused integer overloads must not remain as external declarations. +// CHECK-NOT: declare void @dx.op.storeOutput.i16 +// CHECK-NOT: declare void @dx.op.storeOutput.i32 + +struct RTOut +{ + float4 c : SV_Target; + half4 h : SV_Target1; +}; + +[RootSignature("")] +RTOut main() { + RTOut rtOut; + rtOut.c = float4(0.f, 0.f, 0.f, 0.f); + rtOut.h = half4(0, 0, 0, 0); + return rtOut; +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorint16.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorint16.hlsl new file mode 100644 index 0000000000..835fc3f013 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorint16.hlsl @@ -0,0 +1,18 @@ +// RUN: %dxc -enable-16bit-types -Emain -Tps_6_2 %s | %opt -S -hlsl-dxil-constantColor,constant-red=8,constant-green=7,constant-blue=6,constant-alpha=5 | %FileCheck %s + +// A native uint16_t SV_Target lowers to dx.op.storeOutput.i16. + +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 0, i16 8) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 1, i16 7) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 2, i16 6) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 3, i16 5) + +// Unused storeOutput overloads must not remain as external declarations. +// CHECK-NOT: declare void @dx.op.storeOutput.f16 +// CHECK-NOT: declare void @dx.op.storeOutput.f32 +// CHECK-NOT: declare void @dx.op.storeOutput.i32 + +[RootSignature("")] +uint16_t4 main() : SV_Target { + return uint16_t4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecision.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecision.hlsl new file mode 100644 index 0000000000..08d0fcae82 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecision.hlsl @@ -0,0 +1,18 @@ +// RUN: %dxc -Emain -Tps_6_0 %s | %opt -S -hlsl-dxil-constantColor,constant-red=0.5,constant-green=0.25,constant-blue=0.125,constant-alpha=1 | %FileCheck %s + +// A min16float SV_Target lowers to dx.op.storeOutput.f16 at ps_6_0 without +// -enable-16bit-types. + +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half 0xH3800) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half 0xH3400) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half 0xH3000) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half 0xH3C00) + +// CHECK-NOT: declare void @dx.op.storeOutput.f32 +// CHECK-NOT: declare void @dx.op.storeOutput.i16 +// CHECK-NOT: declare void @dx.op.storeOutput.i32 + +[RootSignature("")] +min16float4 main() : SV_Target { + return min16float4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionFromCB.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionFromCB.hlsl new file mode 100644 index 0000000000..1a15bdcc59 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionFromCB.hlsl @@ -0,0 +1,37 @@ +// RUN: %dxc -Emain -Tps_6_0 %s | %opt -S -hlsl-dxil-constantColor,mod-mode=1 | %FileCheck %s + +// From-constant-buffer mode against a min16float SV_Target0 at ps_6_0 +// without -enable-16bit-types. The tools constant buffer is four 32-bit +// components; loaded values narrow to half. + +// CB return type is f32: +// CHECK: %dx.types.CBufRet.f32 = type { float, float, float, float } + +// Create handle: +// CHECK: %PIX_Constant_Color_CB_Handle = call %dx.types.Handle @dx.op.createHandle(i32 57, i8 2, i32 0, i32 0, i1 false) + +// Load the row: +// CHECK: %PIX_Constant_Color_Value = call %dx.types.CBufRet.f32 @dx.op.cbufferLoadLegacy.f32(i32 59, %dx.types.Handle %PIX_Constant_Color_CB_Handle, i32 0) + +// Extract components: +// CHECK: %PIX_Constant_Color_Value0 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 0 +// CHECK: %PIX_Constant_Color_Value1 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 1 +// CHECK: %PIX_Constant_Color_Value2 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 2 +// CHECK: %PIX_Constant_Color_Value3 = extractvalue %dx.types.CBufRet.f32 %PIX_Constant_Color_Value, 3 + +// Narrow to half: +// CHECK: %PIX_Constant_Color_ValueNarrowed0 = fptrunc float %PIX_Constant_Color_Value0 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed1 = fptrunc float %PIX_Constant_Color_Value1 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed2 = fptrunc float %PIX_Constant_Color_Value2 to half +// CHECK: %PIX_Constant_Color_ValueNarrowed3 = fptrunc float %PIX_Constant_Color_Value3 to half + +// Store SV_Target0: +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half %PIX_Constant_Color_ValueNarrowed0) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half %PIX_Constant_Color_ValueNarrowed1) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half %PIX_Constant_Color_ValueNarrowed2) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half %PIX_Constant_Color_ValueNarrowed3) + +[RootSignature("")] +min16float4 main() : SV_Target { + return min16float4(0, 0, 0, 0); +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionMRT.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionMRT.hlsl new file mode 100644 index 0000000000..1addc05a48 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionMRT.hlsl @@ -0,0 +1,29 @@ +// RUN: %dxc -Emain -Tps_6_0 %s | %opt -S -hlsl-dxil-constantColor | %FileCheck %s + +// MRT at ps_6_0 without -enable-16bit-types: RTV0 is min16float4, RTV1 is +// float4. The override applies to SV_Target0 only. + +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 0, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 1, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 2, half 0xH3C00) +// CHECK: call void @dx.op.storeOutput.f16(i32 5, i32 0, i32 0, i8 3, half 0xH3C00) + +// RTV1 stays unchanged: +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 0, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 1, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 2, float 0.000000e+00) +// CHECK: call void @dx.op.storeOutput.f32(i32 5, i32 1, i32 0, i8 3, float 0.000000e+00) + +struct RTOut +{ + min16float4 h : SV_Target; + float4 c : SV_Target1; +}; + +[RootSignature("")] +RTOut main() { + RTOut rtOut; + rtOut.h = min16float4(0, 0, 0, 0); + rtOut.c = float4(0.f, 0.f, 0.f, 0.f); + return rtOut; +} diff --git a/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionint.hlsl b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionint.hlsl new file mode 100644 index 0000000000..f18c542636 --- /dev/null +++ b/tools/clang/test/HLSLFileCheck/pix/constantcolorminprecisionint.hlsl @@ -0,0 +1,18 @@ +// RUN: %dxc -Emain -Tps_6_0 %s | %opt -S -hlsl-dxil-constantColor,constant-red=8,constant-green=7,constant-blue=6,constant-alpha=5 | %FileCheck %s + +// A min16int SV_Target lowers to dx.op.storeOutput.i16 at ps_6_0 without +// -enable-16bit-types. + +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 0, i16 8) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 1, i16 7) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 2, i16 6) +// CHECK: call void @dx.op.storeOutput.i16(i32 5, i32 0, i32 0, i8 3, i16 5) + +// CHECK-NOT: declare void @dx.op.storeOutput.f16 +// CHECK-NOT: declare void @dx.op.storeOutput.f32 +// CHECK-NOT: declare void @dx.op.storeOutput.i32 + +[RootSignature("")] +min16int4 main() : SV_Target { + return min16int4(1, 2, 3, 4); +} diff --git a/tools/clang/unittests/HLSL/PixTest.cpp b/tools/clang/unittests/HLSL/PixTest.cpp index 0fa134c98f..f71742fe58 100644 --- a/tools/clang/unittests/HLSL/PixTest.cpp +++ b/tools/clang/unittests/HLSL/PixTest.cpp @@ -160,7 +160,10 @@ class PixTest : public ::testing::Test { TEST_METHOD(ToolsUav_RootSignatureSerializationFailurePreservesSignature) TEST_METHOD(ConstantColor_UnusedIntOverloadIsErased) TEST_METHOD(ConstantColor_NoTargetOverloadsAreErased) + TEST_METHOD(ConstantColor_FromConstantBufferIsWellFormed) TEST_METHOD(RemoveDiscards_UnusedDiscardOverloadIsErased) + TEST_METHOD(ReduceMSAAToSingleSample_SM66) + TEST_METHOD(ReduceMSAAToSingleSample_HalfLoad) TEST_METHOD(OperationCacheCleanup_RemovesErasedFunctions) TEST_METHOD(DynamicResourceCleanup_VisitorStopsEarly) @@ -3732,6 +3735,121 @@ float4 main() : SV_Target "discard removal with no discard"); } +TEST_F(PixTest, ConstantColor_FromConstantBufferIsWellFormed) { + const char *source = R"x( +float4 main(float4 position : SV_Position) : SV_Target +{ + return position; +})x"; + + auto compiled = Compile(m_dllSupport, source, L"ps_6_0", {L"-Od"}); + auto output = RunSinglePass(compiled, L"-hlsl-dxil-constantColor,mod-mode=1"); + + // The CBuffer symbol must be a pointer to the struct so ValidateCBuffer + // can reach the annotation. + CComPtr pAssembler; + VERIFY_SUCCEEDED( + m_dllSupport.CreateInstance(CLSID_DxcAssembler, &pAssembler)); + CComPtr pAssembleResult; + VERIFY_SUCCEEDED( + pAssembler->AssembleToContainer(output.Module, &pAssembleResult)); + HRESULT assembleStatus; + VERIFY_SUCCEEDED(pAssembleResult->GetStatus(&assembleStatus)); + VERIFY_SUCCEEDED(assembleStatus); + + CComPtr pNewContainer; + VERIFY_SUCCEEDED(pAssembleResult->GetResult(&pNewContainer)); + + // The CBuffer resource record field 6 is size in bytes; a float4 row + // is 16 bytes. + auto lines = Tokenize(Disassemble(pNewContainer).c_str(), "\n"); + bool foundConstantColorCBuffer = false; + for (auto const &line : lines) { + if (line.find("!\"PIX_ConstantColorCBName\"") == std::string::npos) + continue; + auto fields = Tokenize(line.c_str(), ","); + VERIFY_IS_TRUE(fields.size() > 6); + // Field 1 is the global symbol; it must be a pointer to the CB struct. + VERIFY_ARE_NOT_EQUAL(std::string::npos, fields[1].find('*')); + VERIFY_ARE_EQUAL(16, atoi(fields[6].c_str() + fields[6].find("i32 ") + 4)); + foundConstantColorCBuffer = true; + } + VERIFY_IS_TRUE(foundConstantColorCBuffer); + + // The struct annotation names the float4 row in the reflection header. + bool foundStructAnnotation = false; + for (auto const &line : lines) { + if (line.find("struct PIX_ConstantColorCB_Type") != std::string::npos) + foundStructAnnotation = true; + } + VERIFY_IS_TRUE(foundStructAnnotation); + + VerifyInstrumentedModuleIsValid(pNewContainer, + "constant-colour from constant buffer"); +} + +static void +VerifyMSAALoadSampleWasReduced(std::vector const &lines, + const char *textureLoadOverload, + const char *originalSampleIndex) { + bool foundTextureLoad = false; + for (auto const &line : lines) { + if (line.find(" call ") == std::string::npos || + line.find(textureLoadOverload) == std::string::npos) { + continue; + } + + foundTextureLoad = true; + VERIFY_ARE_EQUAL(std::string::npos, line.find(originalSampleIndex)); + VERIFY_ARE_NOT_EQUAL(std::string::npos, line.find(", i32 0,")); + } + VERIFY_IS_TRUE(foundTextureLoad); +} + +TEST_F(PixTest, ReduceMSAAToSingleSample_SM66) { + if (m_ver.SkipDxilVersion(1, 6)) + return; + + // SM 6.6 lowers the resource handle through annotateHandle. + const char *source = R"x( +Texture2DMS tex : register(t0); +float4 main(float4 position : SV_Position) : SV_Target +{ + return tex.Load(int2(position.xy), 3); +})x"; + + auto compiled = Compile(m_dllSupport, source, L"ps_6_6", {L"-Od"}); + auto output = RunSinglePass(compiled, L"-hlsl-dxil-reduce-msaa-to-single"); + auto lines = Tokenize(Disassemble(output.Module).c_str(), "\n"); + + VerifyMSAALoadSampleWasReduced(lines, "dx.op.textureLoad.f32", ", i32 3,"); + VerifyInstrumentedModuleIsValid(output.Module, + "MSAA reduction on SM 6.6 handle"); +} + +TEST_F(PixTest, ReduceMSAAToSingleSample_HalfLoad) { + if (m_ver.SkipDxilVersion(1, 2)) + return; + + // Texture2DMS.Load lowers to dx.op.textureLoad.f16. + const char *source = R"x( +Texture2DMS tex : register(t0); +float4 main(float4 position : SV_Position) : SV_Target +{ + half4 color = tex.Load(int2(position.xy), 2); + return float4(color); +})x"; + + auto compiled = Compile(m_dllSupport, source, L"ps_6_2", + {L"-Od", L"-enable-16bit-types"}); + auto output = RunSinglePass(compiled, L"-hlsl-dxil-reduce-msaa-to-single"); + auto lines = Tokenize(Disassemble(output.Module).c_str(), "\n"); + + VerifyMSAALoadSampleWasReduced(lines, "dx.op.textureLoad.f16", ", i32 2,"); + VerifyInstrumentedModuleIsValid(output.Module, + "MSAA reduction on 16-bit texture load"); +} + TEST_F(PixTest, OperationCacheCleanup_RemovesErasedFunctions) { const char *source = R"x( float4 main() : SV_Target