summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts498
1 files changed, 498 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts
new file mode 100644
index 0000000000..f9c89b5a28
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts
@@ -0,0 +1,498 @@
+export const description = `Test primitive topology rendering.
+
+Draw a primitive using 6 vertices with each topology and check if the pixel is covered.
+
+Vertex sequence and coordinates are the same for each topology:
+ - Vertex buffer = [v1, v2, v3, v4, v5, v6]
+ - Topology = [point-list, line-list, line-strip, triangle-list, triangle-strip]
+
+Test locations are framebuffer coordinates:
+ - Pixel value { valid: green, invalid: black, format: 'rgba8unorm'}
+ - Test point is valid if the pixel value equals the covered pixel value at the test location.
+ - Primitive restart occurs for strips (line-strip and triangle-strip) between [v3, v4].
+
+ Topology: point-list Valid test location(s) Invalid test location(s)
+
+ v2 v4 v6 Every vertex. Line-strip locations.
+ Triangle-list locations.
+ Triangle-strip locations.
+
+ v1 v3 v5
+
+ Topology: line-list (3 lines)
+
+ v2 v4 v6 Center of three line segments: Line-strip locations.
+ * * * {v1,V2}, {v3,v4}, and {v4,v5}. Triangle-list locations.
+ * * * Triangle-strip locations.
+ * * *
+ v1 v3 v5
+
+ Topology: line-strip (5 lines)
+
+ v2 v4 v6
+ ** ** *
+ * * * * * Line-list locations Triangle-list locations.
+ * ** ** + Center of two line segments: Triangle-strip locations.
+ v1 v3 v5 {v2,v3} and {v4,v5}.
+ With primitive restart:
+ Line segment {v3, v4}.
+
+ Topology: triangle-list (2 triangles)
+
+ v2 v4 v6
+ ** ****** Center of two triangle(s): Triangle-strip locations.
+ **** **** {v1,v2,v3} and {v4,v5,v6}.
+ ****** **
+ v1 v3 v5
+
+ Topology: triangle-strip (4 triangles)
+
+ v2 v4 v6
+ ** ****** ** ****** Triangle-list locations None.
+ **** **** **** **** + Center of two triangle(s):
+ ****** ** ****** ** {v2,v3,v4} and {v3,v4,v5}. With primitive restart:
+ v1 v3 v5 Triangle {v2, v3, v4}
+ and {v3, v4, v5}.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+const kRTSize: number = 56;
+const kColorFormat = 'rgba8unorm';
+const kValidPixelColor = new Uint8Array([0x00, 0xff, 0x00, 0xff]); // green
+const kInvalidPixelColor = new Uint8Array([0x00, 0x00, 0x00, 0x00]); // black
+
+class Point2D {
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+
+ constructor(x: number, y: number) {
+ this.x = x;
+ this.y = y;
+ this.z = 0;
+ this.w = 1;
+ }
+
+ toNDC(): Point2D {
+ // NDC coordinate space is y-up, so we negate the y mapping.
+ // To ensure the resulting vertex in NDC will be placed at the center of the pixel, we
+ // must offset by the pixel coordinates or 0.5.
+ return new Point2D((2 * (this.x + 0.5)) / kRTSize - 1, (-2 * (this.y + 0.5)) / kRTSize + 1);
+ }
+
+ static getMidpoint(a: Point2D, b: Point2D) {
+ return new Point2D((a.x + b.x) / 2, (a.y + b.y) / 2);
+ }
+
+ static getCentroid(a: Point2D, b: Point2D, c: Point2D) {
+ return new Point2D((a.x + b.x + c.x) / 3, (a.y + b.y + c.y) / 3);
+ }
+}
+
+interface TestLocation {
+ location: Point2D;
+ color: Uint8Array;
+}
+
+const VertexLocations = [
+ new Point2D(8, 24), // v1
+ new Point2D(16, 8), // v2
+ new Point2D(24, 24), // v3
+ new Point2D(32, 8), // v4
+ new Point2D(40, 24), // v5
+ new Point2D(48, 8), // v6
+];
+
+function getPointTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Test points are always equal to vertex locations.
+ const testLocations: TestLocation[] = [];
+ for (const location of VertexLocations) {
+ testLocations.push({ location, color: expectedColor });
+ }
+ return testLocations;
+}
+
+function getLineTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Midpoints of 3 line segments
+ return [
+ {
+ // Line {v1, v2}
+ location: Point2D.getMidpoint(VertexLocations[0], VertexLocations[1]),
+ color: expectedColor,
+ },
+ {
+ // Line {v3, v4}
+ location: Point2D.getMidpoint(VertexLocations[2], VertexLocations[3]),
+ color: expectedColor,
+ },
+ {
+ // Line {v5, v6}
+ location: Point2D.getMidpoint(VertexLocations[4], VertexLocations[5]),
+ color: expectedColor,
+ },
+ ];
+}
+
+function getPrimitiveRestartLineTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Midpoints of 2 line segments
+ return [
+ {
+ // Line {v1, v2}
+ location: Point2D.getMidpoint(VertexLocations[0], VertexLocations[1]),
+ color: expectedColor,
+ },
+ {
+ // Line {v5, v6}
+ location: Point2D.getMidpoint(VertexLocations[4], VertexLocations[5]),
+ color: expectedColor,
+ },
+ ];
+}
+
+function getLineStripTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Midpoints of 2 line segments
+ return [
+ {
+ // Line {v2, v3}
+ location: Point2D.getMidpoint(VertexLocations[1], VertexLocations[2]),
+ color: expectedColor,
+ },
+ {
+ // Line {v4, v5}
+ location: Point2D.getMidpoint(VertexLocations[3], VertexLocations[4]),
+ color: expectedColor,
+ },
+ ];
+}
+
+function getTriangleListTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Center of two triangles
+ return [
+ {
+ // Triangle {v1, v2, v3}
+ location: Point2D.getCentroid(VertexLocations[0], VertexLocations[1], VertexLocations[2]),
+ color: expectedColor,
+ },
+ {
+ // Triangle {v4, v5, v6}
+ location: Point2D.getCentroid(VertexLocations[3], VertexLocations[4], VertexLocations[5]),
+ color: expectedColor,
+ },
+ ];
+}
+
+function getTriangleStripTestLocations(expectedColor: Uint8Array): TestLocation[] {
+ // Center of two triangles
+ return [
+ {
+ // Triangle {v2, v3, v4}
+ location: Point2D.getCentroid(VertexLocations[1], VertexLocations[2], VertexLocations[3]),
+ color: expectedColor,
+ },
+ {
+ // Triangle {v3, v4, v5}
+ location: Point2D.getCentroid(VertexLocations[2], VertexLocations[3], VertexLocations[4]),
+ color: expectedColor,
+ },
+ ];
+}
+
+function getDefaultTestLocations({
+ topology,
+ primitiveRestart = false,
+ invalidateLastInList = false,
+}: {
+ topology: GPUPrimitiveTopology;
+ primitiveRestart?: boolean;
+ invalidateLastInList?: boolean;
+}) {
+ function maybeInvalidateLast(locations: TestLocation[]) {
+ if (!invalidateLastInList) return locations;
+
+ return locations.map((tl, i) => {
+ if (i === locations.length - 1) {
+ return {
+ location: tl.location,
+ color: kInvalidPixelColor,
+ };
+ } else {
+ return tl;
+ }
+ });
+ }
+
+ let testLocations: TestLocation[];
+ switch (topology) {
+ case 'point-list':
+ testLocations = [
+ ...getPointTestLocations(kValidPixelColor),
+ ...getLineStripTestLocations(kInvalidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor),
+ ];
+ break;
+ case 'line-list':
+ testLocations = [
+ ...maybeInvalidateLast(getLineTestLocations(kValidPixelColor)),
+ ...getLineStripTestLocations(kInvalidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor),
+ ];
+ break;
+ case 'line-strip':
+ testLocations = [
+ ...(primitiveRestart
+ ? getPrimitiveRestartLineTestLocations(kValidPixelColor)
+ : getLineTestLocations(kValidPixelColor)),
+ ...getLineStripTestLocations(kValidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor),
+ ];
+ break;
+ case 'triangle-list':
+ testLocations = [
+ ...maybeInvalidateLast(getTriangleListTestLocations(kValidPixelColor)),
+ ...getTriangleStripTestLocations(kInvalidPixelColor),
+ ];
+ break;
+ case 'triangle-strip':
+ testLocations = [
+ ...getTriangleListTestLocations(kValidPixelColor),
+ ...getTriangleStripTestLocations(primitiveRestart ? kInvalidPixelColor : kValidPixelColor),
+ ];
+ break;
+ }
+ return testLocations;
+}
+
+function generateVertexBuffer(vertexLocations: Point2D[]): Float32Array {
+ const vertexCoords = new Float32Array(vertexLocations.length * 4);
+ for (let i = 0; i < vertexLocations.length; i++) {
+ const point = vertexLocations[i].toNDC();
+ vertexCoords[i * 4 + 0] = point.x;
+ vertexCoords[i * 4 + 1] = point.y;
+ vertexCoords[i * 4 + 2] = point.z;
+ vertexCoords[i * 4 + 3] = point.w;
+ }
+ return vertexCoords;
+}
+
+const kDefaultDrawCount = 6;
+class PrimitiveTopologyTest extends GPUTest {
+ makeAttachmentTexture(): GPUTexture {
+ return this.device.createTexture({
+ format: kColorFormat,
+ size: { width: kRTSize, height: kRTSize, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ });
+ }
+
+ run({
+ topology,
+ indirect,
+ testLocations,
+ primitiveRestart = false,
+ drawCount = kDefaultDrawCount,
+ }: {
+ topology: GPUPrimitiveTopology;
+ indirect: boolean;
+ testLocations: TestLocation[];
+ primitiveRestart?: boolean;
+ drawCount?: number;
+ }): void {
+ const colorAttachment = this.makeAttachmentTexture();
+
+ // Color load operator will clear color attachment to zero.
+ const encoder = this.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+
+ let stripIndexFormat = undefined;
+ if (topology === 'triangle-strip' || topology === 'line-strip') {
+ stripIndexFormat = 'uint32' as const;
+ }
+
+ // Draw a primitive using 6 vertices based on the type.
+ // Pixels are generated based on vertex position.
+ // If point, 1 pixel is generated at each vertex location.
+ // Otherwise, >1 pixels could be generated.
+ // Output color is solid green.
+ renderPass.setPipeline(
+ this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @location(0) pos : vec4<f32>
+ ) -> @builtin(position) vec4<f32> {
+ return pos;
+ }`,
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride: 4 * Float32Array.BYTES_PER_ELEMENT,
+ attributes: [
+ {
+ format: 'float32x4',
+ offset: 0,
+ shaderLocation: 0,
+ },
+ ],
+ },
+ ],
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`,
+ }),
+ entryPoint: 'main',
+ targets: [{ format: kColorFormat }],
+ },
+ primitive: {
+ topology,
+ stripIndexFormat,
+ },
+ })
+ );
+
+ // Create vertices for the primitive in a vertex buffer and bind it.
+ const vertexCoords = generateVertexBuffer(VertexLocations);
+ const vertexBuffer = this.makeBufferWithContents(vertexCoords, GPUBufferUsage.VERTEX);
+ renderPass.setVertexBuffer(0, vertexBuffer);
+
+ // Restart the strip between [v3, <restart>, v4].
+ if (primitiveRestart) {
+ const indexBuffer = this.makeBufferWithContents(
+ new Uint32Array([0, 1, 2, -1, 3, 4, 5]),
+ GPUBufferUsage.INDEX
+ );
+ renderPass.setIndexBuffer(indexBuffer, 'uint32');
+
+ if (indirect) {
+ renderPass.drawIndexedIndirect(
+ this.makeBufferWithContents(
+ new Uint32Array([drawCount + 1, 1, 0, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ ),
+ 0
+ );
+ } else {
+ renderPass.drawIndexed(drawCount + 1); // extra index for restart
+ }
+ } else {
+ if (indirect) {
+ renderPass.drawIndirect(
+ this.makeBufferWithContents(
+ new Uint32Array([drawCount, 1, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ ),
+ 0
+ );
+ } else {
+ renderPass.draw(drawCount);
+ }
+ }
+
+ renderPass.end();
+
+ this.device.queue.submit([encoder.finish()]);
+
+ for (const testPixel of testLocations) {
+ this.expectSinglePixelIn2DTexture(
+ colorAttachment,
+ kColorFormat,
+ { x: testPixel.location.x, y: testPixel.location.y },
+ { exp: testPixel.color }
+ );
+ }
+ }
+}
+
+export const g = makeTestGroup(PrimitiveTopologyTest);
+
+const topologies: GPUPrimitiveTopology[] = [
+ 'point-list',
+ 'line-list',
+ 'line-strip',
+ 'triangle-list',
+ 'triangle-strip',
+];
+
+g.test('basic')
+ .desc(
+ `Compute test locations for valid and invalid pixels for each topology.
+ If the primitive covers the pixel, the color value will be |kValidPixelColor|.
+ Otherwise, a non-covered pixel will be |kInvalidPixelColor|.
+
+ Params:
+ - topology= {...all topologies}
+ - indirect= {true, false}
+ - primitiveRestart= { true, false } - always false for non-strip topologies
+ `
+ )
+ .params(u =>
+ u //
+ .combine('topology', topologies)
+ .combine('indirect', [false, true])
+ .combine('primitiveRestart', [false, true])
+ .unless(
+ p => p.primitiveRestart && p.topology !== 'line-strip' && p.topology !== 'triangle-strip'
+ )
+ )
+ .fn(t => {
+ t.run({
+ ...t.params,
+ testLocations: getDefaultTestLocations(t.params),
+ });
+ });
+
+g.test('unaligned_vertex_count')
+ .desc(
+ `Test that drawing with a number of vertices that's not a multiple of the vertices a given primitive list topology is not an error. The last primitive is not drawn.
+
+ Params:
+ - topology= {line-list, triangle-list}
+ - indirect= {true, false}
+ - drawCount - number of vertices to draw. A value smaller than the test's default of ${kDefaultDrawCount}.
+ One smaller for line-list. One or two smaller for triangle-list.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('topology', ['line-list', 'triangle-list'] as const)
+ .combine('indirect', [false, true])
+ .expand('drawCount', function* (p) {
+ switch (p.topology) {
+ case 'line-list':
+ yield kDefaultDrawCount - 1;
+ break;
+ case 'triangle-list':
+ yield kDefaultDrawCount - 1;
+ yield kDefaultDrawCount - 2;
+ break;
+ }
+ })
+ )
+ .fn(t => {
+ const testLocations = getDefaultTestLocations({ ...t.params, invalidateLastInList: true });
+ t.run({
+ ...t.params,
+ testLocations,
+ });
+ });