export const description = `Testing render pipeline using overridable constants in vertex stage and fragment stage.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';import { PerTexelComponent } from '../../../util/texture/texel_data.js';class F extends AllFeaturesMaxLimitsGPUTest {async expectShaderOutputWithConstants(isAsync: boolean,format: GPUTextureFormat,expected: PerTexelComponent<number>,vertex: GPUVertexState,fragment: GPUFragmentState) {const renderTarget = this.createTextureTracked({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(),clearValue: {r: kClearValueResult.R,g: kClearValueResult.G,b: kClearValueResult.B,a: kClearValueResult.A,},loadOp: 'clear',storeOp: 'store',},],});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 }],});});const kPrecisionTestFormat = 'rgba32float';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.141592653589793, G: 1.0, B: 1.0, A: 1.0 },vertexConstants: {},fragmentConstants: { R: 3.141592653589793 } as Record<string, GPUPipelineConstantValue>,},])).fn(async t => {const format = kPrecisionTestFormat;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.createTextureTracked({format,size: { width: 1, height: 1, depthOrArrayLayers: 1 },usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,});const renderTarget1 = t.createTextureTracked({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(),clearValue: {r: kClearValueResult.R,g: kClearValueResult.G,b: kClearValueResult.B,a: kClearValueResult.A,},loadOp: 'clear',storeOp: 'store',},],});pass0.setPipeline(pipeline0);pass0.draw(3);pass0.end();const pass1 = encoder.beginRenderPass({colorAttachments: [{view: renderTarget1.createView(),clearValue: {r: kClearValueResult.R,g: kClearValueResult.G,b: kClearValueResult.B,a: kClearValueResult.A,},loadOp: 'clear',storeOp: 'store',},],});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 }],});});