From 35cadc02e2609ceae30a2b3315e01a79a936210d Mon Sep 17 00:00:00 2001 From: Manon Oomen Date: Wed, 24 Jun 2026 16:38:14 +0200 Subject: [PATCH 1/2] Add mipmap support to createTextureWithData. --- include/API/Device.h | 9 +++ include/API/Texture.h | 19 ++++++ lib/API/DX/Device.cpp | 60 ++++++++++++++++--- lib/API/Device.cpp | 49 ++++++++------- lib/API/MTL/MTLDevice.cpp | 32 ++++++---- lib/API/Texture.cpp | 23 +++++++ lib/API/VK/Device.cpp | 31 +++++++++- lib/Support/OffloadMigration.cpp | 4 +- .../Texture2D.GetDimensions.test.yaml | 4 +- .../Texture2D.mips.OperatorIndex.test.yaml | 2 - 10 files changed, 183 insertions(+), 50 deletions(-) diff --git a/include/API/Device.h b/include/API/Device.h index 089416ed3..2d111a7c8 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -354,6 +354,12 @@ class Device { virtual uint32_t getTextureUploadRowStrideInBytes(const TextureCreateDesc &Desc) const = 0; + // The layout an upload buffer must have to feed createTextureWithData / + // copyBufferToTexture for the given texture description. Encodes per-mip + // offsets, row pitch, and total size in the backend's required alignment. + virtual TextureUploadLayout + getTextureUploadLayout(const TextureCreateDesc &Desc) const = 0; + virtual llvm::Expected> createRenderPass(const RenderPassDesc &Desc) = 0; @@ -421,6 +427,9 @@ createBufferWithData(Device &Dev, std::string Name, size_t SizeInBytes, ComputeEncoder *Encoder, std::unique_ptr *OutUploadBuffer); +// Create a texture and upload `Data` (tightly-packed across mip levels) into +// it via a staging buffer recorded on `Encoder`. The staging buffer is handed +// back through `OutUploadBuffer` and must outlive command-buffer submission. llvm::Expected> createTextureWithData(Device &Dev, std::string Name, const TextureCreateDesc &Desc, const void *Data, diff --git a/include/API/Texture.h b/include/API/Texture.h index 1b851393b..946e721f4 100644 --- a/include/API/Texture.h +++ b/include/API/Texture.h @@ -16,6 +16,7 @@ #include "API/Resources.h" #include "llvm/ADT/BitmaskEnum.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" @@ -154,6 +155,24 @@ struct TileShape { uint32_t Depth = 1; }; +struct SubresourceFootprint { + uint64_t Offset = 0; // Byte offset of this subresource in the buffer. + uint32_t RowPitchInBytes = 0; // Destination row stride (may include padding). + uint32_t RowSizeInBytes = 0; // Tightly-packed bytes per row to copy. + uint32_t NumRows = 0; // Number of rows in this subresource. +}; + +struct TextureUploadLayout { + llvm::SmallVector Subresources; // One entry per mip. + uint64_t TotalSizeInBytes = 0; +}; + +// Compute a tightly-packed upload layout (no row or subresource padding) for +// the given texture description. Suitable for backends whose buffer-to-texture +// copy consumes a tightly-packed staging buffer (e.g. Vulkan, Metal). +TextureUploadLayout +computeTightTextureUploadLayout(const TextureCreateDesc &Desc); + class Texture { GPUAPI API; diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 60949a287..5297b4375 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -789,15 +789,20 @@ class DXComputeEncoder : public offloadtest::ComputeEncoder { D3D12_RESOURCE_STATE_COPY_DEST); CB.flushBarrier(); - const uint32_t ElementSize = getFormatSizeInBytes(DXDst.Desc.Fmt); - const D3D12_PLACED_SUBRESOURCE_FOOTPRINT Footprint{ - 0, - CD3DX12_SUBRESOURCE_FOOTPRINT( - getDXGIFormat(DXDst.Desc.Fmt), DXDst.Desc.Width, DXDst.Desc.Height, - 1, getAlignedTexturePitch(DXDst.Desc.Width, ElementSize))}; - const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(DXDst.Resource.Get(), 0); - const CD3DX12_TEXTURE_COPY_LOCATION SrcLoc(DXSrc.Buffer.Get(), Footprint); - CB.CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + const D3D12_RESOURCE_DESC TexDesc = DXDst.Resource->GetDesc(); + const uint32_t NumSubresources = TexDesc.MipLevels; + llvm::SmallVector Layouts( + NumSubresources); + ComPtr Device; + DXDst.Resource->GetDevice(IID_PPV_ARGS(&Device)); + Device->GetCopyableFootprints(&TexDesc, 0, NumSubresources, 0, + Layouts.data(), nullptr, nullptr, nullptr); + for (uint32_t Sub = 0; Sub < NumSubresources; ++Sub) { + const CD3DX12_TEXTURE_COPY_LOCATION DstLoc(DXDst.Resource.Get(), Sub); + const CD3DX12_TEXTURE_COPY_LOCATION SrcLoc(DXSrc.Buffer.Get(), + Layouts[Sub]); + CB.CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + } if (DXSrc.PreferredState != D3D12_RESOURCE_STATE_COPY_SOURCE) CB.addResourceTransition(DXSrc.Buffer.Get(), @@ -2049,6 +2054,43 @@ class DXDevice : public offloadtest::Device { return getAlignedTexturePitch(Desc.Width, getFormatSizeInBytes(Desc.Fmt)); } + TextureUploadLayout + getTextureUploadLayout(const TextureCreateDesc &Desc) const override { + // Only the fields GetCopyableFootprints consults are needed here; layout, + // flags, and clear value do not affect the copyable footprint. + D3D12_RESOURCE_DESC TexDesc = {}; + TexDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + TexDesc.Width = Desc.Width; + TexDesc.Height = Desc.Height; + TexDesc.DepthOrArraySize = 1; + TexDesc.MipLevels = static_cast(Desc.MipLevels); + TexDesc.Format = getDXGIFormat(Desc.Fmt); + TexDesc.SampleDesc.Count = 1; + + const uint32_t NumSubresources = Desc.MipLevels; + llvm::SmallVector Footprints( + NumSubresources); + llvm::SmallVector NumRows(NumSubresources); + llvm::SmallVector RowSizes(NumSubresources); + UINT64 TotalBytes = 0; + Device->GetCopyableFootprints(&TexDesc, 0, NumSubresources, 0, + Footprints.data(), NumRows.data(), + RowSizes.data(), &TotalBytes); + + TextureUploadLayout Layout; + Layout.TotalSizeInBytes = TotalBytes; + Layout.Subresources.reserve(NumSubresources); + for (uint32_t I = 0; I < NumSubresources; ++I) { + SubresourceFootprint Sub; + Sub.Offset = Footprints[I].Offset; + Sub.RowPitchInBytes = Footprints[I].Footprint.RowPitch; + Sub.RowSizeInBytes = static_cast(RowSizes[I]); + Sub.NumRows = NumRows[I]; + Layout.Subresources.push_back(Sub); + } + return Layout; + } + static llvm::Expected> create(ComPtr Adapter, const DeviceConfig &Config) { ComPtr Device; diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index 33260a6cf..6e374649a 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -528,32 +528,34 @@ offloadtest::createTextureWithData( Device &Dev, std::string Name, const TextureCreateDesc &Desc, const void *Data, size_t SizeInBytes, ComputeEncoder *Encoder, std::unique_ptr *OutUploadBuffer) { - - const uint64_t PackedRowStrideInBytes = - Desc.Width * getFormatSizeInBytes(Desc.Fmt); - if (SizeInBytes < PackedRowStrideInBytes * Desc.Height) + if (Encoder == nullptr) return llvm::createStringError( - "Data upload is not enough for texture size."); + "An encoder is required to upload texture data."); + if (OutUploadBuffer == nullptr) + return llvm::createStringError( + "An upload buffer is required to create a texture with data."); auto TextureOrErr = Dev.createTexture(Name, Desc); if (!TextureOrErr) return TextureOrErr.takeError(); auto Texture = std::move(*TextureOrErr); - if (OutUploadBuffer == nullptr) - return llvm::createStringError("An upload buffer is required to create a " - "GpuOnly texture with data."); + const TextureUploadLayout Layout = Dev.getTextureUploadLayout(Desc); - const uint64_t TexRowStrideInBytes = - Dev.getTextureUploadRowStrideInBytes(Desc); - const uint64_t UploadBufferSizeInBytes = - (Desc.Height - 1) * TexRowStrideInBytes + PackedRowStrideInBytes; + // The source data is tightly packed across mips, so its required size is the + // sum of each subresource's tight row size times its row count, independent + // of any backend row/offset padding in the upload buffer. + uint64_t PackedSizeInBytes = 0; + for (const SubresourceFootprint &Sub : Layout.Subresources) + PackedSizeInBytes += uint64_t(Sub.RowSizeInBytes) * Sub.NumRows; + if (SizeInBytes < PackedSizeInBytes) + return llvm::createStringError( + "Data upload is not enough for texture size."); - // Create Upload buffer const BufferCreateDesc UploadDesc = BufferCreateDesc::uploadBuffer(); const std::string UploadBufferName = Name + " (Upload Buffer)"; auto UploadBufferOrErr = - Dev.createBuffer(UploadBufferName, UploadDesc, UploadBufferSizeInBytes); + Dev.createBuffer(UploadBufferName, UploadDesc, Layout.TotalSizeInBytes); if (!UploadBufferOrErr) return UploadBufferOrErr.takeError(); *OutUploadBuffer = std::move(*UploadBufferOrErr); @@ -561,18 +563,19 @@ offloadtest::createTextureWithData( auto MappedPtrOrErr = (*OutUploadBuffer)->map(); if (!MappedPtrOrErr) return MappedPtrOrErr.takeError(); - - uint8_t *DstPtr = (uint8_t *)*MappedPtrOrErr; - const uint8_t *SrcPtr = (const uint8_t *)Data; - - for (uint32_t Y = 0; Y < Desc.Height; ++Y) { - memcpy(DstPtr, SrcPtr, PackedRowStrideInBytes); - DstPtr += TexRowStrideInBytes; - SrcPtr += PackedRowStrideInBytes; + auto *const DstBase = static_cast(*MappedPtrOrErr); + const auto *SrcPtr = static_cast(Data); + + for (const SubresourceFootprint &Sub : Layout.Subresources) { + uint8_t *DstPtr = DstBase + Sub.Offset; + for (uint32_t Row = 0; Row < Sub.NumRows; ++Row) { + memcpy(DstPtr, SrcPtr, Sub.RowSizeInBytes); + DstPtr += Sub.RowPitchInBytes; + SrcPtr += Sub.RowSizeInBytes; + } } (*OutUploadBuffer)->unmap(); - // Copy Buffer to Texture if (auto Err = Encoder->copyBufferToTexture(**OutUploadBuffer, *Texture)) return Err; diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index bf3554bb2..082a17aa6 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -732,21 +732,27 @@ class MTLComputeEncoder : public offloadtest::ComputeEncoder { auto &MTLSrc = static_cast(Src); auto &MTLDst = static_cast(Dst); - // The upload buffer is laid out with a tightly packed row stride matching - // getTextureUploadRowStrideInBytes(), so the source bytes-per-row is the - // texture width times the element size. + // The upload buffer holds tightly packed texel data for every mip level + // (see createTextureWithData): each mip's rows are contiguous with no + // padding, and the mips follow one another. Copy one mip at a time, with + // the source bytes-per-row being that mip's width times the element size. const size_t ElemSize = getFormatSizeInBytes(MTLDst.Desc.Fmt); - const size_t RowBytes = MTLDst.Desc.Width * ElemSize; - const size_t ImageBytes = RowBytes * MTLDst.Desc.Height; - const MTL::Size CopySize(MTLDst.Desc.Width, MTLDst.Desc.Height, 1); insertDebugSignpost(llvm::formatv("copyBufferToTexture {0} -> {1}", MTLSrc.Name, MTLDst.Name) .str()); - BlitEnc->copyFromBuffer(MTLSrc.Buf, /*sourceOffset=*/0, RowBytes, - ImageBytes, CopySize, MTLDst.Tex, - /*destinationSlice=*/0, /*destinationLevel=*/0, - MTL::Origin(0, 0, 0)); + size_t CurrentOffset = 0; + for (uint32_t I = 0; I < MTLDst.Desc.MipLevels; ++I) { + const uint32_t MipWidth = std::max(1u, MTLDst.Desc.Width >> I); + const uint32_t MipHeight = std::max(1u, MTLDst.Desc.Height >> I); + const size_t RowBytes = MipWidth * ElemSize; + const size_t ImageBytes = RowBytes * MipHeight; + BlitEnc->copyFromBuffer(MTLSrc.Buf, CurrentOffset, RowBytes, ImageBytes, + MTL::Size(MipWidth, MipHeight, 1), MTLDst.Tex, + /*destinationSlice=*/0, /*destinationLevel=*/I, + MTL::Origin(0, 0, 0)); + CurrentOffset += ImageBytes; + } addBarrierScope(MTL::BarrierScopeTextures); return llvm::Error::success(); @@ -2402,6 +2408,12 @@ class MTLDevice : public offloadtest::Device { return Desc.Width * getFormatSizeInBytes(Desc.Fmt); } + TextureUploadLayout + getTextureUploadLayout(const TextureCreateDesc &Desc) const override { + // copyBufferToTexture consumes a tightly-packed staging buffer. + return computeTightTextureUploadLayout(Desc); + } + llvm::Expected> createCommandBuffer() override { auto CBOrErr = MTLCommandBuffer::create(GraphicsQueue.Queue); diff --git a/lib/API/Texture.cpp b/lib/API/Texture.cpp index c7d086b74..24f5387fd 100644 --- a/lib/API/Texture.cpp +++ b/lib/API/Texture.cpp @@ -1,6 +1,8 @@ #include "API/Texture.h" #include "API/Device.h" +#include + // Calculate the size in bytes of the texture data given a linear layout // Useful for calculating the size for an upload or readback buffer. size_t @@ -10,3 +12,24 @@ offloadtest::Texture::calculateLinearSizeInBytes(const Device &Dev) const { return (Desc.Height - 1) * Stride + Desc.Width * getFormatSizeInBytes(Desc.Fmt); } + +offloadtest::TextureUploadLayout +offloadtest::computeTightTextureUploadLayout(const TextureCreateDesc &Desc) { + const uint32_t ElementSize = getFormatSizeInBytes(Desc.Fmt); + TextureUploadLayout Layout; + Layout.Subresources.reserve(Desc.MipLevels); + uint64_t Offset = 0; + for (uint32_t I = 0; I < Desc.MipLevels; ++I) { + const uint32_t MipWidth = std::max(1u, Desc.Width >> I); + const uint32_t MipHeight = std::max(1u, Desc.Height >> I); + SubresourceFootprint Sub; + Sub.Offset = Offset; + Sub.RowSizeInBytes = MipWidth * ElementSize; + Sub.RowPitchInBytes = Sub.RowSizeInBytes; + Sub.NumRows = MipHeight; + Layout.Subresources.push_back(Sub); + Offset += uint64_t(Sub.RowSizeInBytes) * Sub.NumRows; + } + Layout.TotalSizeInBytes = Offset; + return Layout; +} diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index 5e5f5c9f7..380d144cd 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -1050,11 +1050,32 @@ class VKComputeEncoder : public offloadtest::ComputeEncoder { VK_ACCESS_TRANSFER_WRITE_BIT); CB.flushBarrier(); + const VkImageAspectFlags AspectMask = isDepthFormat(VKDst.Desc.Fmt) + ? VK_IMAGE_ASPECT_DEPTH_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; + const uint32_t ElementSize = getFormatSizeInBytes(VKDst.Desc.Fmt); + llvm::SmallVector Regions; + uint64_t CurrentOffset = 0; + for (uint32_t I = 0; I < VKDst.Desc.MipLevels; ++I) { + const uint32_t MipWidth = std::max(1u, VKDst.Desc.Width >> I); + const uint32_t MipHeight = std::max(1u, VKDst.Desc.Height >> I); + VkBufferImageCopy Region = {}; + Region.bufferOffset = CurrentOffset; + Region.imageSubresource.aspectMask = AspectMask; + Region.imageSubresource.mipLevel = I; + Region.imageSubresource.baseArrayLayer = 0; + Region.imageSubresource.layerCount = 1; + Region.imageExtent = {MipWidth, MipHeight, 1}; + Regions.push_back(Region); + CurrentOffset += uint64_t(MipWidth) * MipHeight * ElementSize; + } + insertDebugSignpost( - llvm::formatv("copyTextureToBuffer {0} -> {1}", VKSrc.Name, VKDst.Name) + llvm::formatv("copyBufferToTexture {0} -> {1}", VKSrc.Name, VKDst.Name) .str()); vkCmdCopyBufferToImage(CB.CmdBuffer, VKSrc.Buffer, VKDst.Image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, nullptr); + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Regions.size(), + Regions.data()); CB.addImageTransition(VK_ACCESS_TRANSFER_WRITE_BIT, /*SrcAccessMask*/ VK_ACCESS_NONE, /*DstAccessMask*/ @@ -2928,6 +2949,12 @@ class VulkanDevice : public offloadtest::Device { TightRow, Props.limits.optimalBufferCopyRowPitchAlignment)); } + TextureUploadLayout + getTextureUploadLayout(const TextureCreateDesc &Desc) const override { + // copyBufferToTexture consumes a tightly-packed staging buffer. + return computeTightTextureUploadLayout(Desc); + } + const Capabilities &getCapabilities() override { if (Caps.empty()) queryCapabilities(); diff --git a/lib/Support/OffloadMigration.cpp b/lib/Support/OffloadMigration.cpp index 008009d80..1b851c832 100644 --- a/lib/Support/OffloadMigration.cpp +++ b/lib/Support/OffloadMigration.cpp @@ -272,7 +272,7 @@ llvm::Error createResources(Device &Dev, Pipeline &P, CreateDesc.Fmt = *FormatOrErr; CreateDesc.Width = R.BufferPtr->OutputProps.Width; CreateDesc.Height = R.BufferPtr->OutputProps.Height; - CreateDesc.MipLevels = 1; + CreateDesc.MipLevels = R.BufferPtr->OutputProps.MipLevels; for (auto &Data : R.BufferPtr->Data) { std::unique_ptr UploadBuffer; @@ -290,7 +290,7 @@ llvm::Error createResources(Device &Dev, Pipeline &P, Texture = std::move(*TextureOrErr); } else { auto TextureOrErr = - createTextureWithData(Dev, "Texture", CreateDesc, Data.get(), + createTextureWithData(Dev, R.Name, CreateDesc, Data.get(), R.size(), Enc.get(), &UploadBuffer); if (!TextureOrErr) return TextureOrErr.takeError(); diff --git a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml index 484fcc93e..14d2aa74f 100644 --- a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml +++ b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml @@ -114,8 +114,8 @@ Results: ... #--- end -# Unimplemented: Clang + DX: https://github.com/llvm/llvm-project/issues/101558 -# XFAIL: DirectX || Metal +# Unimplemented: Metal: https://github.com/llvm/llvm-project/issues/101558 +# XFAIL: Metal # Bug https://github.com/llvm/llvm-project/issues/197837 # XFAIL: Clang && Vulkan diff --git a/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml b/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml index 49f8ee4ba..67c1f2bab 100644 --- a/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml +++ b/test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml @@ -61,8 +61,6 @@ Results: #--- end # Unimplemented: https://github.com/llvm/offload-test-suite/issues/1039 -# XFAIL: DirectX - # XFAIL: Metal # RUN: split-file %s %t From fa22bfda9ebf453c76c2090d121d97cc84ee01ac Mon Sep 17 00:00:00 2001 From: Manon Oomen Date: Fri, 3 Jul 2026 16:08:04 +0200 Subject: [PATCH 2/2] Mark GetDimensions as XFAIL on Clang && DirectX. --- test/Feature/Textures/Texture2D.GetDimensions.test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml index 14d2aa74f..41677445f 100644 --- a/test/Feature/Textures/Texture2D.GetDimensions.test.yaml +++ b/test/Feature/Textures/Texture2D.GetDimensions.test.yaml @@ -116,6 +116,7 @@ Results: # Unimplemented: Metal: https://github.com/llvm/llvm-project/issues/101558 # XFAIL: Metal +# XFAIL: Clang && DirectX # Bug https://github.com/llvm/llvm-project/issues/197837 # XFAIL: Clang && Vulkan