Skip to content

Commit 8f3df8b

Browse files
EmilioLaisoclaude
andauthored
Full traditional raster pipeline test (#1254)
With the recent work landing Hull/Domain and Geometry shader support, the traditional raster pipeline is now complete. This PR adds a single test that exercises every stage of it in one pass and validates the result against the existing `SimpleTriangle.png` golden image. ### Pipeline Starting from a single point, the test drives all five raster stages: 1 point (triangle centroid "G" for a "big" triangle mapping to the one used in `SimpleTriangle.png`) → Vertex passthrough → Hull quad domain, all tess factors = 1, outputtopology("point") → Tessellator → 4 points - (topology change: patch → points) → Domain tag each point as sub-triangle 0..3, place at its centroid → Geometry expand each centroid → the sub-triangles (topology change: points → triangles) → Pixel outputs colour Here's a very non-precise drawing to visualize. The red dot is G, our input point. <img width="339" height="306" alt="image" src="https://github.com/user-attachments/assets/a2d6b4c5-64cc-45a8-a25a-5b5eb03042c3" /> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8931456 commit 8f3df8b

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
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

Comments
 (0)