summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts456
1 files changed, 456 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts
new file mode 100644
index 0000000000..598d75636d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/overrides.spec.ts
@@ -0,0 +1,456 @@
+export const description = `
+Testing render pipeline using overridable constants in vertex stage and fragment stage.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { PerTexelComponent } from '../../../util/texture/texel_data.js';
+
+class F extends GPUTest {
+ async ExpectShaderOutputWithConstants(
+ isAsync: boolean,
+ format: GPUTextureFormat,
+ expected: PerTexelComponent<number>,
+ vertex: GPUVertexState,
+ fragment: GPUFragmentState
+ ) {
+ const renderTarget = this.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex,
+ fragment,
+ primitive: {
+ topology: 'triangle-list',
+ frontFace: 'ccw',
+ cullMode: 'back',
+ },
+ };
+
+ const promise = isAsync
+ ? this.device.createRenderPipelineAsync(descriptor)
+ : Promise.resolve(this.device.createRenderPipeline(descriptor));
+
+ const pipeline = await promise;
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A,
+ },
+ loadOp: 'clear',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ this.expectSingleColor(renderTarget, format, {
+ size: [1, 1, 1],
+ exp: expected,
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kClearValueResult = { R: 0.2, G: 0.4, B: 0.6, A: 0.8 };
+const kDefaultValueResult = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+
+const kFullScreenTriangleVertexShader = `
+override xright: f32 = 3.0;
+override ytop: f32 = 3.0;
+
+@vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, ytop),
+ vec2<f32>(-1.0, -ytop),
+ vec2<f32>(xright, 0.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+}
+`;
+
+const kFullScreenTriangleFragmentShader = `
+override R: f32 = 1.0;
+override G: f32 = 1.0;
+override B: f32 = 1.0;
+override A: f32 = 1.0;
+
+@fragment fn main()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(R, G, B, A);
+}
+`;
+
+g.test('basic')
+ .desc(
+ `Test that either correct constants override values or default values when no constants override value are provided at pipeline creation time are used correctly in vertex and fragment shader.`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .beginSubcases()
+ .combineWithParams([
+ {
+ expected: kDefaultValueResult,
+ vertexConstants: {},
+ fragmentConstants: {},
+ },
+ {
+ expected: kClearValueResult,
+ vertexConstants: {
+ xright: -3.0,
+ } as Record<string, GPUPipelineConstantValue>,
+ fragmentConstants: {},
+ },
+ {
+ expected: kClearValueResult,
+ vertexConstants: {
+ ytop: -3.0,
+ } as Record<string, GPUPipelineConstantValue>,
+ fragmentConstants: {},
+ },
+ {
+ expected: kDefaultValueResult,
+ vertexConstants: {
+ xright: 4.0,
+ ytop: 4.0,
+ } as Record<string, GPUPipelineConstantValue>,
+ fragmentConstants: {},
+ },
+ {
+ expected: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 0.0, B: 0.0 } as Record<string, GPUPipelineConstantValue>,
+ },
+ {
+ expected: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ ])
+ )
+ .fn(async t => {
+ const format = 'bgra8unorm';
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader,
+ }),
+ entryPoint: 'main',
+ constants: t.params.vertexConstants,
+ },
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader,
+ }),
+ entryPoint: 'main',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }],
+ }
+ );
+ });
+
+g.test('precision')
+ .desc(`Test that the float number precision is preserved for constants`)
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .beginSubcases()
+ .combineWithParams([
+ {
+ expected: { R: 3.14159, G: 1.0, B: 1.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 3.14159 } as Record<string, GPUPipelineConstantValue>,
+ },
+ {
+ expected: { R: 3.141592653589793238, G: 1.0, B: 1.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 3.141592653589793238 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ ])
+ )
+ .fn(async t => {
+ const format = 'rgba32float';
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader,
+ }),
+ entryPoint: 'main',
+ constants: t.params.vertexConstants,
+ },
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader,
+ }),
+ entryPoint: 'main',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }],
+ }
+ );
+ });
+
+g.test('shared_shader_module')
+ .desc(
+ `Test that when the same module is shared by different pipelines, the constant values are still being used correctly.`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .beginSubcases()
+ .combineWithParams([
+ {
+ expected0: kClearValueResult,
+ vertexConstants0: {
+ xright: -3.0,
+ } as Record<string, GPUPipelineConstantValue>,
+ fragmentConstants0: {},
+
+ expected1: kDefaultValueResult,
+ vertexConstants1: {},
+ fragmentConstants1: {},
+ },
+ {
+ expected0: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 },
+ vertexConstants0: {},
+ fragmentConstants0: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+
+ expected1: kDefaultValueResult,
+ vertexConstants1: {},
+ fragmentConstants1: {},
+ },
+ {
+ expected0: { R: 1.0, G: 0.0, B: 1.0, A: 0.0 },
+ vertexConstants0: {},
+ fragmentConstants0: { R: 1.0, G: 0.0, B: 1.0, A: 0.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+
+ expected1: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ vertexConstants1: {},
+ fragmentConstants1: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ ])
+ )
+ .fn(async t => {
+ const format = 'bgra8unorm';
+ const vertexModule = t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader,
+ });
+
+ const fragmentModule = t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader,
+ });
+
+ const createPipelineFn = async (
+ vertexConstants: Record<string, GPUPipelineConstantValue>,
+ fragmentConstants: Record<string, GPUPipelineConstantValue>
+ ) => {
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: vertexModule,
+ entryPoint: 'main',
+ constants: vertexConstants,
+ },
+ fragment: {
+ module: fragmentModule,
+ entryPoint: 'main',
+ targets: [{ format }],
+ constants: fragmentConstants,
+ },
+ primitive: {
+ topology: 'triangle-list',
+ frontFace: 'ccw',
+ cullMode: 'back',
+ },
+ };
+
+ return t.params.isAsync
+ ? t.device.createRenderPipelineAsync(descriptor)
+ : t.device.createRenderPipeline(descriptor);
+ };
+
+ const pipeline0 = await createPipelineFn(
+ t.params.vertexConstants0,
+ t.params.fragmentConstants0
+ );
+ const pipeline1 = await createPipelineFn(
+ t.params.vertexConstants1,
+ t.params.fragmentConstants1
+ );
+
+ const renderTarget0 = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ const renderTarget1 = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass0 = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget0.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A,
+ },
+ loadOp: 'clear',
+ },
+ ],
+ });
+ pass0.setPipeline(pipeline0);
+ pass0.draw(3);
+ pass0.end();
+
+ const pass1 = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget1.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A,
+ },
+ loadOp: 'clear',
+ },
+ ],
+ });
+ pass1.setPipeline(pipeline1);
+ pass1.draw(3);
+ pass1.end();
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSingleColor(renderTarget0, format, {
+ size: [1, 1, 1],
+ exp: t.params.expected0,
+ });
+ t.expectSingleColor(renderTarget1, format, {
+ size: [1, 1, 1],
+ exp: t.params.expected1,
+ });
+ });
+
+g.test('multi_entry_points')
+ .desc(
+ `Test that when the same module is shared by vertex and fragment shader, the constant values are still being used correctly.`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .beginSubcases()
+ .combineWithParams([
+ {
+ expected: { R: 0.8, G: 0.4, B: 0.2, A: 1.0 },
+ vertexConstants: { A: 4.0, B: 4.0 } as Record<string, GPUPipelineConstantValue>,
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ {
+ expected: { R: 0.8, G: 0.4, B: 0.2, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ {
+ expected: kClearValueResult,
+ vertexConstants: { A: -3.0 },
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 } as Record<
+ string,
+ GPUPipelineConstantValue
+ >,
+ },
+ ])
+ )
+ .fn(async t => {
+ const format = 'bgra8unorm';
+ const module = t.device.createShaderModule({
+ code: `
+ override A: f32 = 3.0;
+ override B: f32 = 3.0;
+ override C: f32;
+ override D: f32;
+
+ @vertex fn vertexMain(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, A),
+ vec2<f32>(-1.0, -A),
+ vec2<f32>(B, 0.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @fragment fn fragmentMain()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(A, B, C, D);
+ }
+ `,
+ });
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module,
+ entryPoint: 'vertexMain',
+ constants: t.params.vertexConstants,
+ },
+ {
+ module,
+ entryPoint: 'fragmentMain',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }],
+ }
+ );
+ });