From 34466cac1d720fb00253fc47e85f4d79a16288a6 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 17:52:07 -0700 Subject: [PATCH 1/7] [DX] Support multi-mip Texture2D SRVs (#1039) Lifts the early-return guard on MipLevels > 1 for SRV textures and threads the mip count through to the D3D12_RESOURCE_DESC and the SRV's MipLevels field. Adds a per-mip upload path using GetCopyableFootprints so each subresource is copied with the correct D3D12_TEXTURE_DATA_PITCH_ALIGNMENT row pitch. The single-mip path is unchanged. RWTexture2D mips are still rejected with not_supported (no per-mip UAV support yet). Adds a new Texture2D.Load.MipMaps.test.yaml covering the 4x4 + 3-mip layout documented in docs/MipMappedTextures.md. Re-enables Texture2D.mips.OperatorIndex / Texture2D.OperatorIndex / Texture2D.GetDimensions on dxc-d3d12 and dxc-warp-d3d12 by qualifying the XFAIL to 'Clang && DirectX' for the unrelated Clang-DX path tracked in llvm/llvm-project#101558. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/DX/Device.cpp | 93 ++++++++++++++----- .../Texture2D.GetDimensions.test.yaml | 2 +- .../Textures/Texture2D.Load.MipMaps.test.yaml | 80 ++++++++++++++++ .../Texture2D.OperatorIndex.test.yaml | 2 +- .../Texture2D.mips.OperatorIndex.test.yaml | 4 +- 5 files changed, 155 insertions(+), 26 deletions(-) create mode 100644 test/Feature/Textures/Texture2D.Load.MipMaps.test.yaml diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 1a5d09124..8ccdc8c35 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -227,16 +227,18 @@ getResourceDescription(const Resource &R) { const D3D12_RESOURCE_DIMENSION Dimension = getDXDimension(R.Kind); const offloadtest::CPUBuffer &B = *R.BufferPtr; - if (B.OutputProps.MipLevels != 1) + if (B.OutputProps.MipLevels > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) return llvm::createStringError(std::errc::not_supported, - "Multiple mip levels are not yet supported " - "for DirectX textures."); + "Multiple mip levels are only supported " + "for read-only SRV 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()) @@ -251,8 +253,9 @@ 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; } @@ -280,7 +283,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: @@ -1517,21 +1521,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()); @@ -1613,8 +1627,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; @@ -1666,14 +1696,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/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 From 5eb4bd0165cf2596069559e31bb3a20440e1e920 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 18:16:25 -0700 Subject: [PATCH 2/7] [DX] Validate MipLevels >= 1 in getResourceDescription Addresses code review feedback on the multi-mip support change: the relaxed guard (changed from `!= 1` to `> 1 && !SRV`) silently accepted MipLevels values of 0 or negative integers. Since OutputProps.MipLevels is signed int, 0 would cast to a UINT(0) in the SRV desc (which D3D12 interprets as the magic "use all mip levels" value) and negative values would wrap to a huge uint16_t in the RESOURCE_DESC. Add an explicit lower-bound check so invalid input is rejected with a clear error instead of silently producing surprising behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/DX/Device.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 8ccdc8c35..422aabe3a 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -227,6 +227,9 @@ 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::invalid_argument, + "MipLevels must be >= 1."); if (B.OutputProps.MipLevels > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) return llvm::createStringError(std::errc::not_supported, "Multiple mip levels are only supported " From 33313c82a2b6bd189651cf40feb49a46b14ccfb8 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 18:27:20 -0700 Subject: [PATCH 3/7] [NFC] clang-format Apply clang-format 19.1.6 to changed regions per pr-code-format CI. --- lib/API/DX/Device.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 422aabe3a..74fe55ad0 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -230,7 +230,8 @@ getResourceDescription(const Resource &R) { if (B.OutputProps.MipLevels < 1) return llvm::createStringError(std::errc::invalid_argument, "MipLevels must be >= 1."); - if (B.OutputProps.MipLevels > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) + if (B.OutputProps.MipLevels > 1 && + getDescriptorKind(R.Kind) != DescriptorKind::SRV) return llvm::createStringError(std::errc::not_supported, "Multiple mip levels are only supported " "for read-only SRV textures."); @@ -256,9 +257,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, - MipLevels, Format, {1, 0}, - Layout, Flags}; + const D3D12_RESOURCE_DESC ResDesc = { + Dimension, 0, Width, Height, 1, MipLevels, Format, {1, 0}, Layout, Flags}; return ResDesc; } From a28733a645d63f3a72d191429989a6eeb99860d7 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Wed, 27 May 2026 13:29:16 -0700 Subject: [PATCH 4/7] [NFC] Add const to ReadbackDX references for clang-tidy Fixes misc-const-correctness warnings-as-errors from clang-tidy in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/DX/Device.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 0acab738a..df1c9bfc7 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2425,7 +2425,7 @@ class DXDevice : public offloadtest::Device { for (const ResourceSet &RS : R.second) { if (RS.Readback == nullptr) continue; - DXBuffer &ReadbackDX = llvm::cast(*RS.Readback); + const DXBuffer &ReadbackDX = llvm::cast(*RS.Readback); addReadbackBeginBarrier(IS, RS.Buffer); const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(ReadbackDX.Buffer.Get(), Footprint); @@ -2438,7 +2438,7 @@ class DXDevice : public offloadtest::Device { for (const ResourceSet &RS : R.second) { if (RS.Readback == nullptr) continue; - DXBuffer &ReadbackDX = llvm::cast(*RS.Readback); + const DXBuffer &ReadbackDX = llvm::cast(*RS.Readback); addReadbackBeginBarrier(IS, RS.Buffer); IS.CB->CmdList->CopyResource(ReadbackDX.Buffer.Get(), RS.Buffer.Get()); addReadbackEndBarrier(IS, RS.Buffer); From 40b50f65391178b2c69e3acf8ba8252258c22532 Mon Sep 17 00:00:00 2001 From: alsepkow Date: Thu, 4 Jun 2026 18:05:12 -0700 Subject: [PATCH 5/7] [Support] Hoist MipLevels validation to Pipeline YAML parser Skips non-texture resources where MipLevels is ignored downstream. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/Support/Pipeline.h | 1 + lib/API/DX/Device.cpp | 9 --------- lib/Support/Pipeline.cpp | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 9cf0e5f77..a63d987e3 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -596,6 +596,7 @@ struct Pipeline { llvm::Error validatePipelineKind(); llvm::Error validateDispatchParameters(); + llvm::Error validateResources(); bool isCompute() const { return Kind == ShaderPipelineKind::Compute; } bool isTraditionalRaster() const { diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 2b3c51f1d..40dbc4ffa 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -241,15 +241,6 @@ 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::invalid_argument, - "MipLevels must be >= 1."); - if (B.OutputProps.MipLevels > 1 && - getDescriptorKind(R.Kind) != DescriptorKind::SRV) - return llvm::createStringError(std::errc::not_supported, - "Multiple mip levels are only supported " - "for read-only SRV textures."); - const DXGI_FORMAT Format = R.isTexture() ? getDXFormat(B.Format, B.Channels) : DXGI_FORMAT_UNKNOWN; const uint32_t Width = diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 3940a550d..20d10f259 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -103,6 +103,9 @@ void MappingTraits::mapping(IO &I, } } + if (auto Err = P.validateResources()) + I.setError(llvm::toString(std::move(Err))); + // Initialize result Buffers for (auto &R : P.Results) { R.ActualPtr = P.getBuffer(R.Actual); @@ -767,3 +770,34 @@ llvm::Error offloadtest::Pipeline::validateDispatchParameters() { return llvm::Error::success(); } + +llvm::Error offloadtest::Pipeline::validateResources() { + for (const auto &D : Sets) { + for (const auto &R : D.Resources) { + // MipLevels only applies to textures. Non-texture resources (buffers, + // samplers, constant buffers, etc.) ignore the field in the backends, so + // skip them here to avoid misleading errors. Also skip resources whose + // buffer pointer didn't resolve - a missing-buffer error was already + // raised above. + if (!R.isTexture() || R.BufferPtr == nullptr) + continue; + + const int Mips = R.BufferPtr->OutputProps.MipLevels; + if (Mips < 1) + return llvm::createStringError( + std::errc::invalid_argument, + "Resource '%s': MipLevels must be >= 1. Auto-generated mip " + "chains (MipLevels = 0) are not supported by the test framework; " + "per-mip CPU data must be provided.", + R.Name.c_str()); + + if (Mips > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) + return llvm::createStringError( + std::errc::not_supported, + "Resource '%s': Multiple mip levels are only supported for " + "read-only SRV textures.", + R.Name.c_str()); + } + } + return llvm::Error::success(); +} From d36e6ec8502329e160412854e9716884b6d15c30 Mon Sep 17 00:00:00 2001 From: alsepkow Date: Thu, 4 Jun 2026 18:12:18 -0700 Subject: [PATCH 6/7] [Support] Inline MipLevels check in Pipeline MappingTraits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/Support/Pipeline.h | 1 - lib/Support/Pipeline.cpp | 51 +++++++++++++------------------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index a63d987e3..9cf0e5f77 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -596,7 +596,6 @@ struct Pipeline { llvm::Error validatePipelineKind(); llvm::Error validateDispatchParameters(); - llvm::Error validateResources(); bool isCompute() const { return Kind == ShaderPipelineKind::Compute; } bool isTraditionalRaster() const { diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 20d10f259..2f159850a 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -100,12 +100,26 @@ 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. Skip resources whose buffer pointer + // didn't resolve - a missing-buffer error was already raised above. + if (R.isTexture() && R.BufferPtr) { + 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."); + } } } - if (auto Err = P.validateResources()) - I.setError(llvm::toString(std::move(Err))); - // Initialize result Buffers for (auto &R : P.Results) { R.ActualPtr = P.getBuffer(R.Actual); @@ -770,34 +784,3 @@ llvm::Error offloadtest::Pipeline::validateDispatchParameters() { return llvm::Error::success(); } - -llvm::Error offloadtest::Pipeline::validateResources() { - for (const auto &D : Sets) { - for (const auto &R : D.Resources) { - // MipLevels only applies to textures. Non-texture resources (buffers, - // samplers, constant buffers, etc.) ignore the field in the backends, so - // skip them here to avoid misleading errors. Also skip resources whose - // buffer pointer didn't resolve - a missing-buffer error was already - // raised above. - if (!R.isTexture() || R.BufferPtr == nullptr) - continue; - - const int Mips = R.BufferPtr->OutputProps.MipLevels; - if (Mips < 1) - return llvm::createStringError( - std::errc::invalid_argument, - "Resource '%s': MipLevels must be >= 1. Auto-generated mip " - "chains (MipLevels = 0) are not supported by the test framework; " - "per-mip CPU data must be provided.", - R.Name.c_str()); - - if (Mips > 1 && getDescriptorKind(R.Kind) != DescriptorKind::SRV) - return llvm::createStringError( - std::errc::not_supported, - "Resource '%s': Multiple mip levels are only supported for " - "read-only SRV textures.", - R.Name.c_str()); - } - } - return llvm::Error::success(); -} From 71afde56d58f3312864a3254e17e2fe1dc6e1ee2 Mon Sep 17 00:00:00 2001 From: alsepkow Date: Thu, 4 Jun 2026 18:22:57 -0700 Subject: [PATCH 7/7] [Support] Polish MipLevels check error format and guard structure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/Support/Pipeline.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 2f159850a..3ec0bb675 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -102,21 +102,25 @@ void MappingTraits::mapping(IO &I, } // MipLevels only applies to textures; non-texture resources ignore - // the field in the backends. Skip resources whose buffer pointer - // didn't resolve - a missing-buffer error was already raised above. - if (R.isTexture() && R.BufferPtr) { - 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."); - } + // 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"); } }