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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ Buffers:
- Name: In2
Format: Hex16
Data: [ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]
- Name: Tex
Format: Float32
Channels: 4
OutputProps:
Width: 2
Height: 2
Depth: 1
MipLevels: 2
Data: [ 1.0, 0.0, 0.0, 1.0, # Mip 0 (2x2)
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 0.0, 1.0 ] # Mip 1 (1x1)
- Name: Out1 # Buffer where our output will go
Format: Float32
Stride: 4
Expand Down
93 changes: 93 additions & 0 deletions docs/MipMappedTextures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Mipmapped Textures

The test suite supports textures with multiple mipmap levels. This allows
verifying that the compiler correctly generates code for
`CalculateLevelOfDetail`, `SampleGrad` (explicit gradients), and direct access
via `mips[level][coords]`.

## Defining Mip Levels

To define a texture with multiple mips, specify the `MipLevels` field in
`OutputProps`.

**Example:**
```yaml
Buffers:
- Name: Tex
Format: Float32
Channels: 4
OutputProps:
Width: 4
Height: 4
Depth: 1
MipLevels: 3 # Defines Mip 0 (4x4), Mip 1 (2x2), and Mip 2 (1x1)
```

## Dimension Calculation

The dimensions of each mip level are calculated automatically based on the base
`Width`, `Height`, and `Depth` provided in `OutputProps`. Following standard
graphics API conventions (Vulkan, DirectX, Metal), each subsequent mip level is
half the size of the previous level, rounded down, with a minimum of 1.

For Mip Level $N$:
* $\text{Width}_N = \max(1, \lfloor \text{Width}_0 / 2^N \rfloor)$
* $\text{Height}_N = \max(1, \lfloor \text{Height}_0 / 2^N \rfloor)$
* $\text{Depth}_N = \max(1, \lfloor \text{Depth}_0 / 2^N \rfloor)$

## Data Layout

The `Data` array in the YAML must contain the texel data for **all mip levels
sequentially**, packed tightly without padding.

For a 4x4 texture with 3 mip levels, the `Data` array structure is:

1. **Mip 0 (4x4 = 16 texels):** Indices 0 - 15.
2. **Mip 1 (2x2 = 4 texels):** Indices 16 - 19.
3. **Mip 2 (1x1 = 1 texel):** Index 20.

**Total Size:** 21 texels.

### Example YAML

```yaml
- Name: Tex
Format: Float32
Channels: 4 # RGBA
OutputProps: { Width: 4, Height: 4, Depth: 1, MipLevels: 3 }
Data: [
# --- Mip 0 (4x4) ---
1,0,0,1, 1,0,0,1, 1,0,0,1, 1,0,0,1, # Row 0 (Red)
1,0,0,1, 1,0,0,1, 1,0,0,1, 1,0,0,1, # Row 1
1,0,0,1, 1,0,0,1, 1,0,0,1, 1,0,0,1, # Row 2
1,0,0,1, 1,0,0,1, 1,0,0,1, 1,0,0,1, # Row 3

# --- Mip 1 (2x2) ---
0,1,0,1, 0,1,0,1, # Row 0 (Green)
0,1,0,1, 0,1,0,1, # Row 1

# --- Mip 2 (1x1) ---
0,0,1,1 # Single Pixel (Blue)
]
```

## Usage in Tests

This structure allows you to verify explicit mip selection in shaders:

```hlsl
// Should return Green (Mip 1)
float4 val = Tex.mips[1][int2(0,0)];

// Should return Blue (Mip 2)
float4 val2 = Tex.Load(int3(0,0,2));
```

## Implementation Notes

### Mipmap Filtering

Currently, the test suite hardcodes the mipmap sampling mode to **Nearest**
(`VK_SAMPLER_MIPMAP_MODE_NEAREST` in Vulkan). This ensures deterministic results
for tests that verify specific mip level selection without requiring linear
interpolation between levels. Linear mip filtering is not configurable via YAML.
1 change: 1 addition & 0 deletions include/Support/Pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ struct OutputProperties {
int Height;
int Width;
int Depth;
int MipLevels = 1;
};

