diff options
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.ts | 456 |
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 }], + } + ); + }); |