|
| 1 | +# Exercises the full traditional raster pipeline in one pass: |
| 2 | +# Vertex -> Hull -> Domain -> Geometry -> Pixel |
| 3 | +# starting from a single point. The tessellator amplifies that point into four |
| 4 | +# points (one per sub-triangle of the midpoint subdivision) and the geometry |
| 5 | +# shader expands each into a triangle -- two primitive-topology transitions |
| 6 | +# (patch -> points, points -> triangles). The four sub-triangles tile the |
| 7 | +# original triangle, so the result is validated against the same golden image. |
| 8 | + |
| 9 | +#--- vertex.hlsl |
| 10 | +struct VSOutput { |
| 11 | + float4 position : POSITION; |
| 12 | +}; |
| 13 | + |
| 14 | +VSOutput main(float4 position : POSITION) { |
| 15 | + VSOutput o; |
| 16 | + o.position = position; |
| 17 | + return o; |
| 18 | +} |
| 19 | + |
| 20 | +#--- hull.hlsl |
| 21 | +struct HSInput { |
| 22 | + float4 position : POSITION; |
| 23 | +}; |
| 24 | + |
| 25 | +struct HSOutput { |
| 26 | + float4 position : POSITION; |
| 27 | +}; |
| 28 | + |
| 29 | +struct HSPatchConstants { |
| 30 | + float Edges[4] : SV_TessFactor; |
| 31 | + float Inside[2] : SV_InsideTessFactor; |
| 32 | +}; |
| 33 | + |
| 34 | +// A quad domain with every factor at 1 produces exactly four domain vertices |
| 35 | +// (the quad corners) and no interior points. With the "point" output topology |
| 36 | +// below, the tessellator hands the Geometry stage four point primitives -- one |
| 37 | +// slot per sub-triangle of the midpoint subdivision. |
| 38 | +HSPatchConstants PatchConstants(InputPatch<HSInput, 1> patch, |
| 39 | + uint patchID : SV_PrimitiveID) { |
| 40 | + HSPatchConstants c; |
| 41 | + c.Edges[0] = 1.0; |
| 42 | + c.Edges[1] = 1.0; |
| 43 | + c.Edges[2] = 1.0; |
| 44 | + c.Edges[3] = 1.0; |
| 45 | + c.Inside[0] = 1.0; |
| 46 | + c.Inside[1] = 1.0; |
| 47 | + return c; |
| 48 | +} |
| 49 | + |
| 50 | +[domain("quad")] |
| 51 | +[partitioning("integer")] |
| 52 | +[outputtopology("point")] |
| 53 | +[outputcontrolpoints(1)] |
| 54 | +[patchconstantfunc("PatchConstants")] |
| 55 | +HSOutput main(InputPatch<HSInput, 1> patch, uint i : SV_OutputControlPointID) { |
| 56 | + HSOutput o; |
| 57 | + o.position = patch[i].position; |
| 58 | + return o; |
| 59 | +} |
| 60 | + |
| 61 | +#--- domain.hlsl |
| 62 | +struct DSInput { |
| 63 | + float4 position : POSITION; |
| 64 | +}; |
| 65 | + |
| 66 | +struct DSPatchConstants { |
| 67 | + float Edges[4] : SV_TessFactor; |
| 68 | + float Inside[2] : SV_InsideTessFactor; |
| 69 | +}; |
| 70 | + |
| 71 | +struct DSOutput { |
| 72 | + float4 position : SV_POSITION; // the sub-triangle centroid |
| 73 | + float2 bigCenter : BIGCENTER; // big triangle centroid, forwarded |
| 74 | + nointerpolation uint subTri : SUBTRI; // which sub-triangle (0..3) |
| 75 | +}; |
| 76 | + |
| 77 | +// Classify each of the four tessellated points (at quad-domain corners |
| 78 | +// (0,0),(1,0),(0,1),(1,1)) into one of the four sub-triangles, and place it at |
| 79 | +// that sub-triangle's centroid. Corner sub-triangles sit at G + 0.5*d_k; the |
| 80 | +// central (inverted) one sits at G. The Geometry stage expands each centroid |
| 81 | +// back into a full sub-triangle. |
| 82 | +[domain("quad")] |
| 83 | +DSOutput main(DSPatchConstants constants, float2 uv : SV_DomainLocation, |
| 84 | + const OutputPatch<DSInput, 1> patch) { |
| 85 | + float2 g = patch[0].position.xy; |
| 86 | + |
| 87 | + // Big triangle corner offsets from its centroid (SimpleTriangle layout). |
| 88 | + const float2 d0 = float2(0.0, 0.33333333); |
| 89 | + const float2 d1 = float2(0.25, -0.16666667); |
| 90 | + const float2 d2 = float2(-0.25, -0.16666667); |
| 91 | + |
| 92 | + uint id = (uv.x > 0.5 ? 1u : 0u) + (uv.y > 0.5 ? 2u : 0u); |
| 93 | + |
| 94 | + float2 sc = g; // central sub-triangle centroid == big centroid |
| 95 | + if (id == 0) |
| 96 | + sc = g + 0.5 * d0; |
| 97 | + else if (id == 1) |
| 98 | + sc = g + 0.5 * d1; |
| 99 | + else if (id == 2) |
| 100 | + sc = g + 0.5 * d2; |
| 101 | + |
| 102 | + DSOutput o; |
| 103 | + o.position = float4(sc, 0.0, 1.0); |
| 104 | + o.bigCenter = g; |
| 105 | + o.subTri = id; |
| 106 | + return o; |
| 107 | +} |
| 108 | + |
| 109 | +#--- geometry.hlsl |
| 110 | +struct GSInput { |
| 111 | + float4 position : SV_POSITION; |
| 112 | + float2 bigCenter : BIGCENTER; |
| 113 | + nointerpolation uint subTri : SUBTRI; |
| 114 | +}; |
| 115 | + |
| 116 | +struct GSOutput { |
| 117 | + float4 position : SV_POSITION; |
| 118 | + float4 color : COLOR; |
| 119 | +}; |
| 120 | + |
| 121 | +// Expand one sub-triangle centroid into its three corners. The midpoint |
| 122 | +// subdivision splits the big triangle (corners V0/V1/V2) into three corner |
| 123 | +// sub-triangles plus a central inverted one; together they tile it exactly. |
| 124 | +// Each emitted vertex carries the colour it has in the original gradient |
| 125 | +// (corners = R/G/B, edge midpoints = their averages), so the reassembled image |
| 126 | +// reproduces SimpleTriangle's gradient pixel-for-pixel. |
| 127 | +[maxvertexcount(3)] |
| 128 | +void main(point GSInput input[1], inout TriangleStream<GSOutput> stream) { |
| 129 | + float2 g = input[0].bigCenter; |
| 130 | + uint id = input[0].subTri; |
| 131 | + |
| 132 | + const float2 d0 = float2(0.0, 0.33333333); |
| 133 | + const float2 d1 = float2(0.25, -0.16666667); |
| 134 | + const float2 d2 = float2(-0.25, -0.16666667); |
| 135 | + |
| 136 | + float2 V0 = g + d0, V1 = g + d1, V2 = g + d2; |
| 137 | + float2 M01 = 0.5 * (V0 + V1); |
| 138 | + float2 M12 = 0.5 * (V1 + V2); |
| 139 | + float2 M02 = 0.5 * (V0 + V2); |
| 140 | + |
| 141 | + const float3 cR = float3(1.0, 0.0, 0.0); |
| 142 | + const float3 cG = float3(0.0, 1.0, 0.0); |
| 143 | + const float3 cB = float3(0.0, 0.0, 1.0); |
| 144 | + const float3 cM01 = float3(0.5, 0.5, 0.0); |
| 145 | + const float3 cM12 = float3(0.0, 0.5, 0.5); |
| 146 | + const float3 cM02 = float3(0.5, 0.0, 0.5); |
| 147 | + |
| 148 | + float2 p0, p1, p2; |
| 149 | + float3 q0, q1, q2; |
| 150 | + if (id == 0) { |
| 151 | + p0 = V0; |
| 152 | + q0 = cR; |
| 153 | + p1 = M01; |
| 154 | + q1 = cM01; |
| 155 | + p2 = M02; |
| 156 | + q2 = cM02; |
| 157 | + } else if (id == 1) { |
| 158 | + p0 = M01; |
| 159 | + q0 = cM01; |
| 160 | + p1 = V1; |
| 161 | + q1 = cG; |
| 162 | + p2 = M12; |
| 163 | + q2 = cM12; |
| 164 | + } else if (id == 2) { |
| 165 | + p0 = M02; |
| 166 | + q0 = cM02; |
| 167 | + p1 = M12; |
| 168 | + q1 = cM12; |
| 169 | + p2 = V2; |
| 170 | + q2 = cB; |
| 171 | + } else { |
| 172 | + p0 = M01; |
| 173 | + q0 = cM01; |
| 174 | + p1 = M12; |
| 175 | + q1 = cM12; |
| 176 | + p2 = M02; |
| 177 | + q2 = cM02; |
| 178 | + } |
| 179 | + |
| 180 | + GSOutput o; |
| 181 | + o.position = float4(p0, 0.0, 1.0); |
| 182 | + o.color = float4(q0, 1.0); |
| 183 | + stream.Append(o); |
| 184 | + o.position = float4(p1, 0.0, 1.0); |
| 185 | + o.color = float4(q1, 1.0); |
| 186 | + stream.Append(o); |
| 187 | + o.position = float4(p2, 0.0, 1.0); |
| 188 | + o.color = float4(q2, 1.0); |
| 189 | + stream.Append(o); |
| 190 | +} |
| 191 | + |
| 192 | +#--- pixel.hlsl |
| 193 | +struct PSInput { |
| 194 | + float4 position : SV_POSITION; |
| 195 | + float4 color : COLOR; |
| 196 | +}; |
| 197 | + |
| 198 | +float4 main(PSInput input) : SV_TARGET { return input.color; } |
| 199 | + |
| 200 | +#--- pipeline.yaml |
| 201 | +--- |
| 202 | +Shaders: |
| 203 | + - Stage: Vertex |
| 204 | + Entry: main |
| 205 | + - Stage: Hull |
| 206 | + Entry: main |
| 207 | + - Stage: Domain |
| 208 | + Entry: main |
| 209 | + - Stage: Geometry |
| 210 | + Entry: main |
| 211 | + - Stage: Pixel |
| 212 | + Entry: main |
| 213 | +Buffers: |
| 214 | + # A single control point at the big triangle centroid: ((0 + 0.25 - 0.25)/3, |
| 215 | + # (0.25 - 0.25 - 0.25)/3) = (0, -1/12). Everything downstream is rebuilt from |
| 216 | + # it, so a dropped position visibly moves the whole triangle. |
| 217 | + - Name: VertexData |
| 218 | + Format: Float32 |
| 219 | + Stride: 12 # 1 vertex, float3 position |
| 220 | + Data: [ 0.0, -0.08333333, 0.0 ] |
| 221 | + - Name: Output |
| 222 | + Format: Float32 |
| 223 | + Channels: 4 |
| 224 | + FillSize: 1048576 # 256x256 @ 16 bytes per pixel |
| 225 | + OutputProps: |
| 226 | + Height: 256 |
| 227 | + Width: 256 |
| 228 | + Depth: 1 |
| 229 | +Bindings: |
| 230 | + VertexBuffer: VertexData |
| 231 | + VertexAttributes: |
| 232 | + - Format: Float32 |
| 233 | + Channels: 3 |
| 234 | + Offset: 0 |
| 235 | + Name: POSITION |
| 236 | + Topology: PatchList |
| 237 | + PatchControlPoints: 1 |
| 238 | + RenderTarget: Output |
| 239 | +DescriptorSets: [] |
| 240 | +... |
| 241 | +#--- rules.yaml |
| 242 | +--- |
| 243 | +- Type: PixelPercent |
| 244 | + Val: 0.2 # No more than 0.2% of pixels may be visibly different. |
| 245 | +... |
| 246 | +#--- end |
| 247 | + |
| 248 | +# Metal has no native geometry shader stage, and its tessellation maps HS/DS |
| 249 | +# onto a compute kernel + post-tessellation vertex function. Metal Shader |
| 250 | +# Converter can emulate both stages |
| 251 | +# (https://developer.apple.com/metal/shader-converter/#emulating-geometry), so |
| 252 | +# this is technically possible, but that emulation path isn't wired up here. |
| 253 | +# UNSUPPORTED: Metal |
| 254 | + |
| 255 | +# XFAIL: Clang |
| 256 | +# REQUIRES: goldenimage |
| 257 | + |
| 258 | +# RUN: split-file %s %t |
| 259 | +# RUN: %dxc_target -T vs_6_0 -Fo %t-vertex.o %t/vertex.hlsl |
| 260 | +# RUN: %dxc_target -T hs_6_0 -Fo %t-hull.o %t/hull.hlsl |
| 261 | +# RUN: %dxc_target -T ds_6_0 -Fo %t-domain.o %t/domain.hlsl |
| 262 | +# RUN: %dxc_target -T gs_6_0 -Fo %t-geometry.o %t/geometry.hlsl |
| 263 | +# RUN: %dxc_target -T ps_6_0 -Fo %t-pixel.o %t/pixel.hlsl |
| 264 | +# RUN: %offloader %t/pipeline.yaml %t-vertex.o %t-hull.o %t-domain.o %t-geometry.o %t-pixel.o -r Output -o %t/Output.png |
| 265 | +# RUN: imgdiff %t/Output.png %goldenimage_dir/hlsl/Graphics/SimpleTriangle.png -rules %t/rules.yaml |
0 commit comments