diff --git a/include/API/Encoder.h b/include/API/Encoder.h index 9d2ed1cf4..8857cff3c 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" @@ -105,6 +106,15 @@ 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) {} + + /// 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/API/Enums.h b/include/API/Enums.h index d050b40fa..5aeca2f14 100644 --- a/include/API/Enums.h +++ b/include/API/Enums.h @@ -47,6 +47,19 @@ enum class StoreAction { enum class PrimitiveTopology { TriangleList, PointList, PatchList }; +/// 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 9cf0e5f77..9b9eb7a63 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -539,6 +539,8 @@ struct Pipeline { llvm::SmallVector Results; llvm::SmallVector Sets; DispatchParametersSet DispatchParameters; + std::optional ShadingRateOverride; + bool PrimitiveShadingRate = false; AccelerationStructureDescs AccelStructs; uint32_t getVertexCount() const { @@ -900,6 +902,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 edfaccb7b..9dc2ddd63 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -786,6 +786,42 @@ 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 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 && @@ -1506,6 +1542,21 @@ 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; + Caps.insert( + std::make_pair("VariableShadingRateTier2", + makeCapability("VariableShadingRateTier2", + SupportsVariableShadingRateTier2))); + #define D3D_FEATURE_BOOL(Name) \ Caps.insert( \ std::make_pair(#Name, makeCapability(#Name, Features.Name()))); @@ -2348,6 +2399,11 @@ class DXDevice : public offloadtest::Device { Scissor.Height = static_cast(VP.Height); Encoder.setScissor(Scissor); + if (P.ShadingRateOverride) + Encoder.setShadingRate(*P.ShadingRateOverride); + if (P.PrimitiveShadingRate) + Encoder.enablePrimitiveShadingRate(); + if (P.isTraditionalRaster()) { if (IS.VB) Encoder.setVertexBuffer(0, IS.VB.get(), 0, diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index 7959daa9b..125183f87 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -1937,6 +1937,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 282b2b9c2..78381c7ef 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -3547,6 +3547,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); diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 3940a550d..9c0f5df24 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -77,6 +77,9 @@ void MappingTraits::mapping(IO &I, if (auto Err = P.validateDispatchParameters()) 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) { 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..b56d4732f --- /dev/null +++ b/test/Feature/VRS/draw-rate-2x2.test @@ -0,0 +1,84 @@ +#--- 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 +# REQUIRES: VariableShadingRateTier1 +# 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 ] 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 a2d757c41..3aa63990f 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -175,6 +175,10 @@ 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) if device["Features"].get("RaytracingTier", "NotSupported") != "NotSupported": config.available_features.add("acceleration-structure")