diff options
Diffstat (limited to '')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/stress/render/render_pass.spec.ts | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/stress/render/render_pass.spec.ts b/dom/webgpu/tests/cts/checkout/src/stress/render/render_pass.spec.ts new file mode 100644 index 0000000000..d064e5f95b --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/stress/render/render_pass.spec.ts @@ -0,0 +1,353 @@ +export const description = ` +Stress tests covering GPURenderPassEncoder usage. +`; + +import { makeTestGroup } from '../../common/framework/test_group.js'; +import { range } from '../../common/util/util.js'; +import { GPUTest } from '../../webgpu/gpu_test.js'; + +export const g = makeTestGroup(GPUTest); + +g.test('many') + .desc( + `Tests execution of a huge number of render passes using the same GPURenderPipeline. This uses +a single render pass for every output fragment, with each pass executing a one-vertex draw call.` + ) + .fn(async t => { + const kSize = 1024; + const module = t.device.createShaderModule({ + code: ` + @vertex fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { + let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); + let r = vec2<f32>(1.0 / f32(${kSize})); + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + @fragment fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 1.0, 1.0); + } + `, + }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + const renderTarget = t.device.createTexture({ + size: [kSize, kSize], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }; + const encoder = t.device.createCommandEncoder(); + range(kSize * kSize, i => { + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + pass.draw(1, 1, i); + pass.end(); + }); + t.device.queue.submit([encoder.finish()]); + t.expectSingleColor(renderTarget, 'rgba8unorm', { + size: [kSize, kSize, 1], + exp: { R: 1, G: 0, B: 1, A: 1 }, + }); + }); + +g.test('pipeline_churn') + .desc( + `Tests execution of a large number of render pipelines, each within its own render pass. Each +pass does a single draw call, with one pass per output fragment.` + ) + .fn(async t => { + const kWidth = 64; + const kHeight = 8; + const module = t.device.createShaderModule({ + code: ` + @vertex fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { + let position = vec2<f32>(f32(index % ${kWidth}u), f32(index / ${kWidth}u)); + let size = vec2<f32>(f32(${kWidth}), f32(${kHeight})); + let r = vec2<f32>(1.0) / size; + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + @fragment fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 1.0, 1.0); + } + `, + }); + const renderTarget = t.device.createTexture({ + size: [kWidth, kHeight], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + const depthTarget = t.device.createTexture({ + size: [kWidth, kHeight], + usage: GPUTextureUsage.RENDER_ATTACHMENT, + format: 'depth24plus-stencil8', + }); + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + depthStencilAttachment: { + view: depthTarget.createView(), + depthLoadOp: 'load', + depthStoreOp: 'store', + stencilLoadOp: 'load', + stencilStoreOp: 'discard', + }, + }; + const encoder = t.device.createCommandEncoder(); + range(kWidth * kHeight, i => { + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + depthStencil: { + format: 'depth24plus-stencil8', + + // Not really used, but it ensures that each pipeline is unique. + depthBias: i, + }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + pass.draw(1, 1, i); + pass.end(); + }); + t.device.queue.submit([encoder.finish()]); + t.expectSingleColor(renderTarget, 'rgba8unorm', { + size: [kWidth, kHeight, 1], + exp: { R: 1, G: 0, B: 1, A: 1 }, + }); + }); + +g.test('bind_group_churn') + .desc( + `Tests execution of render passes which switch between a huge number of bind groups. This uses +a single render pass with a single pipeline, and one draw call per fragment of the output texture. +Each draw call is made with a unique bind group 0, with binding 0 referencing a unique uniform +buffer.` + ) + .fn(async t => { + const kSize = 128; + const module = t.device.createShaderModule({ + code: ` + struct Uniforms { index: u32, }; + @group(0) @binding(0) var<uniform> uniforms: Uniforms; + @vertex fn vmain() -> @builtin(position) vec4<f32> { + let index = uniforms.index; + let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); + let r = vec2<f32>(1.0 / f32(${kSize})); + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + @fragment fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 1.0, 1.0); + } + `, + }); + const layout = t.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: 'uniform' }, + }, + ], + }); + const pipeline = t.device.createRenderPipeline({ + layout: t.device.createPipelineLayout({ bindGroupLayouts: [layout] }), + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + const renderTarget = t.device.createTexture({ + size: [kSize, kSize], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }; + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + range(kSize * kSize, i => { + const buffer = t.device.createBuffer({ + size: 4, + usage: GPUBufferUsage.UNIFORM, + mappedAtCreation: true, + }); + new Uint32Array(buffer.getMappedRange())[0] = i; + buffer.unmap(); + pass.setBindGroup( + 0, + t.device.createBindGroup({ layout, entries: [{ binding: 0, resource: { buffer } }] }) + ); + pass.draw(1, 1); + }); + pass.end(); + t.device.queue.submit([encoder.finish()]); + t.expectSingleColor(renderTarget, 'rgba8unorm', { + size: [kSize, kSize, 1], + exp: { R: 1, G: 0, B: 1, A: 1 }, + }); + }); + +g.test('many_draws') + .desc( + `Tests execution of render passes with a huge number of draw calls. This uses a single +render pass with a single pipeline, and one draw call per fragment of the output texture.` + ) + .fn(async t => { + const kSize = 4096; + const module = t.device.createShaderModule({ + code: ` + @vertex fn vmain(@builtin(vertex_index) index: u32) + -> @builtin(position) vec4<f32> { + let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); + let r = vec2<f32>(1.0 / f32(${kSize})); + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + @fragment fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 1.0, 1.0); + } + `, + }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + const renderTarget = t.device.createTexture({ + size: [kSize, kSize], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }; + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + range(kSize * kSize, i => pass.draw(1, 1, i)); + pass.end(); + t.device.queue.submit([encoder.finish()]); + t.expectSingleColor(renderTarget, 'rgba8unorm', { + size: [kSize, kSize, 1], + exp: { R: 1, G: 0, B: 1, A: 1 }, + }); + }); + +g.test('huge_draws') + .desc( + `Tests execution of several render passes with huge draw calls. Each pass uses a single draw +call which draws multiple vertices for each fragment of a large output texture.` + ) + .fn(async t => { + const kSize = 32768; + const kTextureSize = 4096; + const kVertsPerFragment = (kSize * kSize) / (kTextureSize * kTextureSize); + const module = t.device.createShaderModule({ + code: ` + @vertex fn vmain(@builtin(vertex_index) vert_index: u32) + -> @builtin(position) vec4<f32> { + let index = vert_index / ${kVertsPerFragment}u; + let position = vec2<f32>(f32(index % ${kTextureSize}u), f32(index / ${kTextureSize}u)); + let r = vec2<f32>(1.0 / f32(${kTextureSize})); + let a = 2.0 * r; + let b = r - vec2<f32>(1.0); + return vec4<f32>(fma(position, a, b), 0.0, 1.0); + } + @fragment fn fmain() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 0.0, 1.0, 1.0); + } + `, + }); + const pipeline = t.device.createRenderPipeline({ + layout: 'auto', + vertex: { module, entryPoint: 'vmain', buffers: [] }, + primitive: { topology: 'point-list' }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module, + entryPoint: 'fmain', + }, + }); + const renderTarget = t.device.createTexture({ + size: [kTextureSize, kTextureSize], + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: 'rgba8unorm', + }); + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: renderTarget.createView(), + loadOp: 'load', + storeOp: 'store', + }, + ], + }; + + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + pass.draw(kSize * kSize); + pass.end(); + t.device.queue.submit([encoder.finish()]); + t.expectSingleColor(renderTarget, 'rgba8unorm', { + size: [kTextureSize, kTextureSize, 1], + exp: { R: 1, G: 0, B: 1, A: 1 }, + }); + }); |