static inline uint32_t getFormatSize(DataFormat Format) {
Expand Down
24 changes: 21 additions & 3 deletions lib/API/DX/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,16 @@ static DXResourceKind getDXKind(offloadtest::ResourceKind RK) {
llvm_unreachable("All cases handled");
}

static D3D12_RESOURCE_DESC getResourceDescription(const Resource &R) {
static llvm::Expected<D3D12_RESOURCE_DESC>
getResourceDescription(const Resource &R) {
const D3D12_RESOURCE_DIMENSION Dimension = getDXDimension(R.Kind);
const offloadtest::Buffer &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 =
Expand Down Expand Up @@ -611,7 +618,10 @@ class DXDevice : public offloadtest::Device {
llvm::Expected<ResourceBundle> createSRV(Resource &R, InvocationState &IS) {
ResourceBundle Bundle;

const D3D12_RESOURCE_DESC ResDesc = getResourceDescription(R);
auto ResDescOrErr = getResourceDescription(R);
if (!ResDescOrErr)
return ResDescOrErr.takeError();
const D3D12_RESOURCE_DESC ResDesc = *ResDescOrErr;
const D3D12_HEAP_PROPERTIES UploadHeapProp =
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
const D3D12_RESOURCE_DESC UploadResDesc =
Expand Down Expand Up @@ -708,7 +718,10 @@ class DXDevice : public offloadtest::Device {
ResourceBundle Bundle;
const uint32_t BufferSize = getUAVBufferSize(R);

const D3D12_RESOURCE_DESC ResDesc = getResourceDescription(R);
auto ResDescOrErr = getResourceDescription(R);
if (!ResDescOrErr)
return ResDescOrErr.takeError();
const D3D12_RESOURCE_DESC ResDesc = *ResDescOrErr;

const D3D12_HEAP_PROPERTIES ReadBackHeapProp =
CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK);
Expand Down Expand Up @@ -1314,6 +1327,11 @@ class DXDevice : public offloadtest::Device {
std::errc::invalid_argument,
"No render target bound for graphics pipeline.");
const Buffer &OutBuf = *P.Bindings.RTargetBufferPtr;
if (OutBuf.OutputProps.MipLevels != 1)
return llvm::createStringError(
std::errc::not_supported,
"Multiple mip levels are not yet supported for DirectX render "
"targets.");
D3D12_RESOURCE_DESC Desc = {};
Desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
Desc.Width = OutBuf.OutputProps.Width;
Expand Down
82 changes: 54 additions & 28 deletions lib/API/VK/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/Error.h"

#include <algorithm>
#include <cmath>
#include <memory>
#include <numeric>
#include <system_error>
Expand Down Expand Up @@ -714,7 +716,7 @@ class VKDevice : public offloadtest::Device {
ImageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
ImageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
ImageCreateInfo.format = getVKFormat(B.Format, B.Channels);
ImageCreateInfo.mipLevels = 1;
ImageCreateInfo.mipLevels = B.OutputProps.MipLevels;
ImageCreateInfo.arrayLayers = 1;
ImageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
ImageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
Expand Down Expand Up @@ -764,7 +766,7 @@ class VKDevice : public offloadtest::Device {
const Sampler &S = *R.SamplerPtr;
SamplerInfo.magFilter = getVKFilter(S.MagFilter);
SamplerInfo.minFilter = getVKFilter(S.MinFilter);
SamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
SamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
SamplerInfo.addressModeU = getVKAddressMode(S.Address);
SamplerInfo.addressModeV = getVKAddressMode(S.Address);
SamplerInfo.addressModeW = getVKAddressMode(S.Address);
Expand Down Expand Up @@ -1202,7 +1204,8 @@ class VKDevice : public offloadtest::Device {
ViewCreateInfo.subresourceRange.baseMipLevel = 0;
ViewCreateInfo.subresourceRange.baseArrayLayer = 0;
ViewCreateInfo.subresourceRange.layerCount = 1;
ViewCreateInfo.subresourceRange.levelCount = 1;
ViewCreateInfo.subresourceRange.levelCount =
R.BufferPtr->OutputProps.MipLevels;
IndexOfFirstBufferDataInArray = ImageInfos.size();
for (auto &ResRef : IS.Resources[OverallResIdx].ResourceRefs) {
ViewCreateInfo.image = ResRef.Image.Image;
Expand Down Expand Up @@ -1722,20 +1725,31 @@ class VKDevice : public offloadtest::Device {
return;
if (R.isImage()) {
const offloadtest::Buffer &B = *R.BufferPtr;
VkBufferImageCopy BufferCopyRegion = {};
BufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
BufferCopyRegion.imageSubresource.mipLevel = 0;
BufferCopyRegion.imageSubresource.baseArrayLayer = 0;
BufferCopyRegion.imageSubresource.layerCount = 1;
BufferCopyRegion.imageExtent.width = B.OutputProps.Width;
BufferCopyRegion.imageExtent.height = B.OutputProps.Height;
BufferCopyRegion.imageExtent.depth = 1;
BufferCopyRegion.bufferOffset = 0;
llvm::SmallVector<VkBufferImageCopy> Regions;
uint64_t CurrentOffset = 0;
for (int I = 0; I < B.OutputProps.MipLevels; ++I) {
VkBufferImageCopy Region = {};
Region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
Region.imageSubresource.mipLevel = I;
Region.imageSubresource.baseArrayLayer = 0;
Region.imageSubresource.layerCount = 1;
Region.imageExtent.width =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Width) >> I);
Region.imageExtent.height =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Height) >> I);
Region.imageExtent.depth =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Depth) >> I);
Region.bufferOffset = CurrentOffset;
Regions.push_back(Region);
CurrentOffset += static_cast<uint64_t>(Region.imageExtent.width) *
Region.imageExtent.height * Region.imageExtent.depth *
B.getElementSize();
}

VkImageSubresourceRange SubRange = {};
SubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
SubRange.baseMipLevel = 0;
SubRange.levelCount = 1;
SubRange.levelCount = B.OutputProps.MipLevels;
SubRange.layerCount = 1;

VkImageMemoryBarrier ImageBarrier = {};
Expand All @@ -1755,8 +1769,8 @@ class VKDevice : public offloadtest::Device {
nullptr, 1, &ImageBarrier);

vkCmdCopyBufferToImage(IS.CmdBuffer, ResRef.Host.Buffer,
ResRef.Image.Image, VK_IMAGE_LAYOUT_GENERAL, 1,
&BufferCopyRegion);
ResRef.Image.Image, VK_IMAGE_LAYOUT_GENERAL,
Regions.size(), Regions.data());
}

ImageBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
Expand Down Expand Up @@ -1792,10 +1806,11 @@ class VKDevice : public offloadtest::Device {
if (!R.isReadWrite())
return;
if (R.isImage()) {
const offloadtest::Buffer &B = *R.BufferPtr;
VkImageSubresourceRange SubRange = {};
SubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
SubRange.baseMipLevel = 0;
SubRange.levelCount = 1;
SubRange.levelCount = B.OutputProps.MipLevels;
SubRange.layerCount = 1;

VkImageMemoryBarrier ImageBarrier = {};
Expand All @@ -1815,20 +1830,31 @@ class VKDevice : public offloadtest::Device {
nullptr, 1, &ImageBarrier);
}

const offloadtest::Buffer &B = *R.BufferPtr;
VkBufferImageCopy BufferCopyRegion = {};
BufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
BufferCopyRegion.imageSubresource.mipLevel = 0;
BufferCopyRegion.imageSubresource.baseArrayLayer = 0;
BufferCopyRegion.imageSubresource.layerCount = 1;
BufferCopyRegion.imageExtent.width = B.OutputProps.Width;
BufferCopyRegion.imageExtent.height = B.OutputProps.Height;
BufferCopyRegion.imageExtent.depth = 1;
BufferCopyRegion.bufferOffset = 0;
llvm::SmallVector<VkBufferImageCopy> Regions;
uint64_t CurrentOffset = 0;
for (int I = 0; I < B.OutputProps.MipLevels; ++I) {
VkBufferImageCopy Region = {};
Region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
Region.imageSubresource.mipLevel = I;
Region.imageSubresource.baseArrayLayer = 0;
Region.imageSubresource.layerCount = 1;
Region.imageExtent.width =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Width) >> I);
Region.imageExtent.height =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Height) >> I);
Region.imageExtent.depth =
std::max(1u, static_cast<uint32_t>(B.OutputProps.Depth) >> I);
Region.bufferOffset = CurrentOffset;
Regions.push_back(Region);
CurrentOffset += static_cast<uint64_t>(Region.imageExtent.width) *
Region.imageExtent.height * Region.imageExtent.depth *
B.getElementSize();
}

