tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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  });