From 083d609ba51262362fde7208686d9cc8ab481b1b Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Tue, 26 May 2026 21:47:57 -0700 Subject: [PATCH 1/5] [DX] Add Variable Rate Shading Tier 1 support (#1044) Adds plumbing for D3D12 VRS Tier 1 (per-draw RSSetShadingRate) so the offload test suite can validate coarse shading-rate pipelines. * New `ShadingRate` enum (1x1, 1x2, 2x1, 2x2, 2x4, 4x2, 4x4) in `API/Enums.h`, mirroring D3D12_SHADING_RATE. * New optional pipeline field `Pipeline.ShadingRateOverride`, surfaced in YAML as `ShadingRate: 2x2` (etc.) via a ScalarEnumerationTraits specialization. * `RenderEncoder::setShadingRate` virtual hook, defaulting to a no-op so VK / MTL backends silently ignore the field (tests opt out via `UNSUPPORTED:` instead). Adds the corresponding `API/Enums.h` include to `API/Encoder.h`. * DX backend overrides `setShadingRate` to translate the enum and call `ID3D12GraphicsCommandList5::RSSetShadingRate` with no combiners (Tier 2 per-primitive / per-tile rates are not wired yet). * `createGraphicsCommands` issues the call after scissor setup when the pipeline opts in. * Test: `Feature/VRS/draw-rate-2x2.test` renders a 4x4 RT with `ShadingRate: 2x2` and verifies the rasterizer-derivative output `ddx_coarse / 4 == 0.5` confirms the rate took effect (it would read 0.25 at 1x1). PASS on d3d12 (RTX 5060 Ti) + warp-d3d12. UNSUPPORTED: Vulkan || Metal. XFAIL: Clang. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/API/Encoder.h | 6 +++ include/API/Enums.h | 13 +++++ include/Support/Pipeline.h | 15 ++++++ lib/API/DX/Device.cpp | 32 +++++++++++ lib/Support/Pipeline.cpp | 2 + test/Feature/VRS/draw-rate-2x2.test | 83 +++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 test/Feature/VRS/draw-rate-2x2.test diff --git a/include/API/Encoder.h b/include/API/Encoder.h index 3b57f0d05..142397c0f 100644 --- a/include/API/Encoder.h +++ b/include/API/Encoder.h @@ -10,6 +10,7 @@ #define OFFLOADTEST_API_ENCODER_H #include "API/API.h" +#include "API/Enums.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -104,6 +105,11 @@ class RenderEncoder : public CommandEncoder { virtual void setVertexBuffer(uint32_t Slot, Buffer *VB, size_t Offset, uint32_t Stride) = 0; + /// Override the pipeline's default per-draw shading rate. Backends that do + /// not implement Variable Rate Shading are free to ignore this; tests that + /// depend on it should mark themselves UNSUPPORTED on those backends. + virtual void setShadingRate(ShadingRate Rate) {} + virtual llvm::Error drawInstanced(const PipelineState &PSO, uint32_t VertexCount, uint32_t InstanceCount, diff --git a/include/API/Enums.h b/include/API/Enums.h index 5fbcb9ad6..ff15c4b5d 100644 --- a/include/API/Enums.h +++ b/include/API/Enums.h @@ -46,6 +46,19 @@ enum class StoreAction { enum class PrimitiveTopology { TriangleList, PointList }; +/// Per-draw rasterizer shading rate (D3D12 VRS Tier 1). Tier 1 hardware +/// supports the four base rates (1x1, 1x2, 2x1, 2x2); the additional rates +/// (2x4, 4x2, 4x4) require AdditionalShadingRatesSupported. +enum class ShadingRate { + Rate_1x1, + Rate_1x2, + Rate_2x1, + Rate_2x2, + Rate_2x4, + Rate_4x2, + Rate_4x4, +}; + } // namespace offloadtest #endif // OFFLOADTEST_API_ENUMS_H diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index c13f54d2b..7030085f2 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -468,6 +468,7 @@ struct Pipeline { llvm::SmallVector Results; llvm::SmallVector Sets; DispatchParametersSet DispatchParameters; + std::optional ShadingRateOverride; uint32_t getVertexCount() const { if (DispatchParameters.VertexCount) @@ -747,6 +748,20 @@ template <> struct ScalarEnumerationTraits { } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &I, offloadtest::ShadingRate &V) { +#define ENUM_CASE(Val, Yaml) I.enumCase(V, Yaml, offloadtest::ShadingRate::Val) + ENUM_CASE(Rate_1x1, "1x1"); + ENUM_CASE(Rate_1x2, "1x2"); + ENUM_CASE(Rate_2x1, "2x1"); + ENUM_CASE(Rate_2x2, "2x2"); + ENUM_CASE(Rate_2x4, "2x4"); + ENUM_CASE(Rate_4x2, "4x2"); + ENUM_CASE(Rate_4x4, "4x4"); +#undef ENUM_CASE + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &I, offloadtest::dx::RootParamKind &V) { #define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::dx::RootParamKind::Val) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index 1a5d09124..c3833cb55 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -765,6 +765,35 @@ class DXRenderEncoder : public offloadtest::RenderEncoder { ScissorSet = true; } + void setShadingRate(offloadtest::ShadingRate Rate) override { + D3D12_SHADING_RATE DXRate = D3D12_SHADING_RATE_1X1; + switch (Rate) { + case offloadtest::ShadingRate::Rate_1x1: + DXRate = D3D12_SHADING_RATE_1X1; + break; + case offloadtest::ShadingRate::Rate_1x2: + DXRate = D3D12_SHADING_RATE_1X2; + break; + case offloadtest::ShadingRate::Rate_2x1: + DXRate = D3D12_SHADING_RATE_2X1; + break; + case offloadtest::ShadingRate::Rate_2x2: + DXRate = D3D12_SHADING_RATE_2X2; + break; + case offloadtest::ShadingRate::Rate_2x4: + DXRate = D3D12_SHADING_RATE_2X4; + break; + case offloadtest::ShadingRate::Rate_4x2: + DXRate = D3D12_SHADING_RATE_4X2; + break; + case offloadtest::ShadingRate::Rate_4x4: + DXRate = D3D12_SHADING_RATE_4X4; + break; + } + // Tier 1: no combiners (per-primitive / per-tile rates require Tier 2). + CB.CmdList->RSSetShadingRate(DXRate, nullptr); + } + void setVertexBuffer(uint32_t Slot, offloadtest::Buffer *VB, size_t Offset, uint32_t Stride) override { assert(Slot < D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT && @@ -2321,6 +2350,9 @@ class DXDevice : public offloadtest::Device { Scissor.Height = static_cast(VP.Height); Encoder.setScissor(Scissor); + if (P.ShadingRateOverride) + Encoder.setShadingRate(*P.ShadingRateOverride); + if (P.isTraditionalRaster()) { if (IS.VB) Encoder.setVertexBuffer(0, IS.VB.get(), 0, diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 80ff1b03f..00ee37b41 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -72,6 +72,8 @@ void MappingTraits::mapping(IO &I, if (auto Err = P.validateDispatchParameters()) I.setError(llvm::toString(std::move(Err))); + I.mapOptional("ShadingRate", P.ShadingRateOverride); + if (!I.outputting()) { for (auto &D : P.Sets) { for (auto &R : D.Resources) { diff --git a/test/Feature/VRS/draw-rate-2x2.test b/test/Feature/VRS/draw-rate-2x2.test new file mode 100644 index 000000000..b5dbdb4fd --- /dev/null +++ b/test/Feature/VRS/draw-rate-2x2.test @@ -0,0 +1,83 @@ +#--- vertex.hlsl +struct VSInput { + float4 position : POSITION; +}; + +struct VSOutput { + float4 position : SV_POSITION; +}; + +VSOutput main(VSInput input) { + VSOutput output; + output.position = input.position; + return output; +} + +#--- pixel.hlsl +// Encode the per-quad screen-space derivative of SV_Position into the output +// color. Without VRS the derivative is 1 (one pixel per shading invocation) +// and red == 1/4 == 0.25. With a 2x2 coarse rate, each shading invocation +// covers 2x2 pixels, so the derivative across the shading quad is 2 fine +// pixels and red == 2/4 == 0.5 (similarly for green from ddy). +float4 main(float4 pos : SV_Position) : SV_Target { + return float4(ddx_coarse(pos.x) / 4.0, + ddy_coarse(pos.y) / 4.0, + 0.0, 1.0); +} + +#--- pipeline.yaml +--- +Shaders: + - Stage: Vertex + Entry: main + - Stage: Pixel + Entry: main +Buffers: + - Name: VertexData + Format: Float32 + Stride: 16 + Data: [ 0.0, 3.0, 0.0, 1.0, + 3.0, -3.0, 0.0, 1.0, + -3.0, -3.0, 0.0, 1.0 ] + - Name: Output + Format: Float32 + Channels: 4 + FillSize: 256 # 4x4 @ 16 bytes per pixel + OutputProps: + Height: 4 + Width: 4 + Depth: 1 +Bindings: + VertexBuffer: VertexData + VertexAttributes: + - Format: Float32 + Channels: 4 + Offset: 0 + Name: POSITION + RenderTarget: Output +DescriptorSets: [] +ShadingRate: 2x2 +... +#--- end + +# Variable Rate Shading Tier 1 (per-draw RSSetShadingRate) is DirectX-only +# today. WARP supports the API on Windows 10 1903+. +# UNSUPPORTED: Vulkan || Metal +# 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 | FileCheck %s + +# All 16 output pixels share the (0.5, 0.5, 0, 1) value computed by the +# coarse shading invocations, confirming the 2x2 rate took effect. +# CHECK: Name: Output +# CHECK-NEXT: Format: Float32 +# CHECK-NEXT: Channels: 4 +# CHECK-NEXT: Data: [ 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1 ] From c55436e690e217b639955d9ab03a65fe8bd7a3e8 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Wed, 27 May 2026 13:42:17 -0700 Subject: [PATCH 2/5] [DX] Add per-primitive VRS test Expose VariableShadingRateTier2 as a lit feature and add a PrimitiveShadingRate toggle that enables SV_ShadingRate combiners for the DirectX backend. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- include/API/Encoder.h | 4 ++ include/Support/Pipeline.h | 1 + lib/API/DX/Device.cpp | 17 +++++ lib/Support/Pipeline.cpp | 1 + test/Feature/VRS/primitive-rate-2x2.test | 83 ++++++++++++++++++++++++ test/lit.cfg.py | 2 + 6 files changed, 108 insertions(+) create mode 100644 test/Feature/VRS/primitive-rate-2x2.test diff --git a/include/API/Encoder.h b/include/API/Encoder.h index 11c1b639b..8857cff3c 100644 --- a/include/API/Encoder.h +++ b/include/API/Encoder.h @@ -111,6 +111,10 @@ class RenderEncoder : public CommandEncoder { /// depend on it should mark themselves UNSUPPORTED on those backends. virtual void setShadingRate(ShadingRate Rate) {} + /// Enable the SV_ShadingRate per-primitive input. Backends that do not + /// implement Variable Rate Shading Tier 2 are free to ignore this. + virtual void enablePrimitiveShadingRate() {} + virtual llvm::Error drawInstanced(const PipelineState &PSO, uint32_t VertexCount, uint32_t InstanceCount, diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index 7030085f2..0f88d49f6 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -469,6 +469,7 @@ struct Pipeline { llvm::SmallVector Sets; DispatchParametersSet DispatchParameters; std::optional ShadingRateOverride; + bool PrimitiveShadingRate = false; uint32_t getVertexCount() const { if (DispatchParameters.VertexCount) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index f8889e2f8..ef278a84c 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -797,6 +797,13 @@ class DXRenderEncoder : public offloadtest::RenderEncoder { CB.CmdList->RSSetShadingRate(DXRate, nullptr); } + void enablePrimitiveShadingRate() override { + const D3D12_SHADING_RATE_COMBINER Combiners[] = { + D3D12_SHADING_RATE_COMBINER_OVERRIDE, + D3D12_SHADING_RATE_COMBINER_PASSTHROUGH}; + CB.CmdList->RSSetShadingRate(D3D12_SHADING_RATE_1X1, Combiners); + } + void setVertexBuffer(uint32_t Slot, offloadtest::Buffer *VB, size_t Offset, uint32_t Stride) override { assert(Slot < D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT && @@ -1510,6 +1517,14 @@ class DXDevice : public offloadtest::Device { CD3DX12FeatureSupport Features; Features.Init(Device.Get()); + const bool SupportsVariableShadingRateTier2 = + Features.VariableShadingRateTier() >= + D3D12_VARIABLE_SHADING_RATE_TIER_2; + Caps.insert( + std::make_pair("VariableShadingRateTier2", + makeCapability("VariableShadingRateTier2", + SupportsVariableShadingRateTier2))); + #define D3D_FEATURE_BOOL(Name) \ Caps.insert( \ std::make_pair(#Name, makeCapability(#Name, Features.Name()))); @@ -2354,6 +2369,8 @@ class DXDevice : public offloadtest::Device { if (P.ShadingRateOverride) Encoder.setShadingRate(*P.ShadingRateOverride); + if (P.PrimitiveShadingRate) + Encoder.enablePrimitiveShadingRate(); if (P.isTraditionalRaster()) { if (IS.VB) diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 00ee37b41..c6779efe4 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -73,6 +73,7 @@ void MappingTraits::mapping(IO &I, I.setError(llvm::toString(std::move(Err))); I.mapOptional("ShadingRate", P.ShadingRateOverride); + I.mapOptional("PrimitiveShadingRate", P.PrimitiveShadingRate, false); if (!I.outputting()) { for (auto &D : P.Sets) { diff --git a/test/Feature/VRS/primitive-rate-2x2.test b/test/Feature/VRS/primitive-rate-2x2.test new file mode 100644 index 000000000..798ccb95a --- /dev/null +++ b/test/Feature/VRS/primitive-rate-2x2.test @@ -0,0 +1,83 @@ +#--- vertex.hlsl +struct VSInput { + float4 position : POSITION; +}; + +struct VSOutput { + float4 position : SV_POSITION; + uint shadingRate : SV_ShadingRate; +}; + +VSOutput main(VSInput input) { + VSOutput output; + output.position = input.position; + output.shadingRate = 0x5; // D3D12_SHADING_RATE_2X2 + return output; +} + +#--- pixel.hlsl +// Encode the per-quad screen-space derivative of SV_Position into the output +// color. With a per-primitive 2x2 shading rate from SV_ShadingRate, each +// shading invocation covers 2x2 pixels, so both derivatives are 2 fine pixels. +float4 main(float4 pos : SV_Position) : SV_Target { + return float4(ddx_coarse(pos.x) / 4.0, + ddy_coarse(pos.y) / 4.0, + 0.0, 1.0); +} + +#--- pipeline.yaml +--- +Shaders: + - Stage: Vertex + Entry: main + - Stage: Pixel + Entry: main +Buffers: + - Name: VertexData + Format: Float32 + Stride: 16 + Data: [ 0.0, 3.0, 0.0, 1.0, + 3.0, -3.0, 0.0, 1.0, + -3.0, -3.0, 0.0, 1.0 ] + - Name: Output + Format: Float32 + Channels: 4 + FillSize: 256 # 4x4 @ 16 bytes per pixel + OutputProps: + Height: 4 + Width: 4 + Depth: 1 +Bindings: + VertexBuffer: VertexData + VertexAttributes: + - Format: Float32 + Channels: 4 + Offset: 0 + Name: POSITION + RenderTarget: Output +DescriptorSets: [] +PrimitiveShadingRate: true +... +#--- end + +# Variable Rate Shading Tier 2 (SV_ShadingRate) is DirectX-only today. +# UNSUPPORTED: Vulkan || Metal +# REQUIRES: VariableShadingRateTier2 +# XFAIL: Clang + +# RUN: split-file %s %t +# RUN: %dxc_target -T vs_6_4 -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 | FileCheck %s + +# All 16 output pixels share the (0.5, 0.5, 0, 1) value computed by the +# coarse shading invocations, confirming the per-primitive 2x2 rate took effect. +# CHECK: Name: Output +# CHECK-NEXT: Format: Float32 +# CHECK-NEXT: Channels: 4 +# CHECK-NEXT: Data: [ 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, 0.5, 0.5, 0, 1, +# CHECK-NEXT: 0.5, 0.5, 0, 1 ] diff --git a/test/lit.cfg.py b/test/lit.cfg.py index cc59667d7..48b4574bd 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -152,6 +152,8 @@ def setDeviceFeatures(config, device, compiler): config.available_features.add("Int64GroupSharedAtomics") if device["Features"].get("MeshShaderTier", "NotSupported") != "NotSupported": config.available_features.add("MeshShader") + if device["Features"].get("VariableShadingRateTier2", False): + config.available_features.add("VariableShadingRateTier2") setWaveSizeFeaturesDirectX(config, device) if device["API"] == "Metal": From 7310e6a5df0137a6bc3bb48f3dd57bea7e40f607 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Wed, 27 May 2026 13:43:44 -0700 Subject: [PATCH 3/5] [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 ef278a84c..aae64c99d 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -2422,7 +2422,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); @@ -2435,7 +2435,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 d84d2015fcff5a4d8e4b179a5e314a95d2802c62 Mon Sep 17 00:00:00 2001 From: alsepkow Date: Thu, 28 May 2026 14:23:10 -0700 Subject: [PATCH 4/5] Reject ShadingRateOverride on VK/MTL with clean error for convergence The shared Pipeline.ShadingRateOverride / Pipeline.PrimitiveShadingRate fields were honored only by DX; VK and MTL silently rendered at the default rate, which would produce confusing output mismatches if a test forgot to mark itself UNSUPPORTED. Add explicit not-supported errors in executeProgram on both backends so the shared Pipeline contract is honest. Tests in Feature/VRS already mark themselves UNSUPPORTED: Vulkan || Metal, so this guard is belt-and-suspenders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/MTL/MTLDevice.cpp | 9 +++++++++ lib/API/VK/Device.cpp | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index 604cbe243..91383dbfe 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -1761,6 +1761,15 @@ class MTLDevice : public offloadtest::Device { } llvm::Error executeProgram(Pipeline &P) override { + // Variable Rate Shading is not yet implemented on Metal. Tests that set + // a per-draw or per-primitive shading rate should mark themselves + // `UNSUPPORTED: Metal` so this guard never fires in CI; it exists to + // keep the shared Pipeline contract honest (see #1044). + if (P.ShadingRateOverride || P.PrimitiveShadingRate) + return llvm::createStringError( + std::errc::not_supported, + "Variable Rate Shading is not yet implemented on Metal"); + InvocationState IS; auto CBOrErr = MTLCommandBuffer::create(GraphicsQueue.Queue); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index da683603a..d8aae1781 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -3154,6 +3154,15 @@ class VulkanDevice : public offloadtest::Device { } llvm::Error executeProgram(Pipeline &P) override { + // Variable Rate Shading is not yet implemented on Vulkan. Tests that set + // a per-draw or per-primitive shading rate should mark themselves + // `UNSUPPORTED: Vulkan` so this guard never fires in CI; it exists to + // keep the shared Pipeline contract honest (see #1044). + if (P.ShadingRateOverride || P.PrimitiveShadingRate) + return llvm::createStringError( + std::errc::not_supported, + "Variable Rate Shading is not yet implemented on Vulkan"); + InvocationState State; auto CleanupState = llvm::scope_exit([&]() { cleanup(State); From cc1ef5c35e13e394dc8705c5f736e4bde8d5fbb2 Mon Sep 17 00:00:00 2001 From: Alex Sepkowski Date: Fri, 29 May 2026 18:18:09 -0700 Subject: [PATCH 5/5] Gate draw-rate test on VariableShadingRateTier1 capability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/API/DX/Device.cpp | 7 +++++++ test/Feature/VRS/draw-rate-2x2.test | 1 + test/lit.cfg.py | 2 ++ 3 files changed, 10 insertions(+) diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index ee6a0e9d6..9dc2ddd63 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -1542,6 +1542,13 @@ class DXDevice : public offloadtest::Device { CD3DX12FeatureSupport Features; Features.Init(Device.Get()); + const bool SupportsVariableShadingRateTier1 = + Features.VariableShadingRateTier() >= + D3D12_VARIABLE_SHADING_RATE_TIER_1; + Caps.insert( + std::make_pair("VariableShadingRateTier1", + makeCapability("VariableShadingRateTier1", + SupportsVariableShadingRateTier1))); const bool SupportsVariableShadingRateTier2 = Features.VariableShadingRateTier() >= D3D12_VARIABLE_SHADING_RATE_TIER_2; diff --git a/test/Feature/VRS/draw-rate-2x2.test b/test/Feature/VRS/draw-rate-2x2.test index b5dbdb4fd..b56d4732f 100644 --- a/test/Feature/VRS/draw-rate-2x2.test +++ b/test/Feature/VRS/draw-rate-2x2.test @@ -63,6 +63,7 @@ ShadingRate: 2x2 # Variable Rate Shading Tier 1 (per-draw RSSetShadingRate) is DirectX-only # today. WARP supports the API on Windows 10 1903+. # UNSUPPORTED: Vulkan || Metal +# REQUIRES: VariableShadingRateTier1 # XFAIL: Clang # RUN: split-file %s %t diff --git a/test/lit.cfg.py b/test/lit.cfg.py index b0b19c2b0..3aa63990f 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -175,6 +175,8 @@ def setDeviceFeatures(config, device, compiler): config.available_features.add("Int64TypedResourceAtomics") if device["Features"].get("MeshShaderTier", "NotSupported") != "NotSupported": config.available_features.add("MeshShader") + if device["Features"].get("VariableShadingRateTier1", False): + config.available_features.add("VariableShadingRateTier1") if device["Features"].get("VariableShadingRateTier2", False): config.available_features.add("VariableShadingRateTier2") setWaveSizeFeaturesDirectX(config, device)