Skip to content

Commit 8c6a168

Browse files
authored
Introduce a new Texture type with DX,VK,MTL implementations. (#1020)
## Summary - **Introduced `Texture.h`** with a shared `TextureCreateDesc`, `TextureFormat` enum, `TextureUsage` flags, `ClearValue` variant (`ClearColor` / `ClearDepthStencil`), and `validateTextureCreateDesc` returning specific `llvm::Error` messages - **Added `createTexture` to all three backends** (DX, Vulkan, Metal) as a virtual method on `Device`, handling resource creation, optimized clear values, and format/usage validation - **Unified render target creation** via `createRenderTargetFromCPUBuffer(Device&, CPUBuffer&)` free function — all backends now route through `createTexture` instead of manually calling API-specific resource creation - **Unified depth/stencil creation** via `createDefaultDepthStencilTarget(Device&, Width, Height)` free function with `D32FloatS8Uint` format — added depth testing to DX and Metal backends (previously only Vulkan had it) - **Readback buffers** created through the virtual `createBuffer` API with `MemoryLocation::GpuToCpu`; Metal refactored from `StorageModeShared` texture with `getBytes()` to `StorageModePrivate` texture + blit copy to readback buffer (this is to align functionality between backends in preparation of api-agnostic code-flow. Sacrifices had to be made 😛) - **Views on textures** — RTV/DSV descriptor heaps (DX) and `VkImageView` (VK) are now created at `createTexture` time for `RenderTarget`/`DepthStencil` usage, removing view creation boilerplate from pipeline setup - Render target and readback buffer are stored directly as `shared_ptr<VulkanTexture>` / `shared_ptr<VulkanBuffer>` on `InvocationState` - **Clear values read from texture descriptors** in all backends instead of being hardcoded - **Added `toTextureFormat(DataFormat, Channels)`** as a bridge function while refactoring, with `validateTextureDescMatchesCPUBuffer` to catch mismatches between the old and new description systems - **Split Metal storage mode helpers** into `getMetalTextureStorageMode` and `getMetalBufferResourceOptions` because `GpuToCpu` maps differently for textures (Managed) vs buffers (Shared) ## Future direction Added for context to better understand some of the changes in this PR. - **SRV/UAV views on textures** — currently not stored on the texture because they would require a descriptor heap allocation scheme - Work towards eliminating `DataFormat + Channels` dual description. - **Route VK vertex buffer through `createBuffer`** — still uses the old `ResourceRef`/`BufferRef` system - Abstract readback copy - **Unify `InvocationState`** across backends
1 parent f1852e8 commit 8c6a168

12 files changed

Lines changed: 1536 additions & 300 deletions

File tree

include/API/Buffer.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===- Buffer.h - Offload API Buffer --------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef OFFLOADTEST_API_BUFFER_H
13+
#define OFFLOADTEST_API_BUFFER_H
14+
15+
#include "API/Resources.h"
16+
17+
namespace offloadtest {
18+
19+
struct BufferCreateDesc {
20+
MemoryLocation Location;
21+
};
22+
23+
class Buffer {
24+
public:
25+
virtual ~Buffer() = default;
26+
27+
Buffer(const Buffer &) = delete;
28+
Buffer &operator=(const Buffer &) = delete;
29+
30+
protected:
31+
Buffer() = default;
32+
};
33+
34+
} // namespace offloadtest
35+
36+
#endif // OFFLOADTEST_API_BUFFER_H

include/API/Device.h

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
#include "Config.h"
1616

1717
#include "API/API.h"
18+
#include "API/Buffer.h"
1819
#include "API/Capabilities.h"
1920
#include "API/CommandBuffer.h"
21+
#include "API/Texture.h"
22+
#include "Support/Pipeline.h"
2023
#include "llvm/ADT/StringRef.h"
2124
#include "llvm/ADT/iterator_range.h"
2225
#include "llvm/Support/Error.h"
@@ -37,27 +40,6 @@ struct DeviceConfig {
3740
bool EnableValidationLayer = false;
3841
};
3942

40-
enum class MemoryLocation {
41-
GpuOnly,
42-
CpuToGpu,
43-
GpuToCpu,
44-
};
45-
46-
struct BufferCreateDesc {
47-
MemoryLocation Location;
48-
};
49-
50-
class Buffer {
51-
public:
52-
virtual ~Buffer() = default;
53-
54-
Buffer(const Buffer &) = delete;
55-
Buffer &operator=(const Buffer &) = delete;
56-
57-
protected:
58-
Buffer() = default;
59-
};
60-
6143
class Fence {
6244
public:
6345
virtual ~Fence() = default;
@@ -99,6 +81,10 @@ class Device {
9981
virtual llvm::Expected<std::shared_ptr<Buffer>>
10082
createBuffer(std::string Name, BufferCreateDesc &Desc,
10183
size_t SizeInBytes) = 0;
84+
85+
virtual llvm::Expected<std::shared_ptr<Texture>>
86+
createTexture(std::string Name, TextureCreateDesc &Desc) = 0;
87+
10288
virtual void printExtra(llvm::raw_ostream &OS) {}
10389

10490
virtual llvm::Expected<std::unique_ptr<CommandBuffer>>
@@ -122,6 +108,16 @@ initializeMetalDevices(const DeviceConfig Config,
122108
llvm::Expected<llvm::SmallVector<std::unique_ptr<Device>>>
123109
initializeDevices(const DeviceConfig Config);
124110

111+
// Creates a render target texture using the format and dimensions from a
112+
// CPUBuffer. Does not upload the buffer's data — only uses its description to
113+
// configure the texture.
114+
llvm::Expected<std::shared_ptr<Texture>>
115+
createRenderTargetFromCPUBuffer(Device &Dev, const CPUBuffer &Buf);
116+
117+
// Creates a depth/stencil texture matching the dimensions of a render target.
118+
llvm::Expected<std::shared_ptr<Texture>>
119+
createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, uint32_t Height);
120+
125121
} // namespace offloadtest
126122

127123
#endif // OFFLOADTEST_API_DEVICE_H

include/API/FormatConversion.h

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//===- FormatConversion.h - Refactoring helpers ---------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Transitional helpers for converting between the legacy DataFormat + Channels
10+
// description system and the unified Format enum. This file should be deleted
11+
// once the pipeline is fully migrated to use Format directly.
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef OFFLOADTEST_API_FORMATCONVERSION_H
16+
#define OFFLOADTEST_API_FORMATCONVERSION_H
17+
18+
#include "API/Resources.h"
19+
#include "API/Texture.h"
20+
#include "Support/Pipeline.h"
21+
22+
#include "llvm/Support/Error.h"
23+
24+
namespace offloadtest {
25+
26+
// Bridge for code that still describes textures as DataFormat + Channels (e.g.
27+
// render targets bound via CPUBuffer). Once the pipeline is refactored to use
28+
// Format directly, this function can be removed.
29+
inline llvm::Expected<Format> toFormat(DataFormat Format, int Channels) {
30+
switch (Format) {
31+
case DataFormat::Int16:
32+
switch (Channels) {
33+
case 1:
34+
return Format::R16Sint;
35+
case 2:
36+
return Format::RG16Sint;
37+
case 4:
38+
return Format::RGBA16Sint;
39+
}
40+
break;
41+
case DataFormat::UInt16:
42+
switch (Channels) {
43+
case 1:
44+
return Format::R16Uint;
45+
case 2:
46+
return Format::RG16Uint;
47+
case 4:
48+
return Format::RGBA16Uint;
49+
}
50+
break;
51+
case DataFormat::Int32:
52+
switch (Channels) {
53+
case 1:
54+
return Format::R32Sint;
55+
case 2:
56+
return Format::RG32Sint;
57+
case 4:
58+
return Format::RGBA32Sint;
59+
}
60+
break;
61+
case DataFormat::UInt32:
62+
switch (Channels) {
63+
case 1:
64+
return Format::R32Uint;
65+
case 2:
66+
return Format::RG32Uint;
67+
case 4:
68+
return Format::RGBA32Uint;
69+
}
70+
break;
71+
case DataFormat::Float32:
72+
switch (Channels) {
73+
case 1:
74+
return Format::R32Float;
75+
case 2:
76+
return Format::RG32Float;
77+
case 4:
78+
return Format::RGBA32Float;
79+
}
80+
break;
81+
case DataFormat::Depth32:
82+
// D32FloatS8Uint is not expressible as DataFormat + Channels because the
83+
// stencil component is uint8, not a second Depth32 channel. Once the
84+
// pipeline uses Format directly, this limitation goes away.
85+
if (Channels == 1)
86+
return Format::D32Float;
87+
break;
88+
// No Format mapping for these DataFormats.
89+
case DataFormat::Hex8:
90+
case DataFormat::Hex16:
91+
case DataFormat::Hex32:
92+
case DataFormat::Hex64:
93+
case DataFormat::UInt64:
94+
case DataFormat::Int64:
95+
case DataFormat::Float16:
96+
case DataFormat::Float64:
97+
case DataFormat::Bool:
98+
return llvm::createStringError(std::errc::invalid_argument,
99+
"DataFormat %d has no Format equivalent.",
100+
static_cast<int>(Format));
101+
}
102+
return llvm::createStringError(std::errc::invalid_argument,
103+
"No Format for DataFormat %d with %d "
104+
"channel(s).",
105+
static_cast<int>(Format), Channels);
106+
}
107+
108+
// Validates that a TextureCreateDesc is consistent with the CPUBuffer it was
109+
// derived from. Call this after building a TextureCreateDesc from a CPUBuffer
110+
// to catch mismatches between the two description systems.
111+
inline llvm::Error
112+
validateTextureDescMatchesCPUBuffer(const TextureCreateDesc &Desc,
113+
const CPUBuffer &Buf) {
114+
auto ExpectedFmt = toFormat(Buf.Format, Buf.Channels);
115+
if (!ExpectedFmt)
116+
return ExpectedFmt.takeError();
117+
if (Desc.Format != *ExpectedFmt)
118+
return llvm::createStringError(
119+
std::errc::invalid_argument,
120+
"TextureCreateDesc format '%s' does not match CPUBuffer format "
121+
"(DataFormat %d, %d channels -> '%s').",
122+
getFormatName(Desc.Format).data(), static_cast<int>(Buf.Format),
123+
Buf.Channels, getFormatName(*ExpectedFmt).data());
124+
if (Desc.Width != static_cast<uint32_t>(Buf.OutputProps.Width))
125+
return llvm::createStringError(
126+
std::errc::invalid_argument,
127+
"TextureCreateDesc width %u does not match CPUBuffer width %d.",
128+
Desc.Width, Buf.OutputProps.Width);
129+
if (Desc.Height != static_cast<uint32_t>(Buf.OutputProps.Height))
130+
return llvm::createStringError(
131+
std::errc::invalid_argument,
132+
"TextureCreateDesc height %u does not match CPUBuffer height %d.",
133+
Desc.Height, Buf.OutputProps.Height);
134+
if (Desc.MipLevels != static_cast<uint32_t>(Buf.OutputProps.MipLevels))
135+
return llvm::createStringError(
136+
std::errc::invalid_argument,
137+
"TextureCreateDesc mip levels %u does not match CPUBuffer mip "
138+
"levels %d.",
139+
Desc.MipLevels, Buf.OutputProps.MipLevels);
140+
const uint32_t TexelSize = getFormatSizeInBytes(Desc.Format);
141+
if (Buf.Stride > 0 && static_cast<uint32_t>(Buf.Stride) != TexelSize)
142+
return llvm::createStringError(
143+
std::errc::invalid_argument,
144+
"CPUBuffer stride %d does not match texture format element size %u.",
145+
Buf.Stride, TexelSize);
146+
const uint64_t ExpectedSize =
147+
static_cast<uint64_t>(Desc.Width) * Desc.Height * TexelSize;
148+
if (static_cast<uint64_t>(Buf.size()) != ExpectedSize)
149+
return llvm::createStringError(
150+
std::errc::invalid_argument,
151+
"CPUBuffer size %u does not match expected size %llu "
152+
"(width %u * height %u * element size %u).",
153+
Buf.size(), ExpectedSize, Desc.Width, Desc.Height, TexelSize);
154+
return llvm::Error::success();
155+
}
156+
157+
} // namespace offloadtest
158+
159+
#endif // OFFLOADTEST_API_FORMATCONVERSION_H

0 commit comments

Comments
 (0)