render_pass.spec.ts (11575B)
1 export const description = ` 2 Stress tests covering GPURenderPassEncoder usage. 3 `; 4 5 import { makeTestGroup } from '../../common/framework/test_group.js'; 6 import { range } from '../../common/util/util.js'; 7 import { GPUTest } from '../../webgpu/gpu_test.js'; 8 9 export const g = makeTestGroup(GPUTest); 10 11 g.test('many') 12 .desc( 13 `Tests execution of a huge number of render passes using the same GPURenderPipeline. This uses 14 a single render pass for every output fragment, with each pass executing a one-vertex draw call.` 15 ) 16 .fn(t => { 17 const kSize = 1024; 18 const module = t.device.createShaderModule({ 19 code: ` 20 @vertex fn vmain(@builtin(vertex_index) index: u32) 21 -> @builtin(position) vec4<f32> { 22 let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); 23 let r = vec2<f32>(1.0 / f32(${kSize})); 24 let a = 2.0 * r; 25 let b = r - vec2<f32>(1.0); 26 return vec4<f32>(fma(position, a, b), 0.0, 1.0); 27 } 28 @fragment fn fmain() -> @location(0) vec4<f32> { 29 return vec4<f32>(1.0, 0.0, 1.0, 1.0); 30 } 31 `, 32 }); 33 const pipeline = t.device.createRenderPipeline({ 34 layout: 'auto', 35 vertex: { module, entryPoint: 'vmain', buffers: [] }, 36 primitive: { topology: 'point-list' }, 37 fragment: { 38 targets: [{ format: 'rgba8unorm' }], 39 module, 40 entryPoint: 'fmain', 41 }, 42 }); 43 const renderTarget = t.createTextureTracked({ 44 size: [kSize, kSize], 45 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 46 format: 'rgba8unorm', 47 }); 48 const renderPassDescriptor: GPURenderPassDescriptor = { 49 colorAttachments: [ 50 { 51 view: renderTarget.createView(), 52 loadOp: 'load', 53 storeOp: 'store', 54 }, 55 ], 56 }; 57 const encoder = t.device.createCommandEncoder(); 58 range(kSize * kSize, i => { 59 const pass = encoder.beginRenderPass(renderPassDescriptor); 60 pass.setPipeline(pipeline); 61 pass.draw(1, 1, i); 62 pass.end(); 63 }); 64 t.device.queue.submit([encoder.finish()]); 65 t.expectSingleColor(renderTarget, 'rgba8unorm', { 66 size: [kSize, kSize, 1], 67 exp: { R: 1, G: 0, B: 1, A: 1 }, 68 }); 69 }); 70 71 g.test('pipeline_churn') 72 .desc( 73 `Tests execution of a large number of render pipelines, each within its own render pass. Each 74 pass does a single draw call, with one pass per output fragment.` 75 ) 76 .fn(t => { 77 const kWidth = 64; 78 const kHeight = 8; 79 const module = t.device.createShaderModule({ 80 code: ` 81 @vertex fn vmain(@builtin(vertex_index) index: u32) 82 -> @builtin(position) vec4<f32> { 83 let position = vec2<f32>(f32(index % ${kWidth}u), f32(index / ${kWidth}u)); 84 let size = vec2<f32>(f32(${kWidth}), f32(${kHeight})); 85 let r = vec2<f32>(1.0) / size; 86 let a = 2.0 * r; 87 let b = r - vec2<f32>(1.0); 88 return vec4<f32>(fma(position, a, b), 0.0, 1.0); 89 } 90 @fragment fn fmain() -> @location(0) vec4<f32> { 91 return vec4<f32>(1.0, 0.0, 1.0, 1.0); 92 } 93 `, 94 }); 95 const renderTarget = t.createTextureTracked({ 96 size: [kWidth, kHeight], 97 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 98 format: 'rgba8unorm', 99 }); 100 const depthTarget = t.createTextureTracked({ 101 size: [kWidth, kHeight], 102 usage: GPUTextureUsage.RENDER_ATTACHMENT, 103 format: 'depth24plus-stencil8', 104 }); 105 const renderPassDescriptor: GPURenderPassDescriptor = { 106 colorAttachments: [ 107 { 108 view: renderTarget.createView(), 109 loadOp: 'load', 110 storeOp: 'store', 111 }, 112 ], 113 depthStencilAttachment: { 114 view: depthTarget.createView(), 115 depthLoadOp: 'load', 116 depthStoreOp: 'store', 117 stencilLoadOp: 'load', 118 stencilStoreOp: 'discard', 119 }, 120 }; 121 const encoder = t.device.createCommandEncoder(); 122 range(kWidth * kHeight, i => { 123 const pipeline = t.device.createRenderPipeline({ 124 layout: 'auto', 125 vertex: { module, entryPoint: 'vmain', buffers: [] }, 126 primitive: { topology: 'point-list' }, 127 depthStencil: { 128 format: 'depth24plus-stencil8', 129 depthCompare: 'always', 130 depthWriteEnabled: false, 131 // Not really used, but it ensures that each pipeline is unique. 132 depthBias: i, 133 }, 134 fragment: { 135 targets: [{ format: 'rgba8unorm' }], 136 module, 137 entryPoint: 'fmain', 138 }, 139 }); 140 const pass = encoder.beginRenderPass(renderPassDescriptor); 141 pass.setPipeline(pipeline); 142 pass.draw(1, 1, i); 143 pass.end(); 144 }); 145 t.device.queue.submit([encoder.finish()]); 146 t.expectSingleColor(renderTarget, 'rgba8unorm', { 147 size: [kWidth, kHeight, 1], 148 exp: { R: 1, G: 0, B: 1, A: 1 }, 149 }); 150 }); 151 152 g.test('bind_group_churn') 153 .desc( 154 `Tests execution of render passes which switch between a huge number of bind groups. This uses 155 a single render pass with a single pipeline, and one draw call per fragment of the output texture. 156 Each draw call is made with a unique bind group 0, with binding 0 referencing a unique uniform 157 buffer.` 158 ) 159 .fn(t => { 160 const kSize = 128; 161 const module = t.device.createShaderModule({ 162 code: ` 163 struct Uniforms { index: u32, }; 164 @group(0) @binding(0) var<uniform> uniforms: Uniforms; 165 @vertex fn vmain() -> @builtin(position) vec4<f32> { 166 let index = uniforms.index; 167 let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); 168 let r = vec2<f32>(1.0 / f32(${kSize})); 169 let a = 2.0 * r; 170 let b = r - vec2<f32>(1.0); 171 return vec4<f32>(fma(position, a, b), 0.0, 1.0); 172 } 173 @fragment fn fmain() -> @location(0) vec4<f32> { 174 return vec4<f32>(1.0, 0.0, 1.0, 1.0); 175 } 176 `, 177 }); 178 const layout = t.device.createBindGroupLayout({ 179 entries: [ 180 { 181 binding: 0, 182 visibility: GPUShaderStage.VERTEX, 183 buffer: { type: 'uniform' }, 184 }, 185 ], 186 }); 187 const pipeline = t.device.createRenderPipeline({ 188 layout: t.device.createPipelineLayout({ bindGroupLayouts: [layout] }), 189 vertex: { module, entryPoint: 'vmain', buffers: [] }, 190 primitive: { topology: 'point-list' }, 191 fragment: { 192 targets: [{ format: 'rgba8unorm' }], 193 module, 194 entryPoint: 'fmain', 195 }, 196 }); 197 const renderTarget = t.createTextureTracked({ 198 size: [kSize, kSize], 199 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 200 format: 'rgba8unorm', 201 }); 202 const renderPassDescriptor: GPURenderPassDescriptor = { 203 colorAttachments: [ 204 { 205 view: renderTarget.createView(), 206 loadOp: 'load', 207 storeOp: 'store', 208 }, 209 ], 210 }; 211 const encoder = t.device.createCommandEncoder(); 212 const pass = encoder.beginRenderPass(renderPassDescriptor); 213 pass.setPipeline(pipeline); 214 range(kSize * kSize, i => { 215 const buffer = t.createBufferTracked({ 216 size: 4, 217 usage: GPUBufferUsage.UNIFORM, 218 mappedAtCreation: true, 219 }); 220 new Uint32Array(buffer.getMappedRange())[0] = i; 221 buffer.unmap(); 222 pass.setBindGroup( 223 0, 224 t.device.createBindGroup({ layout, entries: [{ binding: 0, resource: { buffer } }] }) 225 ); 226 pass.draw(1, 1); 227 }); 228 pass.end(); 229 t.device.queue.submit([encoder.finish()]); 230 t.expectSingleColor(renderTarget, 'rgba8unorm', { 231 size: [kSize, kSize, 1], 232 exp: { R: 1, G: 0, B: 1, A: 1 }, 233 }); 234 }); 235 236 g.test('many_draws') 237 .desc( 238 `Tests execution of render passes with a huge number of draw calls. This uses a single 239 render pass with a single pipeline, and one draw call per fragment of the output texture.` 240 ) 241 .fn(t => { 242 const kSize = 4096; 243 const module = t.device.createShaderModule({ 244 code: ` 245 @vertex fn vmain(@builtin(vertex_index) index: u32) 246 -> @builtin(position) vec4<f32> { 247 let position = vec2<f32>(f32(index % ${kSize}u), f32(index / ${kSize}u)); 248 let r = vec2<f32>(1.0 / f32(${kSize})); 249 let a = 2.0 * r; 250 let b = r - vec2<f32>(1.0); 251 return vec4<f32>(fma(position, a, b), 0.0, 1.0); 252 } 253 @fragment fn fmain() -> @location(0) vec4<f32> { 254 return vec4<f32>(1.0, 0.0, 1.0, 1.0); 255 } 256 `, 257 }); 258 const pipeline = t.device.createRenderPipeline({ 259 layout: 'auto', 260 vertex: { module, entryPoint: 'vmain', buffers: [] }, 261 primitive: { topology: 'point-list' }, 262 fragment: { 263 targets: [{ format: 'rgba8unorm' }], 264 module, 265 entryPoint: 'fmain', 266 }, 267 }); 268 const renderTarget = t.createTextureTracked({ 269 size: [kSize, kSize], 270 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 271 format: 'rgba8unorm', 272 }); 273 const renderPassDescriptor: GPURenderPassDescriptor = { 274 colorAttachments: [ 275 { 276 view: renderTarget.createView(), 277 loadOp: 'load', 278 storeOp: 'store', 279 }, 280 ], 281 }; 282 const encoder = t.device.createCommandEncoder(); 283 const pass = encoder.beginRenderPass(renderPassDescriptor); 284 pass.setPipeline(pipeline); 285 range(kSize * kSize, i => pass.draw(1, 1, i)); 286 pass.end(); 287 t.device.queue.submit([encoder.finish()]); 288 t.expectSingleColor(renderTarget, 'rgba8unorm', { 289 size: [kSize, kSize, 1], 290 exp: { R: 1, G: 0, B: 1, A: 1 }, 291 }); 292 }); 293 294 g.test('huge_draws') 295 .desc( 296 `Tests execution of several render passes with huge draw calls. Each pass uses a single draw 297 call which draws multiple vertices for each fragment of a large output texture.` 298 ) 299 .fn(t => { 300 const kSize = 32768; 301 const kTextureSize = 4096; 302 const kVertsPerFragment = (kSize * kSize) / (kTextureSize * kTextureSize); 303 const module = t.device.createShaderModule({ 304 code: ` 305 @vertex fn vmain(@builtin(vertex_index) vert_index: u32) 306 -> @builtin(position) vec4<f32> { 307 let index = vert_index / ${kVertsPerFragment}u; 308 let position = vec2<f32>(f32(index % ${kTextureSize}u), f32(index / ${kTextureSize}u)); 309 let r = vec2<f32>(1.0 / f32(${kTextureSize})); 310 let a = 2.0 * r; 311 let b = r - vec2<f32>(1.0); 312 return vec4<f32>(fma(position, a, b), 0.0, 1.0); 313 } 314 @fragment fn fmain() -> @location(0) vec4<f32> { 315 return vec4<f32>(1.0, 0.0, 1.0, 1.0); 316 } 317 `, 318 }); 319 const pipeline = t.device.createRenderPipeline({ 320 layout: 'auto', 321 vertex: { module, entryPoint: 'vmain', buffers: [] }, 322 primitive: { topology: 'point-list' }, 323 fragment: { 324 targets: [{ format: 'rgba8unorm' }], 325 module, 326 entryPoint: 'fmain', 327 }, 328 }); 329 const renderTarget = t.createTextureTracked({ 330 size: [kTextureSize, kTextureSize], 331 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, 332 format: 'rgba8unorm', 333 }); 334 const renderPassDescriptor: GPURenderPassDescriptor = { 335 colorAttachments: [ 336 { 337 view: renderTarget.createView(), 338 loadOp: 'load', 339 storeOp: 'store', 340 }, 341 ], 342 }; 343 344 const encoder = t.device.createCommandEncoder(); 345 const pass = encoder.beginRenderPass(renderPassDescriptor); 346 pass.setPipeline(pipeline); 347 pass.draw(kSize * kSize); 348 pass.end(); 349 t.device.queue.submit([encoder.finish()]); 350 t.expectSingleColor(renderTarget, 'rgba8unorm', { 351 size: [kTextureSize, kTextureSize, 1], 352 exp: { R: 1, G: 0, B: 1, A: 1 }, 353 }); 354 });