for (auto &ResRef : R.ResourceRefs)
vkCmdCopyImageToBuffer(IS.CmdBuffer, ResRef.Image.Image,
VK_IMAGE_LAYOUT_GENERAL, ResRef.Host.Buffer, 1,
&BufferCopyRegion);
VK_IMAGE_LAYOUT_GENERAL, ResRef.Host.Buffer,
Regions.size(), Regions.data());

VkBufferMemoryBarrier Barrier = {};
Barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
Expand Down
20 changes: 20 additions & 0 deletions lib/Support/Pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,25 @@ void MappingTraits<offloadtest::Buffer>::mapping(IO &I,
}

I.mapOptional("OutputProps", B.OutputProps);

if (!I.outputting() && B.OutputProps.Width > 0) {
uint32_t ExpectedSize = 0;
uint32_t W = B.OutputProps.Width;
uint32_t H = B.OutputProps.Height;
uint32_t D = B.OutputProps.Depth;
const uint32_t ElementSize = B.getElementSize();
for (int I = 0; I < B.OutputProps.MipLevels; ++I) {
ExpectedSize += W * H * D * ElementSize;
W = std::max(1u, W / 2);
H = std::max(1u, H / 2);
D = std::max(1u, D / 2);
}

if (B.Size != ExpectedSize)
I.setError(Twine("Buffer '") + B.Name + "' size (" + Twine(B.Size) +
") does not match OutputProps dimensions (" +
Twine(ExpectedSize) + ")");
}
}

void MappingTraits<offloadtest::Resource>::mapping(IO &I,
Expand Down Expand Up @@ -419,6 +438,7 @@ void MappingTraits<offloadtest::OutputProperties>::mapping(
I.mapRequired("Height", P.Height);
I.mapRequired("Width", P.Width);
I.mapRequired("Depth", P.Depth);
I.mapOptional("MipLevels", P.MipLevels, 1);
}

void MappingTraits<offloadtest::dx::RootResource>::mapping(
Expand Down
Loading
Loading