diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts new file mode 100644 index 0000000000..00069b777f --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts @@ -0,0 +1,806 @@ +export const description = ` +Tests that the final sample mask is the logical AND of all the relevant masks, including +the rasterization mask, sample mask, fragment output mask, and alpha to coverage mask (when alphaToCoverageEnabled === true). + +Also tested: +- The positions of samples in the standard sample patterns. +- Per-sample interpolation sampling: @interpolate(perspective, sample). + +TODO: add a test without a 0th color attachment (sparse color attachment), with different color attachments and alpha value output. +The cross-platform behavior is unknown. could be any of: +- coverage is always 100% +- coverage is always 0% +- it uses the first non-null attachment +- it's an error +Details could be found at: https://github.com/gpuweb/cts/issues/2201 +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { assert, range } from '../../../../common/util/util.js'; +import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { checkElementsPassPredicate, checkElementsEqual } from '../../../util/check_contents.js'; +import { TypeF32, TypeU32 } from '../../../util/conversion.js'; +import { TexelView } from '../../../util/texture/texel_view.js'; + +const kColors = [ + // Red + new Uint8Array([0xff, 0, 0, 0xff]), + // Green + new Uint8Array([0, 0xff, 0, 0xff]), + // Blue + new Uint8Array([0, 0, 0xff, 0xff]), + // Yellow + new Uint8Array([0xff, 0xff, 0, 0xff]), +]; + +const kDepthClearValue = 1.0; +const kDepthWriteValue = 0.0; +const kStencilClearValue = 0; +const kStencilReferenceValue = 0xff; + +// Format of the render target and resolve target +const format = 'rgba8unorm'; + +// Format of depth stencil attachment +const depthStencilFormat = 'depth24plus-stencil8'; + +const kRenderTargetSize = 1; + +function hasSample( + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMask: number, + sampleIndex: number = 0 +): boolean { + return (rasterizationMask & sampleMask & fragmentShaderOutputMask & (1 << sampleIndex)) > 0; +} + +function getExpectedColorData( + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMaskOrAlphaToCoverageMask: number +) { + const expectedData = new Float32Array(sampleCount * 4); + if (sampleCount === 1) { + if (hasSample(rasterizationMask, sampleMask, fragmentShaderOutputMaskOrAlphaToCoverageMask)) { + // Texel 3 is sampled at the pixel center + expectedData[0] = kColors[3][0] / 0xff; + expectedData[1] = kColors[3][1] / 0xff; + expectedData[2] = kColors[3][2] / 0xff; + expectedData[3] = kColors[3][3] / 0xff; + } + } else { + for (let i = 0; i < sampleCount; i++) { + if ( + hasSample(rasterizationMask, sampleMask, fragmentShaderOutputMaskOrAlphaToCoverageMask, i) + ) { + const o = i * 4; + expectedData[o + 0] = kColors[i][0] / 0xff; + expectedData[o + 1] = kColors[i][1] / 0xff; + expectedData[o + 2] = kColors[i][2] / 0xff; + expectedData[o + 3] = kColors[i][3] / 0xff; + } + } + } + return expectedData; +} + +function getExpectedDepthData( + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMaskOrAlphaToCoverageMask: number +) { + const expectedData = new Float32Array(sampleCount); + for (let i = 0; i < sampleCount; i++) { + const s = hasSample( + rasterizationMask, + sampleMask, + fragmentShaderOutputMaskOrAlphaToCoverageMask, + i + ); + expectedData[i] = s ? kDepthWriteValue : kDepthClearValue; + } + return expectedData; +} + +function getExpectedStencilData( + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMaskOrAlphaToCoverageMask: number +) { + const expectedData = new Uint32Array(sampleCount); + for (let i = 0; i < sampleCount; i++) { + const s = hasSample( + rasterizationMask, + sampleMask, + fragmentShaderOutputMaskOrAlphaToCoverageMask, + i + ); + expectedData[i] = s ? kStencilReferenceValue : kStencilClearValue; + } + return expectedData; +} + +const kSampleMaskTestShader = ` +struct Varyings { + @builtin(position) Position : vec4<f32>, + @location(0) @interpolate(flat) uvFlat : vec2<f32>, + @location(1) @interpolate(perspective, sample) uvInterpolated : vec2<f32>, +} + +// +// Vertex shader +// + +@vertex +fn vmain(@builtin(vertex_index) VertexIndex : u32, + @builtin(instance_index) InstanceIndex : u32) -> Varyings { + // Standard sample locations within a pixel, where the pixel ranges from (-1,-1) to (1,1), and is + // centered at (0,0) (NDC - the test uses a 1x1 render target). + // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels + var sampleCenters = array( + // sampleCount = 1 + vec2f(0, 0), + // sampleCount = 4 + vec2f(-2, 6) / 8, + vec2f( 6, 2) / 8, + vec2f(-6, -2) / 8, + vec2f( 2, -6) / 8, + ); + // A tiny quad to draw around the sample center to ensure we hit only the expected point. + let kTinyQuadRadius = 1.0 / 32; + var tinyQuad = array( + vec2f( kTinyQuadRadius, kTinyQuadRadius), + vec2f( kTinyQuadRadius, -kTinyQuadRadius), + vec2f(-kTinyQuadRadius, -kTinyQuadRadius), + vec2f( kTinyQuadRadius, kTinyQuadRadius), + vec2f(-kTinyQuadRadius, -kTinyQuadRadius), + vec2f(-kTinyQuadRadius, kTinyQuadRadius), + ); + + var uvsFlat = array( + // sampleCount = 1 + // Note: avoids hitting the point between the 4 texels. + vec2f(0.51, 0.51), + // sampleCount = 4 + vec2f(0.25, 0.25), + vec2f(0.75, 0.25), + vec2f(0.25, 0.75), + vec2f(0.75, 0.75), + ); + var uvsInterpolated = array( + // center quad + // Note: the interpolated point will be exactly in the middle of the 4 texels. + // The test expects to get texel 1,1 (the 3rd texel) in this case. + vec2f(1.0, 0.0), + vec2f(1.0, 1.0), + vec2f(0.0, 1.0), + vec2f(1.0, 0.0), + vec2f(0.0, 1.0), + vec2f(0.0, 0.0), + + // top-left quad (texel 0) + vec2f(0.5, 0.0), + vec2f(0.5, 0.5), + vec2f(0.0, 0.5), + vec2f(0.5, 0.0), + vec2f(0.0, 0.5), + vec2f(0.0, 0.0), + + // top-right quad (texel 1) + vec2f(1.0, 0.0), + vec2f(1.0, 0.5), + vec2f(0.5, 0.5), + vec2f(1.0, 0.0), + vec2f(0.5, 0.5), + vec2f(0.5, 0.0), + + // bottom-left quad (texel 2) + vec2f(0.5, 0.5), + vec2f(0.5, 1.0), + vec2f(0.0, 1.0), + vec2f(0.5, 0.5), + vec2f(0.0, 1.0), + vec2f(0.0, 0.5), + + // bottom-right quad (texel 3) + vec2f(1.0, 0.5), + vec2f(1.0, 1.0), + vec2f(0.5, 1.0), + vec2f(1.0, 0.5), + vec2f(0.5, 1.0), + vec2f(0.5, 0.5) + ); + + var output : Varyings; + let pos = sampleCenters[InstanceIndex] + tinyQuad[VertexIndex]; + output.Position = vec4(pos, ${kDepthWriteValue}, 1.0); + output.uvFlat = uvsFlat[InstanceIndex]; + output.uvInterpolated = uvsInterpolated[InstanceIndex * 6 + VertexIndex]; + return output; +} + +// +// Fragment shaders +// + +@group(0) @binding(0) var mySampler: sampler; +@group(0) @binding(1) var myTexture: texture_2d<f32>; + +// For test named 'fragment_output_mask' + +@group(0) @binding(2) var<uniform> fragMask: u32; +struct FragmentOutput1 { + @builtin(sample_mask) mask : u32, + @location(0) color : vec4<f32>, +} +@fragment fn fmain__fragment_output_mask__flat(varyings: Varyings) -> FragmentOutput1 { + return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvFlat)); +} +@fragment fn fmain__fragment_output_mask__interp(varyings: Varyings) -> FragmentOutput1 { + return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvInterpolated)); +} + +// For test named 'alpha_to_coverage_mask' + +struct FragmentOutput2 { + @location(0) color0 : vec4<f32>, + @location(1) color1 : vec4<f32>, +} +@group(0) @binding(2) var<uniform> alpha: vec2<f32>; +@fragment fn fmain__alpha_to_coverage_mask__flat(varyings: Varyings) -> FragmentOutput2 { + var c = textureSample(myTexture, mySampler, varyings.uvFlat); + return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1])); +} +@fragment fn fmain__alpha_to_coverage_mask__interp(varyings: Varyings) -> FragmentOutput2 { + var c = textureSample(myTexture, mySampler, varyings.uvInterpolated); + return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1])); +} +`; + +class F extends TextureTestMixin(GPUTest) { + private sampleTexture: GPUTexture | undefined; + private sampler: GPUSampler | undefined; + + override async init() { + await super.init(); + if (this.isCompatibility) { + this.skip('WGSL sample_mask is not supported in compatibility mode'); + } + // Create a 2x2 color texture to sample from + // texel 0 - Red + // texel 1 - Green + // texel 2 - Blue + // texel 3 - Yellow + const kSampleTextureSize = 2; + this.sampleTexture = this.createTextureFromTexelView( + TexelView.fromTexelsAsBytes(format, coord => { + const id = coord.x + coord.y * kSampleTextureSize; + return kColors[id]; + }), + { + size: [kSampleTextureSize, kSampleTextureSize, 1], + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + } + ); + + this.sampler = this.device.createSampler({ + magFilter: 'nearest', + minFilter: 'nearest', + }); + } + + GetTargetTexture( + sampleCount: number, + rasterizationMask: number, + pipeline: GPURenderPipeline, + uniformBuffer: GPUBuffer, + colorTargetsCount: number = 1 + ): { color: GPUTexture; depthStencil: GPUTexture } { + assert(this.sampleTexture !== undefined); + assert(this.sampler !== undefined); + + const uniformBindGroup = this.device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { + binding: 0, + resource: this.sampler, + }, + { + binding: 1, + resource: this.sampleTexture.createView(), + }, + { + binding: 2, + resource: { + buffer: uniformBuffer, + }, + }, + ], + }); + + const renderTargetTextures = []; + const resolveTargetTextures: (GPUTexture | null)[] = []; + for (let i = 0; i < colorTargetsCount; i++) { + const renderTargetTexture = this.device.createTexture({ + format, + size: { + width: kRenderTargetSize, + height: kRenderTargetSize, + depthOrArrayLayers: 1, + }, + sampleCount, + mipLevelCount: 1, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + }); + renderTargetTextures.push(renderTargetTexture); + + const resolveTargetTexture = + sampleCount === 1 + ? null + : this.device.createTexture({ + format, + size: { + width: kRenderTargetSize, + height: kRenderTargetSize, + depthOrArrayLayers: 1, + }, + sampleCount: 1, + mipLevelCount: 1, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + resolveTargetTextures.push(resolveTargetTexture); + } + + const depthStencilTexture = this.device.createTexture({ + size: { + width: kRenderTargetSize, + height: kRenderTargetSize, + }, + format: depthStencilFormat, + sampleCount, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + }); + + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: renderTargetTextures.map((renderTargetTexture, index) => { + return { + view: renderTargetTexture.createView(), + resolveTarget: resolveTargetTextures[index]?.createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }, + loadOp: 'clear', + storeOp: 'store', + }; + }), + depthStencilAttachment: { + view: depthStencilTexture.createView(), + depthClearValue: kDepthClearValue, + depthLoadOp: 'clear', + depthStoreOp: 'store', + stencilClearValue: kStencilClearValue, + stencilLoadOp: 'clear', + stencilStoreOp: 'store', + }, + }; + const commandEncoder = this.device.createCommandEncoder(); + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(pipeline); + passEncoder.setBindGroup(0, uniformBindGroup); + passEncoder.setStencilReference(kStencilReferenceValue); + + if (sampleCount === 1) { + if ((rasterizationMask & 1) !== 0) { + // draw center quad + passEncoder.draw(6, 1, 0, 0); + } + } else { + assert(sampleCount === 4); + if ((rasterizationMask & 1) !== 0) { + // draw top-left quad + passEncoder.draw(6, 1, 0, 1); + } + if ((rasterizationMask & 2) !== 0) { + // draw top-right quad + passEncoder.draw(6, 1, 0, 2); + } + if ((rasterizationMask & 4) !== 0) { + // draw bottom-left quad + passEncoder.draw(6, 1, 0, 3); + } + if ((rasterizationMask & 8) !== 0) { + // draw bottom-right quad + passEncoder.draw(6, 1, 0, 4); + } + } + passEncoder.end(); + this.device.queue.submit([commandEncoder.finish()]); + + return { + color: renderTargetTextures[0], + depthStencil: depthStencilTexture, + }; + } + + CheckColorAttachmentResult( + texture: GPUTexture, + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMask: number + ) { + const buffer = this.copySinglePixelTextureToBufferUsingComputePass( + TypeF32, // correspond to 'rgba8unorm' format + 4, + texture.createView(), + sampleCount + ); + + const expected = getExpectedColorData( + sampleCount, + rasterizationMask, + sampleMask, + fragmentShaderOutputMask + ); + this.expectGPUBufferValuesEqual(buffer, expected); + } + + CheckDepthStencilResult( + aspect: 'depth-only' | 'stencil-only', + depthStencilTexture: GPUTexture, + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + fragmentShaderOutputMask: number + ) { + const buffer = this.copySinglePixelTextureToBufferUsingComputePass( + // Use f32 as the scalar type for depth (depth24plus, depth32float) + // Use u32 as the scalar type for stencil (stencil8) + aspect === 'depth-only' ? TypeF32 : TypeU32, + 1, + depthStencilTexture.createView({ aspect }), + sampleCount + ); + + const expected = + aspect === 'depth-only' + ? getExpectedDepthData(sampleCount, rasterizationMask, sampleMask, fragmentShaderOutputMask) + : getExpectedStencilData( + sampleCount, + rasterizationMask, + sampleMask, + fragmentShaderOutputMask + ); + this.expectGPUBufferValuesEqual(buffer, expected); + } +} + +export const g = makeTestGroup(F); + +g.test('fragment_output_mask') + .desc( + ` +Tests that the final sample mask is the logical AND of all the relevant masks -- meaning that the samples +not included in the final mask are discarded on any attachments including +- color outputs +- depth tests +- stencil operations + +The test draws 0/1/1+ textured quads of which each sample in the standard 4-sample pattern results in a different color: +- Sample 0, Texel 0, top-left: Red +- Sample 1, Texel 1, top-left: Green +- Sample 2, Texel 2, top-left: Blue +- Sample 3, Texel 3, top-left: Yellow + +The test checks each sample value of the render target texture and depth stencil texture using a compute pass to +textureLoad each sample index from the texture and write to a storage buffer to compare with expected values. + +- for sampleCount = { 1, 4 } and various combinations of: + - rasterization mask = { 0, ..., 2 ** sampleCount - 1 } + - sample mask = { 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110 } + - fragment shader output @builtin(sample_mask) = { 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110 } +- [choosing 0b11110 because the 5th bit should be ignored] +` + ) + .params(u => + u + .combine('interpolated', [false, true]) + .combine('sampleCount', [1, 4] as const) + .expand('rasterizationMask', function* (p) { + const maxMask = 2 ** p.sampleCount - 1; + for (let i = 0; i <= maxMask; i++) { + yield i; + } + }) + .beginSubcases() + .combine('sampleMask', [ + 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110, + ] as const) + .combine('fragmentShaderOutputMask', [ + 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110, + ] as const) + ) + .fn(t => { + const { sampleCount, rasterizationMask, sampleMask, fragmentShaderOutputMask } = t.params; + + const fragmentMaskUniformBuffer = t.device.createBuffer({ + size: 4, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(fragmentMaskUniformBuffer); + t.device.queue.writeBuffer( + fragmentMaskUniformBuffer, + 0, + new Uint32Array([fragmentShaderOutputMask]) + ); + + const module = t.device.createShaderModule({ code: kSampleMaskTestShader }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain' }, + fragment: { + module, + entryPoint: `fmain__fragment_output_mask__${t.params.interpolated ? 'interp' : 'flat'}`, + targets: [{ format }], + }, + primitive: { topology: 'triangle-list' }, + multisample: { + count: sampleCount, + mask: sampleMask, + alphaToCoverageEnabled: false, + }, + depthStencil: { + format: depthStencilFormat, + depthWriteEnabled: true, + depthCompare: 'always', + + stencilFront: { + compare: 'always', + passOp: 'replace', + }, + stencilBack: { + compare: 'always', + passOp: 'replace', + }, + }, + }); + + const { color, depthStencil } = t.GetTargetTexture( + sampleCount, + rasterizationMask, + pipeline, + fragmentMaskUniformBuffer + ); + + t.CheckColorAttachmentResult( + color, + sampleCount, + rasterizationMask, + sampleMask, + fragmentShaderOutputMask + ); + + t.CheckDepthStencilResult( + 'depth-only', + depthStencil, + sampleCount, + rasterizationMask, + sampleMask, + fragmentShaderOutputMask + ); + + t.CheckDepthStencilResult( + 'stencil-only', + depthStencil, + sampleCount, + rasterizationMask, + sampleMask, + fragmentShaderOutputMask + ); + }); + +g.test('alpha_to_coverage_mask') + .desc( + ` +Test that alpha_to_coverage_mask is working properly with the alpha output of color target[0]. + +- for sampleCount = 4, alphaToCoverageEnabled = true and various combinations of: + - rasterization masks + - increasing alpha0 values of the color0 output including { < 0, = 0, = 1/16, = 2/16, ..., = 15/16, = 1, > 1 } + - alpha1 values of the color1 output = { 0, 0.5, 1.0 }. +- test that for a single pixel in { color0, color1 } { color0, depth, stencil } output the final sample mask is applied to it, moreover: + - if alpha0 is 0.0 or less then alpha to coverage mask is 0x0, + - if alpha0 is 1.0 or greater then alpha to coverage mask is 0xFFFFFFFF, + - that the number of bits in the alpha to coverage mask is non-decreasing, + - that the computation of alpha to coverage mask doesn't depend on any other color output than color0, + - (not included in the spec): that once a sample is included in the alpha to coverage sample mask + it will be included for any alpha greater than or equal to the current value. + +The algorithm of producing the alpha-to-coverage mask is platform-dependent. The test draws a different color +at each sample point. for any two alpha values (alpha and alpha') where 0 < alpha' < alpha < 1, the color values (color and color') must satisfy +color' <= color. +` + ) + .params(u => + u + .combine('interpolated', [false, true]) + .combine('sampleCount', [4] as const) + .expand('rasterizationMask', function* (p) { + const maxMask = 2 ** p.sampleCount - 1; + for (let i = 0; i <= maxMask; i++) { + yield i; + } + }) + .beginSubcases() + .combine('alpha1', [0.0, 0.5, 1.0] as const) + ) + .fn(async t => { + const { sampleCount, rasterizationMask, alpha1 } = t.params; + const sampleMask = 0xffffffff; + + const alphaValues = new Float32Array(4); // [alpha0, alpha1, 0, 0] + const alphaValueUniformBuffer = t.device.createBuffer({ + size: alphaValues.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + t.trackForCleanup(alphaValueUniformBuffer); + + const module = t.device.createShaderModule({ code: kSampleMaskTestShader }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain' }, + fragment: { + module, + entryPoint: `fmain__alpha_to_coverage_mask__${t.params.interpolated ? 'interp' : 'flat'}`, + targets: [{ format }, { format }], + }, + primitive: { topology: 'triangle-list' }, + multisample: { + count: sampleCount, + mask: sampleMask, + alphaToCoverageEnabled: true, + }, + depthStencil: { + format: depthStencilFormat, + depthWriteEnabled: true, + depthCompare: 'always', + + stencilFront: { + compare: 'always', + passOp: 'replace', + }, + stencilBack: { + compare: 'always', + passOp: 'replace', + }, + }, + }); + + // { < 0, = 0, = 1/16, = 2/16, ..., = 15/16, = 1, > 1 } + const alpha0ParamsArray = [-0.1, ...range(16, i => i / 16), 1.0, 1.1]; + + const colorResultPromises = []; + const depthResultPromises = []; + const stencilResultPromises = []; + + for (const alpha0 of alpha0ParamsArray) { + alphaValues[0] = alpha0; + alphaValues[1] = alpha1; + t.device.queue.writeBuffer(alphaValueUniformBuffer, 0, alphaValues); + + const { color, depthStencil } = t.GetTargetTexture( + sampleCount, + rasterizationMask, + pipeline, + alphaValueUniformBuffer, + 2 + ); + + const colorBuffer = t.copySinglePixelTextureToBufferUsingComputePass( + TypeF32, // correspond to 'rgba8unorm' format + 4, + color.createView(), + sampleCount + ); + const colorResult = t.readGPUBufferRangeTyped(colorBuffer, { + type: Float32Array, + typedLength: colorBuffer.size / Float32Array.BYTES_PER_ELEMENT, + }); + colorResultPromises.push(colorResult); + + const depthBuffer = t.copySinglePixelTextureToBufferUsingComputePass( + TypeF32, // correspond to 'depth24plus-stencil8' format + 1, + depthStencil.createView({ aspect: 'depth-only' }), + sampleCount + ); + const depthResult = t.readGPUBufferRangeTyped(depthBuffer, { + type: Float32Array, + typedLength: depthBuffer.size / Float32Array.BYTES_PER_ELEMENT, + }); + depthResultPromises.push(depthResult); + + const stencilBuffer = t.copySinglePixelTextureToBufferUsingComputePass( + TypeU32, // correspond to 'depth24plus-stencil8' format + 1, + depthStencil.createView({ aspect: 'stencil-only' }), + sampleCount + ); + const stencilResult = t.readGPUBufferRangeTyped(stencilBuffer, { + type: Uint32Array, + typedLength: stencilBuffer.size / Uint32Array.BYTES_PER_ELEMENT, + }); + stencilResultPromises.push(stencilResult); + } + + const resultsArray = await Promise.all([ + Promise.all(colorResultPromises), + Promise.all(depthResultPromises), + Promise.all(stencilResultPromises), + ]); + + const checkResults = ( + results: { data: Float32Array | Uint32Array; cleanup(): void }[], + getExpectedDataFn: ( + sampleCount: number, + rasterizationMask: number, + sampleMask: number, + alphaToCoverageMask: number + ) => Float32Array | Uint32Array, + // Alpha to coverage mask should be non-decreasing as the alpha value goes up + // Result value of color and stencil is in positive correlation to alpha + // Result value of depth is in negative correlation to alpha + positiveCorrelation: boolean + ) => { + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const alpha0 = alpha0ParamsArray[i]; + + if (alpha0 <= 0) { + const expected = getExpectedDataFn(sampleCount, rasterizationMask, sampleMask, 0x0); + const check = checkElementsEqual(result.data, expected); + t.expectOK(check); + } else if (alpha0 >= 1) { + const expected = getExpectedDataFn( + sampleCount, + rasterizationMask, + sampleMask, + 0xffffffff + ); + const check = checkElementsEqual(result.data, expected); + t.expectOK(check); + } else { + assert(i > 0); + const prevResult = results[i - 1]; + const check = checkElementsPassPredicate( + result.data, + (index, value) => + positiveCorrelation + ? value >= prevResult.data[index] + : value <= prevResult.data[index], + {} + ); + t.expectOK(check); + } + } + + for (const result of results) { + result.cleanup(); + } + }; + + // Check color results + checkResults(resultsArray[0], getExpectedColorData, true); + + // Check depth results + checkResults(resultsArray[1], getExpectedDepthData, false); + + // Check stencil results + checkResults(resultsArray[2], getExpectedStencilData, true); + }); |