diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index edfaccb7b..40dbc4ffa 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -241,16 +241,13 @@ getResourceDescription(const Resource &R) { const D3D12_RESOURCE_DIMENSION Dimension = getDXDimension(R.Kind); const offloadtest::CPUBuffer &B = *R.BufferPtr; - if (B.OutputProps.MipLevels != 1) - return llvm::createStringError(std::errc::not_supported, - "Multiple mip levels are not yet supported " - "for DirectX textures."); - const DXGI_FORMAT Format = R.isTexture() ? getDXFormat(B.Format, B.Channels) : DXGI_FORMAT_UNKNOWN; const uint32_t Width = R.isTexture() ? B.OutputProps.Width : getUAVBufferSize(R); const uint32_t Height = R.isTexture() ? B.OutputProps.Height : 1; + const uint16_t MipLevels = + R.isTexture() ? static_cast(B.OutputProps.MipLevels) : 1; D3D12_TEXTURE_LAYOUT Layout; if (R.isTexture()) @@ -265,8 +262,8 @@ getResourceDescription(const Resource &R) { const D3D12_RESOURCE_FLAGS Flags = R.isReadWrite() ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE; - const D3D12_RESOURCE_DESC ResDesc = {Dimension, 0, Width, Height, 1, 1, - Format, {1, 0}, Layout, Flags}; + const D3D12_RESOURCE_DESC ResDesc = { + Dimension, 0, Width, Height, 1, MipLevels, Format, {1, 0}, Layout, Flags}; return ResDesc; } @@ -294,7 +291,8 @@ static D3D12_SHADER_RESOURCE_VIEW_DESC getSRVDescription(const Resource &R) { break; case ResourceKind::Texture2D: Desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; - Desc.Texture2D = D3D12_TEX2D_SRV{0, 1, 0, 0}; + Desc.Texture2D = D3D12_TEX2D_SRV{ + 0, static_cast(R.BufferPtr->OutputProps.MipLevels), 0, 0.0f}; break; case ResourceKind::RWStructuredBuffer: case ResourceKind::RWBuffer: @@ -1559,21 +1557,31 @@ class DXDevice : public offloadtest::Device { return std::make_unique(Desc); } - void addResourceUploadCommands(Resource &R, InvocationState &IS, - ComPtr Destination, - ComPtr Source) { + void addResourceUploadCommands( + Resource &R, InvocationState &IS, ComPtr Destination, + ComPtr Source, + llvm::ArrayRef MipFootprints = {}) { addUploadBeginBarrier(IS, Destination); if (R.isTexture()) { const offloadtest::CPUBuffer &B = *R.BufferPtr; - const D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint{ - 0, CD3DX12_SUBRESOURCE_FOOTPRINT( - getDXFormat(B.Format, B.Channels), B.OutputProps.Width, - B.OutputProps.Height, 1, - B.OutputProps.Width * B.getElementSize())}; - const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(Destination.Get(), 0); - const CD3DX12_TEXTURE_COPY_LOCATION SrcLoc(Source.Get(), Footprint); - - IS.CB->CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + if (!MipFootprints.empty()) { + for (uint32_t Mip = 0; Mip < MipFootprints.size(); ++Mip) { + const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(Destination.Get(), Mip); + const CD3DX12_TEXTURE_COPY_LOCATION SrcLoc(Source.Get(), + MipFootprints[Mip]); + IS.CB->CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + } + } else { + const D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint{ + 0, CD3DX12_SUBRESOURCE_FOOTPRINT( + getDXFormat(B.Format, B.Channels), B.OutputProps.Width, + B.OutputProps.Height, 1, + B.OutputProps.Width * B.getElementSize())}; + const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(Destination.Get(), 0); + const CD3DX12_TEXTURE_COPY_LOCATION SrcLoc(Source.Get(), Footprint); + + IS.CB->CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + } } else IS.CB->CmdList->CopyBufferRegion(Destination.Get(), 0, Source.Get(), 0, R.size()); @@ -1655,8 +1663,24 @@ class DXDevice : public offloadtest::Device { const D3D12_RESOURCE_DESC ResDesc = *ResDescOrErr; const D3D12_HEAP_PROPERTIES UploadHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); - const D3D12_RESOURCE_DESC UploadResDesc = - CD3DX12_RESOURCE_DESC::Buffer(R.size()); + + const bool IsMipMappedTexture = + R.isTexture() && R.BufferPtr->OutputProps.MipLevels > 1; + llvm::SmallVector MipFootprints; + llvm::SmallVector MipNumRows; + llvm::SmallVector MipRowSizes; + UINT64 MipTotalBytes = 0; + if (IsMipMappedTexture) { + const uint32_t MipLevels = R.BufferPtr->OutputProps.MipLevels; + MipFootprints.resize(MipLevels); + MipNumRows.resize(MipLevels); + MipRowSizes.resize(MipLevels); + Device->GetCopyableFootprints(&ResDesc, 0, MipLevels, 0, + MipFootprints.data(), MipNumRows.data(), + MipRowSizes.data(), &MipTotalBytes); + } + const D3D12_RESOURCE_DESC UploadResDesc = CD3DX12_RESOURCE_DESC::Buffer( + IsMipMappedTexture ? MipTotalBytes : R.size()); uint32_t RegOffset = 0; @@ -1708,14 +1732,33 @@ class DXDevice : public offloadtest::Device { // Upload data initialization void *ResDataPtr = nullptr; if (SUCCEEDED(UploadBuffer->Map(0, NULL, &ResDataPtr))) { - memcpy(ResDataPtr, ResData.get(), R.size()); + if (IsMipMappedTexture) { + // Source CPU data is tightly packed for all mips (per docs). + // Destination upload buffer has D3D12-aligned per-mip layout from + // GetCopyableFootprints; copy each mip row-by-row applying the + // possibly-padded row pitch. + const uint8_t *Src = reinterpret_cast(ResData.get()); + uint8_t *Dst = static_cast(ResDataPtr); + for (uint32_t Mip = 0; Mip < MipFootprints.size(); ++Mip) { + const auto &FP = MipFootprints[Mip]; + const size_t TightRowBytes = static_cast(MipRowSizes[Mip]); + const size_t PaddedRowPitch = + static_cast(FP.Footprint.RowPitch); + for (UINT Row = 0; Row < MipNumRows[Mip]; ++Row) + memcpy(Dst + FP.Offset + Row * PaddedRowPitch, + Src + Row * TightRowBytes, TightRowBytes); + Src += TightRowBytes * MipNumRows[Mip]; + } + } else { + memcpy(ResDataPtr, ResData.get(), R.size()); + } UploadBuffer->Unmap(0, nullptr); } else { return llvm::createStringError(std::errc::io_error, "Failed to map SRV upload buffer."); } - addResourceUploadCommands(R, IS, Buffer, UploadBuffer); + addResourceUploadCommands(R, IS, Buffer, UploadBuffer, MipFootprints); Bundle.emplace_back(UploadBuffer, Buffer, nullptr, Heap); RegOffset++; diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 3940a550d..3ec0bb675 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -100,6 +100,27 @@ void MappingTraits::mapping(IO &I, if (!R.BufferPtr) I.setError(Twine("Referenced buffer ") + R.Name + " not found!"); } + + // MipLevels only applies to textures; non-texture resources ignore + // the field in the backends. + if (!R.isTexture()) + continue; + + // Prevent a null deref if buffer resolution above failed; the missing + // buffer was already reported. + if (!R.BufferPtr) + continue; + + const int Mips = R.BufferPtr->OutputProps.MipLevels; + if (Mips < 1) + I.setError(Twine("Resource '") + R.Name + + "': MipLevels must be >= 1. Auto-generated mip chains " + "(MipLevels = 0) are not supported by the test " + "framework; per-mip CPU data must be provided"); + else if (Mips > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) + I.setError(Twine("Resource '") + R.Name + + "': multiple mip levels are only supported for " + "read-only SRV textures"); } } diff --git a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml index 484fcc93e..d74667a1e 100644 --- a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml +++ b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml @@ -115,7 +115,7 @@ Results: #--- end # Unimplemented: Clang + DX: https://github.com/llvm/llvm-project/issues/101558 -# XFAIL: DirectX || Metal +# XFAIL: (Clang && DirectX) || Metal # Bug https://github.com/llvm/llvm-project/issues/197837 # XFAIL: Clang && Vulkan diff --git a/test/Feature/Textures/Texture2D.Load.MipMaps.test.yaml b/test/Feature/Textures/Texture2D.Load.MipMaps.test.yaml new file mode 100644 index 000000000..750168d8f --- /dev/null +++ b/test/Feature/Textures/Texture2D.Load.MipMaps.test.yaml @@ -0,0 +1,80 @@ +#--- source.hlsl +[[vk::binding(0, 0)]] Texture2D Tex : register(t0); +[[vk::binding(1, 0)]] RWBuffer Out : register(u0); + +[numthreads(1, 1, 1)] +void main() { + // Texture2D::Load(int3(x, y, mip)) + // Mip 0 (4x4) is Red + Out[0] = Tex.Load(int3(0, 0, 0)); + Out[1] = Tex.Load(int3(3, 3, 0)); + // Mip 1 (2x2) is Green + Out[2] = Tex.Load(int3(0, 0, 1)); + Out[3] = Tex.Load(int3(1, 1, 1)); + // Mip 2 (1x1) is Blue + Out[4] = Tex.Load(int3(0, 0, 2)); +} + +//--- pipeline.yaml +--- +Shaders: + - Stage: Compute + Entry: main + +Buffers: + - Name: Tex + Format: Float32 + Channels: 4 + OutputProps: { Width: 4, Height: 4, Depth: 1, MipLevels: 3 } + Data: [ + # --- Mip 0 (4x4 = 16 RGBA texels) - Red --- + 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, + # --- Mip 1 (2x2 = 4 RGBA texels) - Green --- + 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, + # --- Mip 2 (1x1 = 1 RGBA texel) - Blue --- + 0.0, 0.0, 1.0, 1.0 + ] + + - Name: Out + Format: Float32 + Channels: 4 + FillSize: 80 # 5 * sizeof(float4) + + - Name: Expected + Format: Float32 + Channels: 4 + Data: [ 1.0, 0.0, 0.0, 1.0, # [0] Red (mip 0 corner) + 1.0, 0.0, 0.0, 1.0, # [1] Red (mip 0 corner) + 0.0, 1.0, 0.0, 1.0, # [2] Green (mip 1 corner) + 0.0, 1.0, 0.0, 1.0, # [3] Green (mip 1 corner) + 0.0, 0.0, 1.0, 1.0 ] # [4] Blue (mip 2) + +DescriptorSets: + - Resources: + - Name: Tex + Kind: Texture2D + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 0 } + - Name: Out + Kind: RWBuffer + DirectXBinding: { Register: 0, Space: 0 } + VulkanBinding: { Binding: 1 } + +Results: + - Result: LoadMipsTest + Rule: BufferExact + Actual: Out + Expected: Expected +... +#--- end + +# XFAIL: Clang && DirectX +# XFAIL: Metal + +# RUN: split-file %s %t +# RUN: %dxc_target -T cs_6_0 -Fo %t.o %t/source.hlsl +# RUN: %offloader %t/pipeline.yaml %t.o diff --git a/test/Feature/Textures/Texture2D.OperatorIndex.test.yaml b/test/Feature/Textures/Texture2D.OperatorIndex.test.yaml index e57df3748..00185fd28 100644 --- a/test/Feature/Textures/Texture2D.OperatorIndex.test.yaml +++ b/test/Feature/Textures/Texture2D.OperatorIndex.test.yaml @@ -61,7 +61,7 @@ Results: #--- end # Unimplemented: Clang + DX: https://github.com/llvm/llvm-project/issues/101558 -# XFAIL: DirectX +# XFAIL: Clang && DirectX # RUN: split-file %s %t # RUN: %dxc_target -T cs_6_0 -Fo %t.o %t/source.hlsl diff --git a/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml b/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml index 49f8ee4ba..6fd165d14 100644 --- a/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml +++ b/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml @@ -61,8 +61,8 @@ Results: #--- end # Unimplemented: https://github.com/llvm/offload-test-suite/issues/1039 -# XFAIL: DirectX - +# DXC+DX (d3d12/warp) now supported; Clang+DX still has a separate issue. +# XFAIL: Clang && DirectX # XFAIL: Metal # RUN: split-file %s %t