[require] shader model >= 5.0 [rtv 0] format r32g32b32a32-float size (2dms, 4, 640, 480) [vertex shader] % The pixels in the leftmost column lose their leftmost sample. static const float epsilon = 0.25f * 2.0f / 640.0f; static const float2 vertices[3] = { {-1.0f + epsilon, 1.0f}, { 3.0f, 1.0f}, {-1.0f + epsilon, -3.0f}, }; void main(uint id : SV_VertexID, out float4 position : SV_Position, out float2 attr : ATTR) { position = float4(vertices[id], 0.0f, 1.0f); attr = float2(position.x * 320.0f + 320.0f, -position.y * 240.0f + 240.0f); } % The multiplication by (sample_idx + 1) when computing the error is to % introduce a dependency on the sample index which hopefully the compiler is not % able to optimize; this way the shader is hopefully forced to execute for each % sample. These tests depend on the implementation using the default sample % positions. % First let's check that we're getting the position interpolation right: % center interpolation. [pixel shader] float4 main(float4 pos : SV_Position, uint sample_idx : SV_SampleIndex) : SV_Target { float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = pos.xy - ref; float2 err = abs(diff) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(msl) draw triangle list 3 todo probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) todo probe (639, 0) rgba(0.0, 0.0, 0.0, 0.0) todo probe ( 0, 479) rgba(0.25, 0.25, 0.25, 0.25) todo probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Centroid interpolation, which means the pixel center if all samples are % covered and the first covered sample if a least a sample is not covered (in % general it has nothing to do with the actual centroid of the covered samples). % The WARP driver seems to have problems with some corner pixels, which we % avoid. [pixel shader] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; float4 main(centroid float4 pos : SV_Position, uint sample_idx : SV_SampleIndex) : SV_Target { bool first_col = floor(pos.x) == 0.0f; float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = pos.xy - ref; float2 expected = first_col ? positions[0] : 0.0f; float2 err = abs(diff - expected) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(glsl | msl) draw triangle list 3 todo probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) todo probe (638, 0) rgba(0.0, 0.0, 0.0, 0.0) todo probe ( 0, 478) rgba(0.25, 0.25, 0.25, 0.25) todo probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Sample interpolation. [pixel shader todo] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; float4 main(sample float4 pos : SV_Position, uint sample_idx : SV_SampleIndex) : SV_Target { float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = pos.xy - ref; float2 err = abs(diff - positions[sample_idx]) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(sm<6) draw triangle list 3 probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 0) rgba(0.0, 0.0, 0.0, 0.0) probe ( 0, 479) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Same tests with a non-SV semantic: pixel center interpolation. [pixel shader] float4 main(float4 pos : SV_Position, float2 attr : ATTR, uint sample_idx : SV_SampleIndex) : SV_Target { float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = attr.xy - ref; float2 err = abs(diff) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(msl) draw triangle list 3 probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 0) rgba(0.0, 0.0, 0.0, 0.0) probe ( 0, 479) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Centroid interpolation, which means the pixel center if all samples are % covered and the first covered sample if a least a sample is not covered (in % general it has nothing to do with the actual centroid of the covered samples). % The WARP driver seems to have problems with some corner pixels, which we % avoid. % % Since Vulkan has more relaxed specification for centroid interpolation than % D3D12, not all drivers implement the behavior we need. llvmpipe is one of % those, so is marked todo. [pixel shader] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; float4 main(float4 pos : SV_Position, centroid float2 attr : ATTR, uint sample_idx : SV_SampleIndex) : SV_Target { bool first_col = floor(pos.x) == 0.0f; float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = attr.xy - ref; float2 expected = first_col ? positions[0] : 0.0f; float2 err = abs(diff - expected) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(glsl | msl) draw triangle list 3 todo(llvmpipe) probe (0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (638, 0) rgba(0.0, 0.0, 0.0, 0.0) probe (0, 478) rgba(0.25, 0.25, 0.25, 0.25) todo(llvmpipe) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Sample interpolation. [pixel shader todo] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; float4 main(float4 pos : SV_Position, sample float2 attr : ATTR, uint sample_idx : SV_SampleIndex) : SV_Target { float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = attr.xy - ref; float2 err = abs(diff - positions[sample_idx]) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(sm<6) draw triangle list 3 probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 0) rgba(0.0, 0.0, 0.0, 0.0) probe ( 0, 479) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Using EvaluateAttributeCentroid(). [pixel shader todo] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; float4 main(float4 pos : SV_Position, float2 attr : ATTR, uint sample_idx : SV_SampleIndex) : SV_Target { bool first_col = floor(pos.x) == 0.0f; float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 eval = EvaluateAttributeCentroid(attr); float2 diff = eval.xy - ref; float2 expected = first_col ? positions[0] : 0.0f; float2 err = abs(diff - expected) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(sm<6) draw triangle list 3 todo(llvmpipe) probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (638, 0) rgba(0.0, 0.0, 0.0, 0.0) probe ( 0, 478) rgba(0.25, 0.25, 0.25, 0.25) todo(llvmpipe) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0) % Using EvaluateAttributeAtSample(). [pixel shader todo] static const float2 positions[4] = { {-0.125f, -0.375f}, { 0.375f, -0.125f}, {-0.375f, 0.125f}, { 0.125f, 0.375f}, }; static const uint mixer[4] = {1, 3, 2, 0}; float4 main(float4 pos : SV_Position, float2 attr : ATTR, uint sample_idx : SV_SampleIndex) : SV_Target { /* Use a different sample index than the one we're evaluating, just in case * the compiler tries to cheat and do a plain sample interpolation instead * of using the specific sample we requested. */ float2 eval; /* Older WARP versions do not like calling EvaluateAttributeAtSample() on * anything that is not an immediate constant. */ switch (mixer[sample_idx]) { case 0: eval = EvaluateAttributeAtSample(attr, 0); break; case 1: eval = EvaluateAttributeAtSample(attr, 1); break; case 2: eval = EvaluateAttributeAtSample(attr, 2); break; case 3: eval = EvaluateAttributeAtSample(attr, 3); break; } float2 ref = floor(pos.xy) + float2(0.5f, 0.5f); float2 diff = eval.xy - ref; float2 err = abs(diff - positions[mixer[sample_idx]]) * (sample_idx + 1); return floor(1000.0f * float4(err, 0.0f, 0.0f)); } [test] clear rtv 0 1.0 1.0 1.0 1.0 todo(sm<6) draw triangle list 3 probe ( 0, 0) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 0) rgba(0.0, 0.0, 0.0, 0.0) probe ( 0, 479) rgba(0.25, 0.25, 0.25, 0.25) probe (639, 479) rgba(0.0, 0.0, 0.0, 0.0)