Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/API/Device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::unique_ptr<RenderPass>>
createRenderPass(const RenderPassDesc &Desc) = 0;

Expand Down Expand Up @@ -421,6 +427,9 @@ createBufferWithData(Device &Dev, std::string Name,
size_t SizeInBytes, ComputeEncoder *Encoder,
std::unique_ptr<offloadtest::Buffer> *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<std::unique_ptr<offloadtest::Texture>>
createTextureWithData(Device &Dev, std::string Name,
const TextureCreateDesc &Desc, const void *Data,
Expand Down
19 changes: 19 additions & 0 deletions include/API/Texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<SubresourceFootprint> 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;

Expand Down
60 changes: 51 additions & 9 deletions lib/API/DX/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> Layouts(
NumSubresources);
ComPtr<ID3D12DeviceX> 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(),
Expand Down Expand Up @@ -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 = {};
Comment thread
MarijnS95 marked this conversation as resolved.
TexDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
TexDesc.Width = Desc.Width;
TexDesc.Height = Desc.Height;
TexDesc.DepthOrArraySize = 1;
TexDesc.MipLevels = static_cast<UINT16>(Desc.MipLevels);
TexDesc.Format = getDXGIFormat(Desc.Fmt);
TexDesc.SampleDesc.Count = 1;

const uint32_t NumSubresources = Desc.MipLevels;
llvm::SmallVector<D3D12_PLACED_SUBRESOURCE_FOOTPRINT> Footprints(
NumSubresources);
llvm::SmallVector<UINT> NumRows(NumSubresources);
llvm::SmallVector<UINT64> 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<uint32_t>(RowSizes[I]);
Sub.NumRows = NumRows[I];
Layout.Subresources.push_back(Sub);
}
return Layout;
}

static llvm::Expected<std::unique_ptr<offloadtest::Device>>
create(ComPtr<IDXCoreAdapter> Adapter, const DeviceConfig &Config) {
ComPtr<ID3D12DeviceX> Device;
Expand Down
49 changes: 26 additions & 23 deletions lib/API/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,51 +528,54 @@ offloadtest::createTextureWithData(
Device &Dev, std::string Name, const TextureCreateDesc &Desc,
const void *Data, size_t SizeInBytes, ComputeEncoder *Encoder,
std::unique_ptr<offloadtest::Buffer> *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);

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<uint8_t *>(*MappedPtrOrErr);
const auto *SrcPtr = static_cast<const uint8_t *>(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;

Expand Down
32 changes: 22 additions & 10 deletions lib/API/MTL/MTLDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,21 +732,27 @@ class MTLComputeEncoder : public offloadtest::ComputeEncoder {
auto &MTLSrc = static_cast<MTLBuffer &>(Src);
auto &MTLDst = static_cast<MTLTexture &>(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();
Expand Down Expand Up @@ -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<std::unique_ptr<offloadtest::CommandBuffer>>
createCommandBuffer() override {
auto CBOrErr = MTLCommandBuffer::create(GraphicsQueue.Queue);
Expand Down
23 changes: 23 additions & 0 deletions lib/API/Texture.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "API/Texture.h"
#include "API/Device.h"

#include <algorithm>

// 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
Expand All @@ -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;

@MarijnS95 MarijnS95 Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes uncompressed formats: MipWidth * ElementSize and mips shrinking to 1x1 texel are wrong for block-compressed formats (min 4x4 block). DX is fine via GetCopyableFootprints; VK/Metal would not be. No BC formats in the suite today, so just worth a note:

Suggested change
Sub.RowSizeInBytes = MipWidth * ElementSize;
// Assumes uncompressed formats; block-compressed formats would need
// block-based row sizing rather than MipWidth * ElementSize.
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;
}
31 changes: 29 additions & 2 deletions lib/API/VK/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<VkBufferImageCopy> 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*/
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions lib/Support/OffloadMigration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<offloadtest::Buffer> UploadBuffer;
Expand All @@ -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();
Expand Down
5 changes: 3 additions & 2 deletions test/Feature/Textures/Texture2D.GetDimensions.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-points the same issue (llvm-project#101558) from "Clang + DX" to "Metal". If it's a clang frontend issue it should hit DX and Metal equally, so DX presumably now passes for a different reason (the MipLevels propagation) and the link was mis-attributed. Can you confirm DX CI is green here and fix the comment's rationale rather than re-aiming the same link at Metal?

# XFAIL: Clang && DirectX

# Bug https://github.com/llvm/llvm-project/issues/197837
# XFAIL: Clang && Vulkan
Expand Down
2 changes: 0 additions & 2 deletions test/Feature/Textures/Texture2D.mips.OperatorIndex.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading