From 411997764765fa6cd26eb2be88be5817515627dc Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 17:04:25 -0700 Subject: [PATCH 01/12] [DirectX] Map Depth32 to R32_FLOAT for user-bound textures (#1046) Adds the missing case for DataFormat::Depth32 in DX getDXFormat() so that SRV/UAV reads of textures declared with a depth-compatible format succeed. The depth-stencil view path (createTexture) uses getDXGIFormat() which already maps to D32_FLOAT. Adds Texture2D.Load.Depth32.test.yaml exercising the new path with a compute Load() of a 2x2 single-channel depth-format texture. Issue: https://github.com/llvm/offload-test-suite/issues/1046 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/DX/Device.cpp | 10 ++- .../Textures/Texture2D.Load.Depth32.test.yaml | 68 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 test/Feature/Textures/Texture2D.Load.Depth32.test.yaml diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 1a5d09124..dd07dad4b 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -117,8 +117,14 @@ static DXGI_FORMAT getDXFormat(DataFormat Format, int Channels) { return DXGI_FORMAT_R32G32B32A32_UINT; llvm_unreachable("Unsupported channel count for 64-bit format"); case DataFormat::Depth32: - llvm_unreachable( - "Depth32 format is not yet supported in the DirectX backend."); + if (Channels != 1) + llvm_unreachable("Depth32 format only supports a single channel."); + // For user-bound resources (SRV/UAV), expose the depth-compatible + // typeless component as R32_FLOAT so shaders can Load()/Sample() the + // depth values directly. Depth-stencil view paths use getDXGIFormat() + // (see lib/API/DX/Device.cpp createTexture()) which uses the DSV-format + // mapping (D32_FLOAT) instead. + return DXGI_FORMAT_R32_FLOAT; default: llvm_unreachable("Unsupported Resource format specified"); } diff --git a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml new file mode 100644 index 000000000..e42090e12 --- /dev/null +++ b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml @@ -0,0 +1,68 @@ +#--- source.hlsl +// Verifies that a Texture2D bound with a depth-compatible format +// (Depth32 in the YAML; VK_FORMAT_D32_SFLOAT on Vulkan, DXGI_FORMAT_R32_FLOAT +// SRV on DirectX) can be Load()ed by a compute shader and round-trip the +// expected single-channel float values. +// +// Tracking: https://github.com/llvm/offload-test-suite/issues/1046 + +[[vk::binding(0, 0)]] Texture2D Tex : register(t0); +[[vk::binding(1, 0)]] RWBuffer Out : register(u0); + +[numthreads(1, 1, 1)] +void main() { + Out[0] = Tex.Load(int3(0, 0, 0)); + Out[1] = Tex.Load(int3(1, 0, 0)); + Out[2] = Tex.Load(int3(0, 1, 0)); + Out[3] = Tex.Load(int3(1, 1, 0)); +} + +//--- pipeline.yaml +--- +Shaders: + - Stage: Compute + Entry: main + DispatchSize: [1, 1, 1] + +Buffers: + - Name: Tex + Format: Depth32 + Channels: 1 + OutputProps: { Width: 2, Height: 2, Depth: 1 } + Data: [ 0.2, + 0.8, + 0.4, + 0.6 ] + + - Name: Out + Format: Float32 + Channels: 1 + FillSize: 16 # 4 * sizeof(float) + + - Name: Expected + Format: Float32 + Channels: 1 + Data: [ 0.2, 0.8, 0.4, 0.6 ] + +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: LoadDepth32Test + Rule: BufferExact + Actual: Out + Expected: Expected +... +#--- end + +# 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 From 7cf03a1996684dd8bba814819ef46af71907a88e Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 17:39:43 -0700 Subject: [PATCH 02/12] [OffloadTest][DX][VK] Add SV_Depth pixel-shader output test (#1046) Adds end-to-end coverage for SV_Depth by introducing a new `Bindings.DepthBuffer` schema key that lets a test bind a CPU-readable depth attachment, draws four single-pixel triangles each writing a distinct exact-binary-fraction depth via SV_Depth, copies the depth target back to a host buffer, and compares element-by-element against the expected values on D3D12, WARP, and Vulkan. Schema/API plumbing ------------------- * `IOBindings` gains `DepthBuffer` (name) + `DepthBufferPtr` (resolved CPUBuffer*), mirroring the existing RenderTarget pattern, with YAML parsing and name resolution in `lib/Support/Pipeline.cpp`. * New cross-backend helper `createDepthBufferFromCPUBuffer` in `lib/API/Device.cpp` builds a D32_FLOAT DepthStencil texture from a Depth32/1-channel CPUBuffer and validates the texture description against the buffer. * `isFloatingPointFormat` now accepts `DataFormat::Depth32` so the existing `BufferFloatULP` comparator can be used for depth read-back, matching the existing `testBufferFloatULP` case mapping Depth32 to a float compare. DirectX backend (`lib/API/DX/Device.cpp`) ------------------------------------------- * `createDepthStencil` now branches on `Bindings.DepthBufferPtr`: when set it builds the depth target from the helper and allocates a paired `DSReadback` buffer; otherwise it falls back to the existing default depth target (no read back). * After the draw, the depth target is transitioned from DEPTH_WRITE to COPY_SOURCE and copied to `DSReadback` using a placed footprint whose format matches the resource's actual DXGI format (D32_FLOAT), not the SRV-cast (R32_FLOAT) used for shader reads. * `readBack` maps `DSReadback`, gets copyable footprints, and copies into the user's `DepthBufferPtr` via `copyFromTexture`. * PSO DSVFormat is now derived from the actual depth target (`State.DepthStencil->getDesc().Fmt`) instead of being hard-coded to D32_FLOAT_S8X24_UINT, so depth-only formats are valid here. Vulkan backend (`lib/API/VK/Device.cpp`) ------------------------------------------ * InvocationState gains the same `DSReadback` buffer. * `createDepthStencil` mirrors the DX branch. * After `endEncoding`, `copyTextureToReadback` is reused for the depth attachment with the depth aspect/layout/stage masks (`LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL`, `ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT`, `LATE_FRAGMENT_TESTS_BIT`). * `readBackData` maps and copies depth bytes into the `DepthBufferPtr` using its image row stride. Metal backend (`lib/API/MTL/MTLDevice.cpp`) --------------------------------------------- * `createDepthStencil` now fail-fasts with `not_supported` if a test binds `Bindings.DepthBuffer` so the next contributor knows to wire it up instead of getting silently undefined behavior. Test ---- * New `test/Feature/Semantics/SVDepth.test` runs on D3D12, WARP, and VK and verifies four per-pixel SV_Depth writes (0.125, 0.25, 0.375, 0.5) against a Depth32 read-back buffer with `BufferFloatULP` ULP 0. `XFAIL: Clang` because DXC's HLSL -> DXIL lowering of SV_Depth is not yet implemented in clang. * Fix a YAML-schema regression in the previously-committed `Texture2D.Load.Depth32.test.yaml`: `DispatchSize` -> top-level `DispatchParameters: { DispatchGroupCount: ... }` to track the upstream schema rename. SV_StencilRef is intentionally out of scope until a stencil-bearing `DataFormat` (e.g. D24S8) is added; `createDepthBufferFromCPUBuffer` deliberately uses `Format::D32Float`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/API/Device.h | 6 + include/Support/Pipeline.h | 9 ++ lib/API/DX/Device.cpp | 82 +++++++++++- lib/API/Device.cpp | 26 ++++ lib/API/MTL/MTLDevice.cpp | 4 + lib/API/VK/Device.cpp | 53 ++++++++ lib/Support/Pipeline.cpp | 10 +- test/Feature/Semantics/SVDepth.test | 124 ++++++++++++++++++ .../Textures/Texture2D.Load.Depth32.test.yaml | 4 +- 9 files changed, 315 insertions(+), 3 deletions(-) create mode 100644 test/Feature/Semantics/SVDepth.test diff --git a/include/API/Device.h b/include/API/Device.h index 104c32b60..71347cc0e 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -242,6 +242,12 @@ createRenderTargetFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); llvm::Expected> createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, uint32_t Height); +// Creates a depth texture using the format and dimensions from a CPUBuffer. +// The buffer's Format must be DataFormat::Depth32 with 1 channel. Does not +// upload data — only uses the description to configure the texture. +llvm::Expected> +createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); + llvm::Expected> createBufferWithData(Device &Dev, std::string Name, const BufferCreateDesc &Desc, const void *Data, diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index c13f54d2b..f09335cef 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -400,6 +400,15 @@ struct IOBindings { std::string RenderTarget; CPUBuffer *RTargetBufferPtr = nullptr; + + // Optional: a CPU-readable depth buffer attachment. When set, the graphics + // pipeline binds this buffer's storage as the depth target and copies the + // contents back after the draw. Required for testing SV_Depth* / SV_StencilRef + // pixel shader outputs. When unset, backends create an internal depth target + // that is not read back. + std::string DepthBuffer; + CPUBuffer *DepthBufferPtr = nullptr; + PrimitiveTopology Topology = PrimitiveTopology::TriangleList; uint32_t getVertexStride() const { diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index dd07dad4b..fa406b9bd 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -953,6 +953,10 @@ class DXDevice : public offloadtest::Device { std::unique_ptr RenderTarget; std::unique_ptr RTReadback; std::unique_ptr DepthStencil; + // Set only when the pipeline has a Bindings.DepthBuffer; the depth target + // contents are copied here after the draw so the test can verify + // SV_Depth* writes. + std::unique_ptr DSReadback; std::unique_ptr VB; llvm::SmallVector DescTables; @@ -2248,6 +2252,28 @@ class DXDevice : public offloadtest::Device { P.Bindings.RTargetBufferPtr->copyFromTexture(Mapped, Placed.Footprint.RowPitch); Readback.Buffer->Unmap(0, nullptr); + + if (IS.DSReadback) { + void *DSMapped = nullptr; + auto &DSReadback = llvm::cast(*IS.DSReadback); + if (auto Err = HR::toError(DSReadback.Buffer->Map(0, nullptr, &DSMapped), + "Failed to map depth buffer readback")) + return Err; + + auto &DS = llvm::cast(*IS.DepthStencil); + const D3D12_RESOURCE_DESC DSDesc = DS.Resource->GetDesc(); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT DSPlaced = {}; + uint32_t DSNumRows = 0; + uint64_t DSRowSizeInBytes = 0; + uint64_t DSTotalBytes = 0; + Device->GetCopyableFootprints(&DSDesc, 0u, 1u, 0u, &DSPlaced, &DSNumRows, + &DSRowSizeInBytes, &DSTotalBytes); + + P.Bindings.DepthBufferPtr->copyFromTexture(DSMapped, + DSPlaced.Footprint.RowPitch); + DSReadback.Buffer->Unmap(0, nullptr); + } + return llvm::Error::success(); } @@ -2281,6 +2307,28 @@ class DXDevice : public offloadtest::Device { } llvm::Error createDepthStencil(Pipeline &P, InvocationState &IS) { + // If the test bound a CPU-readable depth buffer, create the depth target + // from it and allocate a readback buffer. Otherwise fall back to the + // default depth target (which is not read back). + if (P.Bindings.DepthBufferPtr) { + const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; + auto TexOrErr = + offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + if (!TexOrErr) + return TexOrErr.takeError(); + IS.DepthStencil = std::move(*TexOrErr); + + BufferCreateDesc BufDesc = {}; + BufDesc.Location = MemoryLocation::GpuToCpu; + BufDesc.Usage = BufferUsage::Storage; + auto BufOrErr = createBuffer("DSReadback", BufDesc, + getAlignedTextureBufferSize(DSBuf)); + if (!BufOrErr) + return BufOrErr.takeError(); + IS.DSReadback = std::move(*BufOrErr); + return llvm::Error::success(); + } + auto TexOrErr = offloadtest::createDefaultDepthStencilTarget( *this, P.Bindings.RTargetBufferPtr->OutputProps.Width, P.Bindings.RTargetBufferPtr->OutputProps.Height); @@ -2366,6 +2414,35 @@ class DXDevice : public offloadtest::Device { IS.CB->CmdList->CopyTextureRegion(&DstLoc, 0, 0, 0, &SrcLoc, nullptr); + // If a depth buffer is bound for readback, transition the depth target + // from DEPTH_WRITE to COPY_SOURCE and copy its contents to the readback + // buffer using the depth-aspect placed footprint. + if (IS.DSReadback) { + auto &DSReadback = llvm::cast(*IS.DSReadback); + const D3D12_RESOURCE_BARRIER DSBarrier = + CD3DX12_RESOURCE_BARRIER::Transition( + DS.Resource.Get(), D3D12_RESOURCE_STATE_DEPTH_WRITE, + D3D12_RESOURCE_STATE_COPY_SOURCE); + IS.CB->CmdList->ResourceBarrier(1, &DSBarrier); + + const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; + // CopyTextureRegion requires the placed-footprint format to match the + // source resource format (D32_FLOAT for a depth target), not the + // shader-visible (R32_FLOAT) cast used for SRV reads. + const DXGI_FORMAT DSResFormat = DS.Resource->GetDesc().Format; + const D3D12_PLACED_SUBRESOURCE_FOOTPRINT DSFootprint{ + 0, + CD3DX12_SUBRESOURCE_FOOTPRINT(DSResFormat, DSBuf.OutputProps.Width, + DSBuf.OutputProps.Height, 1, + getAlignedTexturePitch( + DSBuf.OutputProps.Width, + DSBuf.getElementSize()))}; + const CD3DX12_TEXTURE_COPY_LOCATION DSDstLoc(DSReadback.Buffer.Get(), + DSFootprint); + const CD3DX12_TEXTURE_COPY_LOCATION DSSrcLoc(DS.Resource.Get(), 0); + IS.CB->CmdList->CopyTextureRegion(&DSDstLoc, 0, 0, 0, &DSSrcLoc, nullptr); + } + auto CopyBackResource = [&IS, this](ResourcePair &R) { if (R.first->isTexture()) { const offloadtest::CPUBuffer &B = *R.first->BufferPtr; @@ -2513,7 +2590,10 @@ class DXDevice : public offloadtest::Device { TraditionalRasterPipelineCreateDesc PipelineDesc = {}; PipelineDesc.Topology = P.Bindings.Topology; - PipelineDesc.DSFormat = Format::D32FloatS8Uint; + // Match the PSO depth-stencil format to the actual depth target the + // backend created (default: D32_FLOAT_S8X24_UINT; user-bound depth + // buffers may be a simpler depth-only format like D32_FLOAT). + PipelineDesc.DSFormat = State.DepthStencil->getDesc().Fmt; for (auto &Shader : P.Shaders) { ShaderContainer SC = {}; SC.EntryPoint = Shader.Entry; diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index 86875096e..d8e198b81 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -107,6 +107,32 @@ offloadtest::createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, return Dev.createTexture("DepthStencil", Desc); } +llvm::Expected> +offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, + const CPUBuffer &Buf) { + auto TexFmtOrErr = toFormat(Buf.Format, Buf.Channels); + if (!TexFmtOrErr) + return TexFmtOrErr.takeError(); + if (*TexFmtOrErr != Format::D32Float) + return llvm::createStringError( + std::errc::invalid_argument, + "Depth buffer binding requires DataFormat::Depth32 with 1 channel."); + + TextureCreateDesc Desc = {}; + Desc.Location = MemoryLocation::GpuOnly; + Desc.Usage = TextureUsage::DepthStencil; + Desc.Fmt = *TexFmtOrErr; + Desc.Width = Buf.OutputProps.Width; + Desc.Height = Buf.OutputProps.Height; + Desc.MipLevels = 1; + Desc.OptimizedClearValue = ClearDepthStencil{1.0f, 0}; + + if (auto Err = validateTextureDescMatchesCPUBuffer(Desc, Buf)) + return Err; + + return Dev.createTexture("DepthBuffer", Desc); +} + // This is a separate function because recursion is not allowed in this code // base. static llvm::Expected> diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index f591fe089..d66207494 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -1355,6 +1355,10 @@ class MTLDevice : public offloadtest::Device { } llvm::Error createDepthStencil(Pipeline &P, InvocationState &IS) { + if (P.Bindings.DepthBufferPtr) + return llvm::createStringError( + std::errc::not_supported, + "Bindings.DepthBuffer is not yet supported on the Metal backend."); auto TexOrErr = offloadtest::createDefaultDepthStencilTarget( *this, P.Bindings.RTargetBufferPtr->OutputProps.Width, P.Bindings.RTargetBufferPtr->OutputProps.Height); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index b26e66393..afc615bd6 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -1149,6 +1149,11 @@ class VulkanDevice : public offloadtest::Device { std::unique_ptr RenderTarget; std::unique_ptr RTReadback; std::unique_ptr DepthStencil; + // Optional CPU-readable readback buffer for the depth target. Only + // created when a test binds Bindings.DepthBuffer; the depth target + // contents are copied here after the draw so the test can verify + // SV_Depth* writes. + std::unique_ptr DSReadback; std::unique_ptr VB; uint32_t ShaderStageMask = 0; @@ -2262,6 +2267,27 @@ class VulkanDevice : public offloadtest::Device { } llvm::Error createDepthStencil(Pipeline &P, InvocationState &IS) { + // If the test bound a CPU-readable depth buffer, create the depth target + // from it and allocate a readback buffer. Otherwise fall back to the + // default depth target (which is not read back). + if (P.Bindings.DepthBufferPtr) { + const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; + auto TexOrErr = + offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + if (!TexOrErr) + return TexOrErr.takeError(); + IS.DepthStencil = std::move(*TexOrErr); + + BufferCreateDesc BufDesc = {}; + BufDesc.Location = MemoryLocation::GpuToCpu; + BufDesc.Usage = BufferUsage::Storage; + auto BufOrErr = createBuffer("DSReadback", BufDesc, DSBuf.size()); + if (!BufOrErr) + return BufOrErr.takeError(); + IS.DSReadback = std::move(*BufOrErr); + return llvm::Error::success(); + } + auto TexOrErr = offloadtest::createDefaultDepthStencilTarget( *this, P.Bindings.RTargetBufferPtr->OutputProps.Width, P.Bindings.RTargetBufferPtr->OutputProps.Height); @@ -3034,6 +3060,15 @@ class VulkanDevice : public offloadtest::Device { VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + + if (IS.DSReadback) { + copyTextureToReadback( + IS.CB->CmdBuffer, llvm::cast(*IS.DepthStencil), + llvm::cast(*IS.DSReadback), + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT); + } } for (auto &R : IS.Resources) @@ -3099,6 +3134,24 @@ class VulkanDevice : public offloadtest::Device { auto *RT = P.Bindings.RTargetBufferPtr; RT->copyFromTexture(Mapped, RT->getImageRowBytes()); vkUnmapMemory(Device, Readback.Memory); + + if (IS.DSReadback) { + auto &DSReadback = llvm::cast(*IS.DSReadback); + + VkMappedMemoryRange DSRange = {}; + DSRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + DSRange.offset = 0; + DSRange.size = VK_WHOLE_SIZE; + DSRange.memory = DSReadback.Memory; + + void *DSMapped = nullptr; // NOLINT(misc-const-correctness) + vkMapMemory(Device, DSReadback.Memory, 0, VK_WHOLE_SIZE, 0, &DSMapped); + vkInvalidateMappedMemoryRanges(Device, 1, &DSRange); + + auto *DSBuf = P.Bindings.DepthBufferPtr; + DSBuf->copyFromTexture(DSMapped, DSBuf->getImageRowBytes()); + vkUnmapMemory(Device, DSReadback.Memory); + } } return llvm::Error::success(); } diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 80ff1b03f..a31f3e06a 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -16,7 +16,7 @@ using namespace offloadtest; static bool isFloatingPointFormat(DataFormat Format) { return Format == DataFormat::Float16 || Format == DataFormat::Float32 || - Format == DataFormat::Float64; + Format == DataFormat::Float64 || Format == DataFormat::Depth32; } void PushConstantBlock::getContent( @@ -155,6 +155,13 @@ void MappingTraits::mapping(IO &I, I.setError(Twine("Referenced render target buffer ") + P.Bindings.RenderTarget + " not found!"); } + + if (!P.Bindings.DepthBuffer.empty()) { + P.Bindings.DepthBufferPtr = P.getBuffer(P.Bindings.DepthBuffer); + if (!P.Bindings.DepthBufferPtr) + I.setError(Twine("Referenced depth buffer ") + P.Bindings.DepthBuffer + + " not found!"); + } } } @@ -425,6 +432,7 @@ void MappingTraits::mapping( I.mapOptional("VertexBuffer", B.VertexBuffer); I.mapOptional("VertexAttributes", B.VertexAttributes); I.mapOptional("RenderTarget", B.RenderTarget); + I.mapOptional("DepthBuffer", B.DepthBuffer); I.mapOptional("Topology", B.Topology, offloadtest::PrimitiveTopology::TriangleList); } diff --git a/test/Feature/Semantics/SVDepth.test b/test/Feature/Semantics/SVDepth.test new file mode 100644 index 000000000..9d1defa29 --- /dev/null +++ b/test/Feature/Semantics/SVDepth.test @@ -0,0 +1,124 @@ +# This test exercises: +# * SV_Depth - Pixel shader output that overrides the rasterizer-interpolated +# depth and is written to the bound depth target. +# +# SV_Depth: https://github.com/llvm/wg-hlsl/issues/1046 +# Clang's HLSL -> DXIL lowering does not yet implement SV_Depth. + +#--- vertex.hlsl +struct VSInput { + float4 pos : POSITION; +}; + +struct VSOutput { + float4 position : SV_POSITION; +}; + +VSOutput main(VSInput input) { + VSOutput o; + o.position = input.pos; + return o; +} + +#--- pixel.hlsl +struct PSInput { + float4 position : SV_POSITION; +}; + +struct PSOutput { + float4 color : SV_TARGET; + float depth : SV_Depth; +}; + +// Four primitives, one per pixel of a 4x1 render target. Each primitive emits +// a distinct depth value via SV_Depth so the depth target read back to the +// CPU can be checked element-by-element. +// +// All emitted depths are exact binary fractions in [0, 1) and all are less +// than the clear value of 1.0, so they pass the default DepthFunc=LESS test. +PSOutput main(PSInput input, uint primID : SV_PrimitiveID) { + PSOutput o; + o.color = float4(1.0, 0.0, 0.0, 1.0); + float depths[4] = { 0.125, 0.250, 0.375, 0.5 }; + o.depth = depths[primID]; + return o; +} + +#--- pipeline.yaml +--- +Shaders: + - Stage: Vertex + Entry: main + - Stage: Pixel + Entry: main +Buffers: + # Geometry: 4 triangles, each covering exactly one pixel of a 4x1 render + # target. Pixel-center NDC coordinates are (-0.75, 0), (-0.25, 0), (+0.25, + # 0), (+0.75, 0). Shared edges fall at NDC x in {-0.5, 0, +0.5}, none of + # which coincide with a pixel center, so coverage is unambiguous. + - Name: VertexData + Format: Float32 + Stride: 16 + Data: [ + # Triangle 0 (CCW, pixel 0): + -1.0, -1.0, 0.0, 1.0, + 0.0, -1.0, 0.0, 1.0, + -1.0, 1.0, 0.0, 1.0, + # Triangle 1 (CCW, pixel 1): + 0.0, -1.0, 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + -1.0, 1.0, 0.0, 1.0, + # Triangle 2 (CW, pixel 2): + 0.0, -1.0, 0.0, 1.0, + 0.0, 1.0, 0.0, 1.0, + 1.0, -1.0, 0.0, 1.0, + # Triangle 3 (CW, pixel 3): + 0.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 0.0, 1.0, + 1.0, -1.0, 0.0, 1.0, + ] + - Name: RenderTarget + Format: Float32 + Channels: 4 + FillSize: 64 # 4x1 @ 16 bytes per pixel + OutputProps: + Height: 1 + Width: 4 + Depth: 1 + - Name: DepthTarget + Format: Depth32 + Channels: 1 + FillSize: 16 # 4x1 @ 4 bytes per pixel; contents are overwritten on clear + OutputProps: + Height: 1 + Width: 4 + Depth: 1 + - Name: DepthTarget_Expected + Format: Depth32 + Channels: 1 + Data: [ 0.125, 0.250, 0.375, 0.5 ] +Bindings: + VertexBuffer: VertexData + VertexAttributes: + - Format: Float32 + Channels: 4 + Offset: 0 + Name: POSITION + RenderTarget: RenderTarget + DepthBuffer: DepthTarget +DescriptorSets: [] +Results: + - Result: DepthValues + Rule: BufferFloatULP + ULPT: 0 + Actual: DepthTarget + Expected: DepthTarget_Expected +... +#--- end + +# XFAIL: Clang + +# RUN: split-file %s %t +# RUN: %dxc_target -T vs_6_0 -Fo %t-vertex.o %t/vertex.hlsl +# RUN: %dxc_target -T ps_6_0 -Fo %t-pixel.o %t/pixel.hlsl +# RUN: %offloader %t/pipeline.yaml %t-vertex.o %t-pixel.o diff --git a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml index e42090e12..c062a0458 100644 --- a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml +++ b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml @@ -22,7 +22,9 @@ void main() { Shaders: - Stage: Compute Entry: main - DispatchSize: [1, 1, 1] + +DispatchParameters: + DispatchGroupCount: [1, 1, 1] Buffers: - Name: Tex From 2dff94e5e4e9f3a4d6aa694548bbb5b45b065812 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 18:26:50 -0700 Subject: [PATCH 03/12] [NFC] clang-format Apply clang-format 19.1.6 to changed regions per pr-code-format CI. --- include/Support/Pipeline.h | 6 +++--- lib/API/DX/Device.cpp | 12 +++++------- lib/API/Device.cpp | 3 +-- lib/API/VK/Device.cpp | 15 +++++++-------- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index f09335cef..e6625031d 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -403,9 +403,9 @@ struct IOBindings { // Optional: a CPU-readable depth buffer attachment. When set, the graphics // pipeline binds this buffer's storage as the depth target and copies the - // contents back after the draw. Required for testing SV_Depth* / SV_StencilRef - // pixel shader outputs. When unset, backends create an internal depth target - // that is not read back. + // contents back after the draw. Required for testing SV_Depth* / + // SV_StencilRef pixel shader outputs. When unset, backends create an internal + // depth target that is not read back. std::string DepthBuffer; CPUBuffer *DepthBufferPtr = nullptr; diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index fa406b9bd..8b2820cf4 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2312,8 +2312,7 @@ class DXDevice : public offloadtest::Device { // default depth target (which is not read back). if (P.Bindings.DepthBufferPtr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; - auto TexOrErr = - offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -2432,11 +2431,10 @@ class DXDevice : public offloadtest::Device { const DXGI_FORMAT DSResFormat = DS.Resource->GetDesc().Format; const D3D12_PLACED_SUBRESOURCE_FOOTPRINT DSFootprint{ 0, - CD3DX12_SUBRESOURCE_FOOTPRINT(DSResFormat, DSBuf.OutputProps.Width, - DSBuf.OutputProps.Height, 1, - getAlignedTexturePitch( - DSBuf.OutputProps.Width, - DSBuf.getElementSize()))}; + CD3DX12_SUBRESOURCE_FOOTPRINT( + DSResFormat, DSBuf.OutputProps.Width, DSBuf.OutputProps.Height, 1, + getAlignedTexturePitch(DSBuf.OutputProps.Width, + DSBuf.getElementSize()))}; const CD3DX12_TEXTURE_COPY_LOCATION DSDstLoc(DSReadback.Buffer.Get(), DSFootprint); const CD3DX12_TEXTURE_COPY_LOCATION DSSrcLoc(DS.Resource.Get(), 0); diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index d8e198b81..e78129d38 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -108,8 +108,7 @@ offloadtest::createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, } llvm::Expected> -offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, - const CPUBuffer &Buf) { +offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf) { auto TexFmtOrErr = toFormat(Buf.Format, Buf.Channels); if (!TexFmtOrErr) return TexFmtOrErr.takeError(); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index afc615bd6..1a68111ea 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -2272,8 +2272,7 @@ class VulkanDevice : public offloadtest::Device { // default depth target (which is not read back). if (P.Bindings.DepthBufferPtr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; - auto TexOrErr = - offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -3062,12 +3061,12 @@ class VulkanDevice : public offloadtest::Device { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); if (IS.DSReadback) { - copyTextureToReadback( - IS.CB->CmdBuffer, llvm::cast(*IS.DepthStencil), - llvm::cast(*IS.DSReadback), - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT); + copyTextureToReadback(IS.CB->CmdBuffer, + llvm::cast(*IS.DepthStencil), + llvm::cast(*IS.DSReadback), + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT); } } From fde7afa5714a7ffcf8ad79c33807753fae27cae5 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 19:44:34 -0700 Subject: [PATCH 04/12] [test] Mark Texture2D.Load.Depth32 UNSUPPORTED on Metal The Metal offload backend cannot bind a Depth32 texture as an SRV through this path (rejects with Metal does not support buffer robustness). Mark the test UNSUPPORTED on Metal until #1046 grows a Metal story; D3D12 and Vulkan continue to PASS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/Feature/Textures/Texture2D.Load.Depth32.test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml index c062a0458..54529431a 100644 --- a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml +++ b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml @@ -68,3 +68,8 @@ Results: # 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 + +# Metal's offload backend cannot bind a depth-format texture as an SRV +# through this code path (hits "Metal does not support buffer robustness" +# in the MTL backend). Mark unsupported until #1046 has a Metal story. +# UNSUPPORTED: Metal From 63728960c6703a27ca8093656ea643bc5aa0eefd Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 19:53:24 -0700 Subject: [PATCH 05/12] [test] Mark SVDepth UNSUPPORTED on Metal Mac MTL CI reports `Bindings.DepthBuffer is not yet supported on the Metal backend.` for `Feature/Semantics/SVDepth.test`. The Metal device backend has no handling for the `DepthBuffer` binding introduced alongside this test, so mark it `UNSUPPORTED: Metal` until the MTL implementation lands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/Feature/Semantics/SVDepth.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Feature/Semantics/SVDepth.test b/test/Feature/Semantics/SVDepth.test index 9d1defa29..133eed3b4 100644 --- a/test/Feature/Semantics/SVDepth.test +++ b/test/Feature/Semantics/SVDepth.test @@ -117,6 +117,8 @@ Results: #--- end # XFAIL: Clang +# Metal backend doesn't yet support Bindings.DepthBuffer. +# UNSUPPORTED: Metal # RUN: split-file %s %t # RUN: %dxc_target -T vs_6_0 -Fo %t-vertex.o %t/vertex.hlsl From b2306cc981f1650f50826c04dfd3e4a21d71ad77 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Wed, 27 May 2026 13:40:34 -0700 Subject: [PATCH 06/12] [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 bdcc7fcde..07ee42722 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2454,7 +2454,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); @@ -2467,7 +2467,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 a7b1b16fde2b5630237cce62fb83a164cc78b0bf Mon Sep 17 00:00:00 2001 From: alsepkow Date: Fri, 29 May 2026 17:41:38 -0700 Subject: [PATCH 07/12] Trim verbose comments on depth-buffer paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/API/Device.h | 4 +--- include/Support/Pipeline.h | 7 ++----- lib/API/DX/Device.cpp | 14 +++----------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/include/API/Device.h b/include/API/Device.h index 3f4635604..e263b78f7 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -255,9 +255,7 @@ createRenderTargetFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); llvm::Expected> createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, uint32_t Height); -// Creates a depth texture using the format and dimensions from a CPUBuffer. -// The buffer's Format must be DataFormat::Depth32 with 1 channel. Does not -// upload data — only uses the description to configure the texture. +// Creates a depth texture from a CPUBuffer description; does not upload data. llvm::Expected> createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 96ac42bad..50ca59aa6 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -421,11 +421,8 @@ struct IOBindings { std::string RenderTarget; CPUBuffer *RTargetBufferPtr = nullptr; - // Optional: a CPU-readable depth buffer attachment. When set, the graphics - // pipeline binds this buffer's storage as the depth target and copies the - // contents back after the draw. Required for testing SV_Depth* / - // SV_StencilRef pixel shader outputs. When unset, backends create an internal - // depth target that is not read back. + // Optional depth target bound for readback; when unset, backends create an + // internal depth target. std::string DepthBuffer; CPUBuffer *DepthBufferPtr = nullptr; diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index e5dc5f1ab..1b0c00550 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -122,11 +122,6 @@ static DXGI_FORMAT getDXFormat(DataFormat Format, int Channels) { case DataFormat::Depth32: if (Channels != 1) llvm_unreachable("Depth32 format only supports a single channel."); - // For user-bound resources (SRV/UAV), expose the depth-compatible - // typeless component as R32_FLOAT so shaders can Load()/Sample() the - // depth values directly. Depth-stencil view paths use getDXGIFormat() - // (see lib/API/DX/Device.cpp createTexture()) which uses the DSV-format - // mapping (D32_FLOAT) instead. return DXGI_FORMAT_R32_FLOAT; default: llvm_unreachable("Unsupported Resource format specified"); @@ -987,9 +982,7 @@ class DXDevice : public offloadtest::Device { std::unique_ptr RenderTarget; std::unique_ptr RTReadback; std::unique_ptr DepthStencil; - // Set only when the pipeline has a Bindings.DepthBuffer; the depth target - // contents are copied here after the draw so the test can verify - // SV_Depth* writes. + // Populated when Bindings.DepthBuffer is set, for SV_Depth verification. std::unique_ptr DSReadback; std::unique_ptr VB; @@ -2452,9 +2445,8 @@ class DXDevice : public offloadtest::Device { IS.CB->CmdList->ResourceBarrier(1, &DSBarrier); const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; - // CopyTextureRegion requires the placed-footprint format to match the - // source resource format (D32_FLOAT for a depth target), not the - // shader-visible (R32_FLOAT) cast used for SRV reads. + // CopyTextureRegion footprint format must match the source resource + // (D32_FLOAT), not the shader-visible R32_FLOAT SRV cast. const DXGI_FORMAT DSResFormat = DS.Resource->GetDesc().Format; const D3D12_PLACED_SUBRESOURCE_FOOTPRINT DSFootprint{ 0, From 093258228c185e4a4989bd205d6edc8f2ca55fc0 Mon Sep 17 00:00:00 2001 From: alsepkow Date: Wed, 3 Jun 2026 16:17:10 -0700 Subject: [PATCH 08/12] [API][DX][VK] Use Format enum for depth-buffer YAML binding Address review feedback on the SV_Depth path: the YAML depth-buffer binding parsed its format from a CPUBuffer's DataFormat+Channels and then errored out unless that resolved to Format::D32Float, which was a needless round-trip through the transitional toFormat() bridge and hard-coded a single accepted format. Take the format directly from the YAML instead. Bindings.DepthBuffer is now a struct rather than a bare buffer name: DepthBuffer: Name: DepthTarget Format: D32Float # optional; defaults to D32Float createDepthBufferFromCPUBuffer takes the Format as an argument, drops the toFormat() call, and validates with isDepthFormat() so depth-stencil formats like D32FloatS8Uint can be plumbed through once a backend wires them up. The CPUBuffer remains the readback destination and is no longer used as the format source; a new validateTextureDimsMatchCPUBuffer helper checks width/height/size without re-deriving Format via toFormat(). DX, VK, and MTL backends, the IOBindings YAML mapping, and the SVDepth lit test are updated for the new binding shape. SVDepth.test still passes on D3D12 and Vulkan. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/API/Device.h | 6 ++++- include/API/FormatConversion.h | 38 +++++++++++++++++++++++++++++ include/Support/Pipeline.h | 22 ++++++++++++++--- lib/API/DX/Device.cpp | 13 +++++----- lib/API/Device.cpp | 18 ++++++++------ lib/API/MTL/MTLDevice.cpp | 2 +- lib/API/VK/Device.cpp | 9 ++++--- lib/Support/Pipeline.cpp | 14 ++++++++--- test/Feature/Semantics/SVDepth.test | 4 ++- 9 files changed, 97 insertions(+), 29 deletions(-) diff --git a/include/API/Device.h b/include/API/Device.h index e263b78f7..00e639e8e 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -256,8 +256,12 @@ llvm::Expected> createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, uint32_t Height); // Creates a depth texture from a CPUBuffer description; does not upload data. +// Fmt names the GPU texture format directly (sourced from the YAML +// Bindings.DepthBuffer block) so the depth path doesn't have to round-trip +// through DataFormat + Channels and can name depth-stencil formats like +// D32FloatS8Uint that aren't expressible in DataFormat. llvm::Expected> -createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); +createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf, Format Fmt); llvm::Expected> createBufferWithData(Device &Dev, std::string Name, diff --git a/include/API/FormatConversion.h b/include/API/FormatConversion.h index 816705389..024d16846 100644 --- a/include/API/FormatConversion.h +++ b/include/API/FormatConversion.h @@ -156,6 +156,44 @@ validateTextureDescMatchesCPUBuffer(const TextureCreateDesc &Desc, return llvm::Error::success(); } +// Validates that a TextureCreateDesc's dimensions and texel-byte footprint +// match the CPUBuffer it will be read back into. Unlike +// validateTextureDescMatchesCPUBuffer, this does not assert that Desc.Fmt is +// equivalent to (Buf.Format + Buf.Channels) via toFormat -- callers that source +// Desc.Fmt directly from the YAML (e.g. the depth-buffer binding) use the +// CPUBuffer purely as readback storage and may use formats that don't have a +// DataFormat equivalent (such as D32FloatS8Uint). +inline llvm::Error +validateTextureDimsMatchCPUBuffer(const TextureCreateDesc &Desc, + const CPUBuffer &Buf) { + if (Desc.Width != static_cast(Buf.OutputProps.Width)) + return llvm::createStringError( + std::errc::invalid_argument, + "TextureCreateDesc width %u does not match CPUBuffer width %d.", + Desc.Width, Buf.OutputProps.Width); + if (Desc.Height != static_cast(Buf.OutputProps.Height)) + return llvm::createStringError( + std::errc::invalid_argument, + "TextureCreateDesc height %u does not match CPUBuffer height %d.", + Desc.Height, Buf.OutputProps.Height); + if (Desc.MipLevels != static_cast(Buf.OutputProps.MipLevels)) + return llvm::createStringError( + std::errc::invalid_argument, + "TextureCreateDesc mip levels %u does not match CPUBuffer mip " + "levels %d.", + Desc.MipLevels, Buf.OutputProps.MipLevels); + const uint32_t TexelSize = getFormatSizeInBytes(Desc.Fmt); + const uint64_t ExpectedSize = + static_cast(Desc.Width) * Desc.Height * TexelSize; + if (static_cast(Buf.size()) != ExpectedSize) + return llvm::createStringError( + std::errc::invalid_argument, + "CPUBuffer size %u does not match expected size %llu " + "(width %u * height %u * element size %u).", + Buf.size(), ExpectedSize, Desc.Width, Desc.Height, TexelSize); + return llvm::Error::success(); +} + } // namespace offloadtest #endif // OFFLOADTEST_API_FORMATCONVERSION_H diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 50ca59aa6..1fe248129 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -421,10 +421,20 @@ struct IOBindings { std::string RenderTarget; CPUBuffer *RTargetBufferPtr = nullptr; - // Optional depth target bound for readback; when unset, backends create an - // internal depth target. - std::string DepthBuffer; - CPUBuffer *DepthBufferPtr = nullptr; + // Optional depth target bound for readback; when Name is empty, backends + // create an internal depth target. Fmt names the GPU texture format + // explicitly so the YAML doesn't have to round-trip through + // (DataFormat + Channels) and so depth-stencil formats like D32FloatS8Uint + // can be selected directly. Ptr is resolved after parsing to the named + // CPUBuffer entry that owns the readback storage. + struct DepthBufferBinding { + std::string Name; + Format Fmt = Format::D32Float; + CPUBuffer *Ptr = nullptr; + + bool empty() const { return Name.empty(); } + }; + DepthBufferBinding DepthBuffer; PrimitiveTopology Topology = PrimitiveTopology::TriangleList; @@ -673,6 +683,10 @@ template <> struct MappingTraits { static void mapping(IO &I, offloadtest::IOBindings &B); }; +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::IOBindings::DepthBufferBinding &B); +}; + template <> struct MappingTraits { static void mapping(IO &I, offloadtest::PushConstantValue &B); }; diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 1b0c00550..a236dc540 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2289,8 +2289,8 @@ class DXDevice : public offloadtest::Device { Device->GetCopyableFootprints(&DSDesc, 0u, 1u, 0u, &DSPlaced, &DSNumRows, &DSRowSizeInBytes, &DSTotalBytes); - P.Bindings.DepthBufferPtr->copyFromTexture(DSMapped, - DSPlaced.Footprint.RowPitch); + P.Bindings.DepthBuffer.Ptr->copyFromTexture(DSMapped, + DSPlaced.Footprint.RowPitch); DSReadback.Buffer->Unmap(0, nullptr); } @@ -2330,9 +2330,10 @@ class DXDevice : public offloadtest::Device { // If the test bound a CPU-readable depth buffer, create the depth target // from it and allocate a readback buffer. Otherwise fall back to the // default depth target (which is not read back). - if (P.Bindings.DepthBufferPtr) { - const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; - auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + if (P.Bindings.DepthBuffer.Ptr) { + const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( + *this, DSBuf, P.Bindings.DepthBuffer.Fmt); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -2444,7 +2445,7 @@ class DXDevice : public offloadtest::Device { D3D12_RESOURCE_STATE_COPY_SOURCE); IS.CB->CmdList->ResourceBarrier(1, &DSBarrier); - const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; + const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; // CopyTextureRegion footprint format must match the source resource // (D32_FLOAT), not the shader-visible R32_FLOAT SRV cast. const DXGI_FORMAT DSResFormat = DS.Resource->GetDesc().Format; diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index e78129d38..84c796f40 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -108,25 +108,27 @@ offloadtest::createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, } llvm::Expected> -offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf) { - auto TexFmtOrErr = toFormat(Buf.Format, Buf.Channels); - if (!TexFmtOrErr) - return TexFmtOrErr.takeError(); - if (*TexFmtOrErr != Format::D32Float) +offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf, + Format Fmt) { + if (!isDepthFormat(Fmt)) return llvm::createStringError( std::errc::invalid_argument, - "Depth buffer binding requires DataFormat::Depth32 with 1 channel."); + "Depth buffer binding requires a depth format; got '%s'.", + getFormatName(Fmt).data()); TextureCreateDesc Desc = {}; Desc.Location = MemoryLocation::GpuOnly; Desc.Usage = TextureUsage::DepthStencil; - Desc.Fmt = *TexFmtOrErr; + Desc.Fmt = Fmt; Desc.Width = Buf.OutputProps.Width; Desc.Height = Buf.OutputProps.Height; Desc.MipLevels = 1; Desc.OptimizedClearValue = ClearDepthStencil{1.0f, 0}; - if (auto Err = validateTextureDescMatchesCPUBuffer(Desc, Buf)) + // The CPUBuffer here is the readback destination, not a format source. Skip + // the toFormat-based consistency check (which can't express depth-stencil + // formats) and validate only that the readback buffer is sized to match. + if (auto Err = validateTextureDimsMatchCPUBuffer(Desc, Buf)) return Err; return Dev.createTexture("DepthBuffer", Desc); diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index 1f649d739..1621055d3 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -1371,7 +1371,7 @@ class MTLDevice : public offloadtest::Device { } llvm::Error createDepthStencil(Pipeline &P, InvocationState &IS) { - if (P.Bindings.DepthBufferPtr) + if (P.Bindings.DepthBuffer.Ptr) return llvm::createStringError( std::errc::not_supported, "Bindings.DepthBuffer is not yet supported on the Metal backend."); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index 53d3f999e..4d853d9ed 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -2659,9 +2659,10 @@ class VulkanDevice : public offloadtest::Device { // If the test bound a CPU-readable depth buffer, create the depth target // from it and allocate a readback buffer. Otherwise fall back to the // default depth target (which is not read back). - if (P.Bindings.DepthBufferPtr) { - const CPUBuffer &DSBuf = *P.Bindings.DepthBufferPtr; - auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); + if (P.Bindings.DepthBuffer.Ptr) { + const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( + *this, DSBuf, P.Bindings.DepthBuffer.Fmt); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -3543,7 +3544,7 @@ class VulkanDevice : public offloadtest::Device { vkMapMemory(Device, DSReadback.Memory, 0, VK_WHOLE_SIZE, 0, &DSMapped); vkInvalidateMappedMemoryRanges(Device, 1, &DSRange); - auto *DSBuf = P.Bindings.DepthBufferPtr; + auto *DSBuf = P.Bindings.DepthBuffer.Ptr; DSBuf->copyFromTexture(DSMapped, DSBuf->getImageRowBytes()); vkUnmapMemory(Device, DSReadback.Memory); } diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 343910754..59e1ff0d4 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -166,10 +166,10 @@ void MappingTraits::mapping(IO &I, } if (!P.Bindings.DepthBuffer.empty()) { - P.Bindings.DepthBufferPtr = P.getBuffer(P.Bindings.DepthBuffer); - if (!P.Bindings.DepthBufferPtr) - I.setError(Twine("Referenced depth buffer ") + P.Bindings.DepthBuffer + - " not found!"); + P.Bindings.DepthBuffer.Ptr = P.getBuffer(P.Bindings.DepthBuffer.Name); + if (!P.Bindings.DepthBuffer.Ptr) + I.setError(Twine("Referenced depth buffer ") + + P.Bindings.DepthBuffer.Name + " not found!"); } // Resolve buffer name references in acceleration structure descriptions. @@ -486,6 +486,12 @@ void MappingTraits::mapping( I.mapOptional("PatchControlPoints", B.PatchControlPoints); } +void MappingTraits::mapping( + IO &I, offloadtest::IOBindings::DepthBufferBinding &B) { + I.mapRequired("Name", B.Name); + I.mapOptional("Format", B.Fmt, offloadtest::Format::D32Float); +} + void MappingTraits::mapping( IO &I, offloadtest::PushConstantBlock &B) { I.mapRequired("Stage", B.Stage); diff --git a/test/Feature/Semantics/SVDepth.test b/test/Feature/Semantics/SVDepth.test index 133eed3b4..e995382ce 100644 --- a/test/Feature/Semantics/SVDepth.test +++ b/test/Feature/Semantics/SVDepth.test @@ -105,7 +105,9 @@ Bindings: Offset: 0 Name: POSITION RenderTarget: RenderTarget - DepthBuffer: DepthTarget + DepthBuffer: + Name: DepthTarget + Format: D32Float DescriptorSets: [] Results: - Result: DepthValues From 5e9a62391111d500b8f4c878edcd9de51409c038 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Mon, 15 Jun 2026 18:47:31 -0700 Subject: [PATCH 09/12] Cleanup --- include/API/Device.h | 8 +--- include/API/FormatConversion.h | 19 +++------ include/Support/Pipeline.h | 16 ++++--- lib/API/DX/Device.cpp | 7 +--- lib/API/Device.cpp | 14 +++---- lib/API/VK/Device.cpp | 42 +++++++++---------- lib/Support/Check.cpp | 8 +--- lib/Support/Pipeline.cpp | 9 +--- test/Feature/Semantics/SVDepth.test | 6 +-- .../Textures/Texture2D.GatherCmp.test.yaml | 3 +- .../Textures/Texture2D.Load.Depth32.test.yaml | 9 ++-- .../Textures/Texture2D.SampleCmp.test.yaml | 3 +- .../Vk.SampledTexture2D.GatherCmp.test.yaml | 9 ++-- .../Vk.SampledTexture2D.SampleCmp.test.yaml | 6 ++- 14 files changed, 68 insertions(+), 91 deletions(-) diff --git a/include/API/Device.h b/include/API/Device.h index 00e639e8e..0c44d0e04 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -255,13 +255,9 @@ createRenderTargetFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); llvm::Expected> createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, uint32_t Height); -// Creates a depth texture from a CPUBuffer description; does not upload data. -// Fmt names the GPU texture format directly (sourced from the YAML -// Bindings.DepthBuffer block) so the depth path doesn't have to round-trip -// through DataFormat + Channels and can name depth-stencil formats like -// D32FloatS8Uint that aren't expressible in DataFormat. +// Creates a depth texture from a CPUBuffer whose GpuFormat is a depth format. llvm::Expected> -createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf, Format Fmt); +createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf); llvm::Expected> createBufferWithData(Device &Dev, std::string Name, diff --git a/include/API/FormatConversion.h b/include/API/FormatConversion.h index 024d16846..455278ff3 100644 --- a/include/API/FormatConversion.h +++ b/include/API/FormatConversion.h @@ -80,13 +80,6 @@ inline llvm::Expected toFormat(DataFormat Format, int Channels) { return Format::RGBA32Float; } break; - case DataFormat::Depth32: - // D32FloatS8Uint is not expressible as DataFormat + Channels because the - // stencil component is uint8, not a second Depth32 channel. Once the - // pipeline uses Format directly, this limitation goes away. - if (Channels == 1) - return Format::D32Float; - break; // No Format mapping for these DataFormats. case DataFormat::Hex8: case DataFormat::Hex16: @@ -156,13 +149,11 @@ validateTextureDescMatchesCPUBuffer(const TextureCreateDesc &Desc, return llvm::Error::success(); } -// Validates that a TextureCreateDesc's dimensions and texel-byte footprint -// match the CPUBuffer it will be read back into. Unlike -// validateTextureDescMatchesCPUBuffer, this does not assert that Desc.Fmt is -// equivalent to (Buf.Format + Buf.Channels) via toFormat -- callers that source -// Desc.Fmt directly from the YAML (e.g. the depth-buffer binding) use the -// CPUBuffer purely as readback storage and may use formats that don't have a -// DataFormat equivalent (such as D32FloatS8Uint). +// Validates that a TextureCreateDesc's dimensions and footprint are consistent +// with the CPUBuffer used for readback storage. Call this when format +// equivalence is not derived from DataFormat and Channels. +// This helper intentionally skips the toFormat-based format check. +// In that path, Desc.Fmt is set directly from GpuFormat. inline llvm::Error validateTextureDimsMatchCPUBuffer(const TextureCreateDesc &Desc, const CPUBuffer &Buf) { diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 1fe248129..6c00d7e06 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -69,7 +69,6 @@ enum class DataFormat { Float16, Float32, Float64, - Depth32, Bool, }; @@ -160,7 +159,6 @@ static inline uint32_t getFormatSize(DataFormat Format) { case DataFormat::UInt32: case DataFormat::Int32: case DataFormat::Float32: - case DataFormat::Depth32: case DataFormat::Bool: return 4; case DataFormat::Hex64: @@ -178,6 +176,10 @@ struct CPUBuffer { int Channels; int Stride; uint32_t ArraySize; + // When set, names the GPU texture format directly (e.g. D32Float) instead of + // inferring it from DataFormat + Channels via toFormat(). This lets depth + // buffers and other special formats be expressed without extending DataFormat. + std::optional GpuFormat; // Data can contain one block of data for a singular resource // or multiple blocks for a resource array. llvm::SmallVector> Data; @@ -422,14 +424,11 @@ struct IOBindings { CPUBuffer *RTargetBufferPtr = nullptr; // Optional depth target bound for readback; when Name is empty, backends - // create an internal depth target. Fmt names the GPU texture format - // explicitly so the YAML doesn't have to round-trip through - // (DataFormat + Channels) and so depth-stencil formats like D32FloatS8Uint - // can be selected directly. Ptr is resolved after parsing to the named - // CPUBuffer entry that owns the readback storage. + // create an internal depth target. Ptr is resolved after parsing to the + // named CPUBuffer entry that owns the readback storage; the GPU format for + // the depth texture comes from the buffer's GpuFormat field. struct DepthBufferBinding { std::string Name; - Format Fmt = Format::D32Float; CPUBuffer *Ptr = nullptr; bool empty() const { return Name.empty(); } @@ -836,7 +835,6 @@ template <> struct ScalarEnumerationTraits { ENUM_CASE(Float16); ENUM_CASE(Float32); ENUM_CASE(Float64); - ENUM_CASE(Depth32); ENUM_CASE(Bool); #undef ENUM_CASE } diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index a236dc540..f54a0352f 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -119,10 +119,6 @@ static DXGI_FORMAT getDXFormat(DataFormat Format, int Channels) { if (Channels == 2) return DXGI_FORMAT_R32G32B32A32_UINT; llvm_unreachable("Unsupported channel count for 64-bit format"); - case DataFormat::Depth32: - if (Channels != 1) - llvm_unreachable("Depth32 format only supports a single channel."); - return DXGI_FORMAT_R32_FLOAT; default: llvm_unreachable("Unsupported Resource format specified"); } @@ -982,7 +978,6 @@ class DXDevice : public offloadtest::Device { std::unique_ptr RenderTarget; std::unique_ptr RTReadback; std::unique_ptr DepthStencil; - // Populated when Bindings.DepthBuffer is set, for SV_Depth verification. std::unique_ptr DSReadback; std::unique_ptr VB; @@ -2333,7 +2328,7 @@ class DXDevice : public offloadtest::Device { if (P.Bindings.DepthBuffer.Ptr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( - *this, DSBuf, P.Bindings.DepthBuffer.Fmt); + *this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index 84c796f40..7adb640fe 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -108,13 +108,14 @@ offloadtest::createDefaultDepthStencilTarget(Device &Dev, uint32_t Width, } llvm::Expected> -offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf, - Format Fmt) { - if (!isDepthFormat(Fmt)) +offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf) { + if (!Buf.GpuFormat || !isDepthFormat(*Buf.GpuFormat)) return llvm::createStringError( std::errc::invalid_argument, - "Depth buffer binding requires a depth format; got '%s'.", - getFormatName(Fmt).data()); + "Depth buffer requires a CPUBuffer with a depth GpuFormat; got '%s'.", + Buf.GpuFormat ? getFormatName(*Buf.GpuFormat).data() : ""); + + Format Fmt = *Buf.GpuFormat; TextureCreateDesc Desc = {}; Desc.Location = MemoryLocation::GpuOnly; @@ -125,9 +126,6 @@ offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf, Desc.MipLevels = 1; Desc.OptimizedClearValue = ClearDepthStencil{1.0f, 0}; - // The CPUBuffer here is the readback destination, not a format source. Skip - // the toFormat-based consistency check (which can't express depth-stencil - // formats) and validate only that the readback buffer is sized to match. if (auto Err = validateTextureDimsMatchCPUBuffer(Desc, Buf)) return Err; diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index 4d853d9ed..2d0192f9a 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -59,10 +59,6 @@ static VkFormat getVKFormat(DataFormat Format, int Channels) { VKFormats(UINT, 64) break; case DataFormat::Float64: VKFormats(SFLOAT, 64) break; - case DataFormat::Depth32: - if (Channels != 1) - llvm_unreachable("Depth32 format only supports a single channel."); - return VK_FORMAT_D32_SFLOAT; default: llvm_unreachable("Unsupported Resource format specified"); } @@ -1186,10 +1182,6 @@ class VulkanDevice : public offloadtest::Device { std::unique_ptr RenderTarget; std::unique_ptr RTReadback; std::unique_ptr DepthStencil; - // Optional CPU-readable readback buffer for the depth target. Only - // created when a test binds Bindings.DepthBuffer; the depth target - // contents are copied here after the draw so the test can verify - // SV_Depth* writes. std::unique_ptr DSReadback; std::unique_ptr VB; @@ -2470,13 +2462,16 @@ class VulkanDevice : public offloadtest::Device { llvm::Expected createImage(Resource &R, BufferRef &Host, int UsageOverride = 0) { const offloadtest::CPUBuffer &B = *R.BufferPtr; - if (B.Format == DataFormat::Depth32 && R.isReadWrite()) + const bool IsDepth = + B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); + if (IsDepth && R.isReadWrite()) return llvm::createStringError(std::errc::invalid_argument, "Image memory allocation failed."); VkImageCreateInfo ImageCreateInfo = {}; ImageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; ImageCreateInfo.imageType = getVKImageType(R.Kind); - ImageCreateInfo.format = getVKFormat(B.Format, B.Channels); + ImageCreateInfo.format = B.GpuFormat ? getVulkanFormat(*B.GpuFormat) + : getVKFormat(B.Format, B.Channels); ImageCreateInfo.mipLevels = B.OutputProps.MipLevels; ImageCreateInfo.arrayLayers = 1; ImageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; @@ -2662,7 +2657,7 @@ class VulkanDevice : public offloadtest::Device { if (P.Bindings.DepthBuffer.Ptr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( - *this, DSBuf, P.Bindings.DepthBuffer.Fmt); + *this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -2848,12 +2843,15 @@ class VulkanDevice : public offloadtest::Device { ViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; ViewCreateInfo.viewType = getImageViewType(R.Kind); ViewCreateInfo.format = - getVKFormat(R.BufferPtr->Format, R.BufferPtr->Channels); + R.BufferPtr->GpuFormat + ? getVulkanFormat(*R.BufferPtr->GpuFormat) + : getVKFormat(R.BufferPtr->Format, R.BufferPtr->Channels); ViewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; ViewCreateInfo.subresourceRange.aspectMask = - R.BufferPtr->Format == DataFormat::Depth32 + (R.BufferPtr->GpuFormat && + isDepthFormat(*R.BufferPtr->GpuFormat)) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; ViewCreateInfo.subresourceRange.baseMipLevel = 0; @@ -3111,11 +3109,13 @@ class VulkanDevice : public offloadtest::Device { return; if (R.isImage()) { const offloadtest::CPUBuffer &B = *R.BufferPtr; + const bool IsDepth = + B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); llvm::SmallVector Regions; uint64_t CurrentOffset = 0; for (int I = 0; I < B.OutputProps.MipLevels; ++I) { VkBufferImageCopy Region = {}; - Region.imageSubresource.aspectMask = B.Format == DataFormat::Depth32 + Region.imageSubresource.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; Region.imageSubresource.mipLevel = I; @@ -3135,9 +3135,8 @@ class VulkanDevice : public offloadtest::Device { } VkImageSubresourceRange SubRange = {}; - SubRange.aspectMask = B.Format == DataFormat::Depth32 - ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + SubRange.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; SubRange.baseMipLevel = 0; SubRange.levelCount = B.OutputProps.MipLevels; SubRange.layerCount = 1; @@ -3257,10 +3256,11 @@ class VulkanDevice : public offloadtest::Device { return; if (R.isImage()) { const offloadtest::CPUBuffer &B = *R.BufferPtr; + const bool IsDepth = + B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); VkImageSubresourceRange SubRange = {}; - SubRange.aspectMask = B.Format == DataFormat::Depth32 - ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + SubRange.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; SubRange.baseMipLevel = 0; SubRange.levelCount = B.OutputProps.MipLevels; SubRange.layerCount = 1; @@ -3287,7 +3287,7 @@ class VulkanDevice : public offloadtest::Device { uint64_t CurrentOffset = 0; for (int I = 0; I < B.OutputProps.MipLevels; ++I) { VkBufferImageCopy Region = {}; - Region.imageSubresource.aspectMask = B.Format == DataFormat::Depth32 + Region.imageSubresource.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; Region.imageSubresource.mipLevel = I; diff --git a/lib/Support/Check.cpp b/lib/Support/Check.cpp index b685fe2e9..3c92fe95e 100644 --- a/lib/Support/Check.cpp +++ b/lib/Support/Check.cpp @@ -214,7 +214,6 @@ testBufferFloat(std::function ComparisonFn, case offloadtest::DataFormat::Float64: return testAllArray(ComparisonFn, B1, B2); case offloadtest::DataFormat::Float32: - case offloadtest::DataFormat::Depth32: return testAllArray(ComparisonFn, B1, B2); case offloadtest::DataFormat::Float16: { return testAllArray(ComparisonFn, B1, B2); @@ -236,8 +235,7 @@ static bool testBufferFloatEpsilon(offloadtest::CPUBuffer *B1, }; return testBufferFloat(Fn, B1, B2); } - case offloadtest::DataFormat::Float32: - case offloadtest::DataFormat::Depth32: { + case offloadtest::DataFormat::Float32: { auto Fn = [Epsilon, DM](const float &FS, const float &FR) { return compareFloatEpsilon(FS, FR, (float)Epsilon, DM); }; @@ -266,8 +264,7 @@ static bool testBufferFloatULP(offloadtest::CPUBuffer *B1, }; return testBufferFloat(Fn, B1, B2); } - case offloadtest::DataFormat::Float32: - case offloadtest::DataFormat::Depth32: { + case offloadtest::DataFormat::Float32: { auto Fn = [ULPT, DM](const float &FS, const float &FR) { return compareFloatULP(FS, FR, ULPT, DM); }; @@ -365,7 +362,6 @@ static const std::string getBufferStr(offloadtest::CPUBuffer *B) { case DF::Float16: return formatBuffer(B); // assuming no native float16 case DF::Float32: - case DF::Depth32: return formatBuffer(B); case DF::Float64: return formatBuffer(B); diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 59e1ff0d4..9c5279267 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -16,7 +16,7 @@ using namespace offloadtest; static bool isFloatingPointFormat(DataFormat Format) { return Format == DataFormat::Float16 || Format == DataFormat::Float32 || - Format == DataFormat::Float64 || Format == DataFormat::Depth32; + Format == DataFormat::Float64; } void PushConstantBlock::getContent( @@ -366,6 +366,7 @@ void MappingTraits::mapping(IO &I, I.mapRequired("Name", B.Name); I.mapRequired("Format", B.Format); I.mapOptional("Channels", B.Channels, 1); + I.mapOptional("GpuFormat", B.GpuFormat); I.mapOptional("Stride", B.Stride, 0); I.mapOptional("ArraySize", B.ArraySize, 1); setCounters(I, B); @@ -410,9 +411,6 @@ void MappingTraits::mapping(IO &I, case DF::Float32: setData(I, B); break; - case DF::Depth32: - setData(I, B); - break; case DF::Float64: setData(I, B); break; @@ -489,7 +487,6 @@ void MappingTraits::mapping( void MappingTraits::mapping( IO &I, offloadtest::IOBindings::DepthBufferBinding &B) { I.mapRequired("Name", B.Name); - I.mapOptional("Format", B.Fmt, offloadtest::Format::D32Float); } void MappingTraits::mapping( @@ -538,8 +535,6 @@ void MappingTraits::mapping( return setData(I, B); // assuming no native float16 case DF::Float32: return setData(I, B); - case DF::Depth32: - return setData(I, B); case DF::Float64: return setData(I, B); case DF::Bool: diff --git a/test/Feature/Semantics/SVDepth.test b/test/Feature/Semantics/SVDepth.test index e995382ce..a316c3486 100644 --- a/test/Feature/Semantics/SVDepth.test +++ b/test/Feature/Semantics/SVDepth.test @@ -86,7 +86,8 @@ Buffers: Width: 4 Depth: 1 - Name: DepthTarget - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 FillSize: 16 # 4x1 @ 4 bytes per pixel; contents are overwritten on clear OutputProps: @@ -94,7 +95,7 @@ Buffers: Width: 4 Depth: 1 - Name: DepthTarget_Expected - Format: Depth32 + Format: Float32 Channels: 1 Data: [ 0.125, 0.250, 0.375, 0.5 ] Bindings: @@ -107,7 +108,6 @@ Bindings: RenderTarget: RenderTarget DepthBuffer: Name: DepthTarget - Format: D32Float DescriptorSets: [] Results: - Result: DepthValues diff --git a/test/Feature/Textures/Texture2D.GatherCmp.test.yaml b/test/Feature/Textures/Texture2D.GatherCmp.test.yaml index 9a950a6c8..416b141e8 100644 --- a/test/Feature/Textures/Texture2D.GatherCmp.test.yaml +++ b/test/Feature/Textures/Texture2D.GatherCmp.test.yaml @@ -68,7 +68,8 @@ Shaders: Buffers: - Name: Tex - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, # (0,0) R=0.2 diff --git a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml index 54529431a..e24eb700d 100644 --- a/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml +++ b/test/Feature/Textures/Texture2D.Load.Depth32.test.yaml @@ -1,8 +1,8 @@ #--- source.hlsl // Verifies that a Texture2D bound with a depth-compatible format -// (Depth32 in the YAML; VK_FORMAT_D32_SFLOAT on Vulkan, DXGI_FORMAT_R32_FLOAT -// SRV on DirectX) can be Load()ed by a compute shader and round-trip the -// expected single-channel float values. +// (GpuFormat: D32Float in the YAML; VK_FORMAT_D32_SFLOAT on Vulkan, +// DXGI_FORMAT_R32_FLOAT SRV on DirectX) can be Load()ed by a compute shader +// and round-trip the expected single-channel float values. // // Tracking: https://github.com/llvm/offload-test-suite/issues/1046 @@ -28,7 +28,8 @@ DispatchParameters: Buffers: - Name: Tex - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, diff --git a/test/Feature/Textures/Texture2D.SampleCmp.test.yaml b/test/Feature/Textures/Texture2D.SampleCmp.test.yaml index 52a06c512..2de485fec 100644 --- a/test/Feature/Textures/Texture2D.SampleCmp.test.yaml +++ b/test/Feature/Textures/Texture2D.SampleCmp.test.yaml @@ -107,7 +107,8 @@ Shaders: Buffers: - Name: Tex - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, # (0,0) -> 0.2 diff --git a/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.GatherCmp.test.yaml b/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.GatherCmp.test.yaml index b37435331..2299606d3 100644 --- a/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.GatherCmp.test.yaml +++ b/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.GatherCmp.test.yaml @@ -49,7 +49,8 @@ Shaders: Buffers: - Name: SampledTexLess - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, # (0,0) R=0.2 @@ -58,7 +59,8 @@ Buffers: 0.8 ] # (1,1) R=0.8 - Name: SampledTexGreater - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, @@ -67,7 +69,8 @@ Buffers: 0.8 ] - Name: SampledTexRepeat - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, diff --git a/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.SampleCmp.test.yaml b/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.SampleCmp.test.yaml index 7d9a605bc..49f775c7c 100644 --- a/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.SampleCmp.test.yaml +++ b/test/Feature/Vk.SampledTextures/Vk.SampledTexture2D/Vk.SampledTexture2D.SampleCmp.test.yaml @@ -88,7 +88,8 @@ Shaders: Buffers: - Name: SampledTexLess - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, # (0,0) -> 0.2 @@ -97,7 +98,8 @@ Buffers: 0.8 ] # (1,1) -> 0.8 - Name: SampledTexGreater - Format: Depth32 + Format: Float32 + GpuFormat: D32Float Channels: 1 OutputProps: { Width: 2, Height: 2, Depth: 1 } Data: [ 0.2, From b2a28e4df92299ce666326e60b90485fcdd03694 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Mon, 15 Jun 2026 19:05:23 -0700 Subject: [PATCH 10/12] [NFC] clang-format Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/Support/Pipeline.h | 3 ++- lib/API/DX/Device.cpp | 3 +-- lib/API/VK/Device.cpp | 27 +++++++++++---------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index d09a07fea..1150e1afe 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -178,7 +178,8 @@ struct CPUBuffer { uint32_t ArraySize; // When set, names the GPU texture format directly (e.g. D32Float) instead of // inferring it from DataFormat + Channels via toFormat(). This lets depth - // buffers and other special formats be expressed without extending DataFormat. + // buffers and other special formats be expressed without extending + // DataFormat. std::optional GpuFormat; // Data can contain one block of data for a singular resource // or multiple blocks for a resource array. diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 5a7d477ec..6bcfd0b2e 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2846,8 +2846,7 @@ class DXDevice : public offloadtest::Device { // default depth target (which is not read back). if (P.Bindings.DepthBuffer.Ptr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; - auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( - *this, DSBuf); + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index bee407132..ce548efe6 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -3118,8 +3118,7 @@ class VulkanDevice : public offloadtest::Device { llvm::Expected createImage(Resource &R, BufferRef &Host, int UsageOverride = 0) { const offloadtest::CPUBuffer &B = *R.BufferPtr; - const bool IsDepth = - B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); + const bool IsDepth = B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); if (IsDepth && R.isReadWrite()) return llvm::createStringError(std::errc::invalid_argument, "Image memory allocation failed."); @@ -3322,8 +3321,7 @@ class VulkanDevice : public offloadtest::Device { // default depth target (which is not read back). if (P.Bindings.DepthBuffer.Ptr) { const CPUBuffer &DSBuf = *P.Bindings.DepthBuffer.Ptr; - auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer( - *this, DSBuf); + auto TexOrErr = offloadtest::createDepthBufferFromCPUBuffer(*this, DSBuf); if (!TexOrErr) return TexOrErr.takeError(); IS.DepthStencil = std::move(*TexOrErr); @@ -3577,8 +3575,7 @@ class VulkanDevice : public offloadtest::Device { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; ViewCreateInfo.subresourceRange.aspectMask = - (R.BufferPtr->GpuFormat && - isDepthFormat(*R.BufferPtr->GpuFormat)) + (R.BufferPtr->GpuFormat && isDepthFormat(*R.BufferPtr->GpuFormat)) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; ViewCreateInfo.subresourceRange.baseMipLevel = 0; @@ -3842,9 +3839,8 @@ class VulkanDevice : public offloadtest::Device { uint64_t CurrentOffset = 0; for (int I = 0; I < B.OutputProps.MipLevels; ++I) { VkBufferImageCopy Region = {}; - Region.imageSubresource.aspectMask = IsDepth - ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + Region.imageSubresource.aspectMask = + IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; Region.imageSubresource.mipLevel = I; Region.imageSubresource.baseArrayLayer = 0; Region.imageSubresource.layerCount = 1; @@ -3862,8 +3858,8 @@ class VulkanDevice : public offloadtest::Device { } VkImageSubresourceRange SubRange = {}; - SubRange.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + SubRange.aspectMask = + IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; SubRange.baseMipLevel = 0; SubRange.levelCount = B.OutputProps.MipLevels; SubRange.layerCount = 1; @@ -3986,8 +3982,8 @@ class VulkanDevice : public offloadtest::Device { const bool IsDepth = B.GpuFormat.has_value() && isDepthFormat(*B.GpuFormat); VkImageSubresourceRange SubRange = {}; - SubRange.aspectMask = IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + SubRange.aspectMask = + IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; SubRange.baseMipLevel = 0; SubRange.levelCount = B.OutputProps.MipLevels; SubRange.layerCount = 1; @@ -4014,9 +4010,8 @@ class VulkanDevice : public offloadtest::Device { uint64_t CurrentOffset = 0; for (int I = 0; I < B.OutputProps.MipLevels; ++I) { VkBufferImageCopy Region = {}; - Region.imageSubresource.aspectMask = IsDepth - ? VK_IMAGE_ASPECT_DEPTH_BIT - : VK_IMAGE_ASPECT_COLOR_BIT; + Region.imageSubresource.aspectMask = + IsDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; Region.imageSubresource.mipLevel = I; Region.imageSubresource.baseArrayLayer = 0; Region.imageSubresource.layerCount = 1; From db5af03ff087be4ce771a7f30188022f35f00592 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 16 Jun 2026 14:23:59 -0700 Subject: [PATCH 11/12] [NFC] Add const to Fmt for clang-tidy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/Device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/API/Device.cpp b/lib/API/Device.cpp index 55390a80c..9463f01b1 100644 --- a/lib/API/Device.cpp +++ b/lib/API/Device.cpp @@ -239,7 +239,7 @@ offloadtest::createDepthBufferFromCPUBuffer(Device &Dev, const CPUBuffer &Buf) { "Depth buffer requires a CPUBuffer with a depth GpuFormat; got '%s'.", Buf.GpuFormat ? getFormatName(*Buf.GpuFormat).data() : ""); - Format Fmt = *Buf.GpuFormat; + const Format Fmt = *Buf.GpuFormat; TextureCreateDesc Desc = {}; Desc.Location = MemoryLocation::GpuOnly; From c51524707463dd44f5f01a0f0536f9f6fec6dcbe Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Wed, 17 Jun 2026 11:42:45 -0700 Subject: [PATCH 12/12] [VK] Only set stencil aspect bit when format has stencil Fixes crash on depth-only formats like D32Float. --- lib/API/VK/Device.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index ce548efe6..936fd7344 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -2634,8 +2634,9 @@ class VulkanDevice : public offloadtest::Device { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; ViewCi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } else { - ViewCi.subresourceRange.aspectMask = - VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + ViewCi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (isStencilFormat(Desc.Fmt)) + ViewCi.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; } // Tex destructor will clean up Image + Memory on failure. if (auto Err = VK::toError(