diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pass/resolve.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pass/resolve.spec.ts | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pass/resolve.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pass/resolve.spec.ts new file mode 100644 index 0000000000..46b03e7b39 --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pass/resolve.spec.ts @@ -0,0 +1,183 @@ +export const description = `API Operation Tests for RenderPass StoreOp. +Tests a render pass with a resolveTarget resolves correctly for many combinations of: + - number of color attachments, some with and some without a resolveTarget + - renderPass storeOp set to {'store', 'discard'} + - resolveTarget mip level {0, >0} (TODO?: different mip level from colorAttachment) + - resolveTarget {2d array layer, TODO: 3d slice} {0, >0} with {2d, TODO: 3d} resolveTarget + (TODO?: different z from colorAttachment) + - TODO: test all renderable color formats + - TODO: test that any not-resolved attachments are rendered to correctly. + - TODO: test different loadOps + - TODO?: resolveTarget mip level {0, >0} (TODO?: different mip level from colorAttachment) + - TODO?: resolveTarget {2d array layer, TODO: 3d slice} {0, >0} with {2d, TODO: 3d} resolveTarget + (different z from colorAttachment) +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; + +const kSlotsToResolve = [ + [0, 2], + [1, 3], + [0, 1, 2, 3], +]; + +const kSize = 4; +const kFormat: GPUTextureFormat = 'rgba8unorm'; + +export const g = makeTestGroup(TextureTestMixin(GPUTest)); + +g.test('render_pass_resolve') + .params(u => + u + .combine('storeOperation', ['discard', 'store'] as const) + .beginSubcases() + .combine('numColorAttachments', [2, 4] as const) + .combine('slotsToResolve', kSlotsToResolve) + .combine('resolveTargetBaseMipLevel', [0, 1] as const) + .combine('resolveTargetBaseArrayLayer', [0, 1] as const) + ) + .fn(t => { + const targets: GPUColorTargetState[] = []; + for (let i = 0; i < t.params.numColorAttachments; i++) { + targets.push({ format: kFormat }); + } + + // These shaders will draw a white triangle into a texture. After draw, the top left + // half of the texture will be white, and the bottom right half will be unchanged. When this + // texture is resolved, there will be two distinct colors in each portion of the texture, as + // well as a line between the portions that contain the midpoint color due to the multisample + // resolve. + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ + code: ` + @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, -1.0), + vec2<f32>(-1.0, 1.0), + vec2<f32>( 1.0, 1.0)); + return vec4<f32>(pos[VertexIndex], 0.0, 1.0); + }`, + }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ + code: ` + struct Output { + @location(0) fragColor0 : vec4<f32>, + @location(1) fragColor1 : vec4<f32>, + @location(2) fragColor2 : vec4<f32>, + @location(3) fragColor3 : vec4<f32>, + }; + + @fragment fn main() -> Output { + return Output( + vec4<f32>(1.0, 1.0, 1.0, 1.0), + vec4<f32>(1.0, 1.0, 1.0, 1.0), + vec4<f32>(1.0, 1.0, 1.0, 1.0), + vec4<f32>(1.0, 1.0, 1.0, 1.0) + ); + }`, + }), + entryPoint: 'main', + targets, + }, + primitive: { topology: 'triangle-list' }, + multisample: { count: 4 }, + }); + + const resolveTargets: GPUTexture[] = []; + const renderPassColorAttachments: GPURenderPassColorAttachment[] = []; + + // The resolve target must be the same size as the color attachment. If we're resolving to mip + // level 1, the resolve target base mip level should be 2x the color attachment size. + const kResolveTargetSize = kSize << t.params.resolveTargetBaseMipLevel; + + for (let i = 0; i < t.params.numColorAttachments; i++) { + const colorAttachment = t.device.createTexture({ + format: kFormat, + size: { width: kSize, height: kSize, depthOrArrayLayers: 1 }, + sampleCount: 4, + mipLevelCount: 1, + usage: + GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + if (t.params.slotsToResolve.includes(i)) { + const colorAttachment = t.device.createTexture({ + format: kFormat, + size: { width: kSize, height: kSize, depthOrArrayLayers: 1 }, + sampleCount: 4, + mipLevelCount: 1, + usage: + GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const resolveTarget = t.device.createTexture({ + format: kFormat, + size: { + width: kResolveTargetSize, + height: kResolveTargetSize, + depthOrArrayLayers: t.params.resolveTargetBaseArrayLayer + 1, + }, + sampleCount: 1, + mipLevelCount: t.params.resolveTargetBaseMipLevel + 1, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + // Clear to black for the load operation. After the draw, the top left half of the attachment + // will be white and the bottom right half will be black. + renderPassColorAttachments.push({ + view: colorAttachment.createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }, + loadOp: 'clear', + storeOp: t.params.storeOperation, + resolveTarget: resolveTarget.createView({ + baseMipLevel: t.params.resolveTargetBaseMipLevel, + baseArrayLayer: t.params.resolveTargetBaseArrayLayer, + }), + }); + + resolveTargets.push(resolveTarget); + } else { + renderPassColorAttachments.push({ + view: colorAttachment.createView(), + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }, + loadOp: 'clear', + storeOp: t.params.storeOperation, + }); + } + } + + const encoder = t.device.createCommandEncoder(); + + const pass = encoder.beginRenderPass({ + colorAttachments: renderPassColorAttachments, + }); + pass.setPipeline(pipeline); + pass.draw(3); + pass.end(); + t.device.queue.submit([encoder.finish()]); + + // Verify the resolve targets contain the correct values. Note that we use z to specify the + // array layer from which to pull the pixels for testing. + const z = t.params.resolveTargetBaseArrayLayer; + for (const resolveTarget of resolveTargets) { + t.expectSinglePixelComparisonsAreOkInTexture( + { texture: resolveTarget, mipLevel: t.params.resolveTargetBaseMipLevel }, + [ + // Top left pixel should be {1.0, 1.0, 1.0, 1.0}. + { coord: { x: 0, y: 0, z }, exp: { R: 1.0, G: 1.0, B: 1.0, A: 1.0 } }, + // Bottom right pixel should be {0, 0, 0, 0}. + { coord: { x: kSize - 1, y: kSize - 1, z }, exp: { R: 0, G: 0, B: 0, A: 0 } }, + // Top right pixel should be {0.5, 0.5, 0.5, 0.5} due to the multisampled resolve. + { coord: { x: kSize - 1, y: 0, z }, exp: { R: 0.5, G: 0.5, B: 0.5, A: 0.5 } }, + ] + ); + } + }); |