export const description = ` Tests for the indirect-specific aspects of drawIndirect/drawIndexedIndirect. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { kDrawIndirectParametersSize, kDrawIndexedIndirectParametersSize, } from '../../../capability_info.js'; import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js'; import * as ttu from '../../../texture_test_utils.js'; const filled = new Uint8Array([0, 255, 0, 255]); const notFilled = new Uint8Array([0, 0, 0, 0]); const kRenderTargetFormat = 'rgba8unorm'; class F extends AllFeaturesMaxLimitsGPUTest { makeIndexBuffer(): GPUBuffer { return this.makeBufferWithContents( /* prettier-ignore */ new Uint32Array([ 0, 1, 2, // The bottom left triangle 1, 2, 3, // The top right triangle ]), GPUBufferUsage.INDEX ); } makeVertexBuffer(isIndexed: boolean): GPUBuffer { /* prettier-ignore */ const vertices = isIndexed ? [ -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, ] : [ // The bottom left triangle -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, // The top right triangle -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, ]; return this.makeBufferWithContents(new Float32Array(vertices), GPUBufferUsage.VERTEX); } makeIndirectBuffer(isIndexed: boolean, indirectOffset: number): GPUBuffer { const o = indirectOffset / Uint32Array.BYTES_PER_ELEMENT; const parametersSize = isIndexed ? kDrawIndexedIndirectParametersSize : kDrawIndirectParametersSize; const arraySize = o + parametersSize * 2; const indirectBuffer = [...Array(arraySize)].map(() => Math.floor(Math.random() * 100)); if (isIndexed) { // draw args that will draw the left bottom triangle (expected call) indirectBuffer[o] = 3; // indexCount indirectBuffer[o + 1] = 1; // instanceCount indirectBuffer[o + 2] = 0; // firstIndex indirectBuffer[o + 3] = 0; // baseVertex indirectBuffer[o + 4] = 0; // firstInstance // draw args that will draw both triangles indirectBuffer[o + 5] = 6; // indexCount indirectBuffer[o + 6] = 1; // instanceCount indirectBuffer[o + 7] = 0; // firstIndex indirectBuffer[o + 8] = 0; // baseVertex indirectBuffer[o + 9] = 0; // firstInstance if (o >= parametersSize) { // draw args that will draw the right top triangle indirectBuffer[o - 5] = 3; // indexCount indirectBuffer[o - 4] = 1; // instanceCount indirectBuffer[o - 3] = 3; // firstIndex indirectBuffer[o - 2] = 0; // baseVertex indirectBuffer[o - 1] = 0; // firstInstance } if (o >= parametersSize * 2) { // draw args that will draw nothing indirectBuffer[0] = 0; // indexCount indirectBuffer[1] = 0; // instanceCount indirectBuffer[2] = 0; // firstIndex indirectBuffer[3] = 0; // baseVertex indirectBuffer[4] = 0; // firstInstance } } else { // draw args that will draw the left bottom triangle (expected call) indirectBuffer[o] = 3; // vertexCount indirectBuffer[o + 1] = 1; // instanceCount indirectBuffer[o + 2] = 0; // firstVertex indirectBuffer[o + 3] = 0; // firstInstance // draw args that will draw both triangles indirectBuffer[o + 4] = 6; // vertexCount indirectBuffer[o + 5] = 1; // instanceCount indirectBuffer[o + 6] = 0; // firstVertex indirectBuffer[o + 7] = 0; // firstInstance if (o >= parametersSize) { // draw args that will draw the right top triangle indirectBuffer[o - 4] = 3; // vertexCount indirectBuffer[o - 3] = 1; // instanceCount indirectBuffer[o - 2] = 3; // firstVertex indirectBuffer[o - 1] = 0; // firstInstance } if (o >= parametersSize * 2) { // draw args that will draw nothing indirectBuffer[0] = 0; // vertexCount indirectBuffer[1] = 0; // instanceCount indirectBuffer[2] = 0; // firstVertex indirectBuffer[3] = 0; // firstInstance } } return this.makeBufferWithContents(new Uint32Array(indirectBuffer), GPUBufferUsage.INDIRECT); } } export const g = makeTestGroup(F); g.test('basics') .desc( `Test that the indirect draw parameters are tightly packed for drawIndirect and drawIndexedIndirect. An indirectBuffer is created based on indirectOffset. The actual draw args being used indicated by the indirectOffset is going to draw a left bottom triangle. While the remaining indirectBuffer is populated with random numbers or draw args that draw right top triangle, both, or nothing which will fail the color check. The test will check render target to see if only the left bottom area is filled, meaning the expected draw args is uploaded correctly by the indirectBuffer and indirectOffset. Params: - draw{Indirect, IndexedIndirect} - indirectOffset= {0, 4, k * sizeof(args struct), k * sizeof(args struct) + 4} ` ) .params(u => u .combine('isIndexed', [true, false]) .beginSubcases() .expand('indirectOffset', p => { const indirectDrawParametersSize = p.isIndexed ? kDrawIndexedIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT : kDrawIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT; return [ 0, Uint32Array.BYTES_PER_ELEMENT, 1 * indirectDrawParametersSize, 1 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, 3 * indirectDrawParametersSize, 3 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, 99 * indirectDrawParametersSize, 99 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT, ] as const; }) ) .fn(t => { const { isIndexed, indirectOffset } = t.params; const vertexBuffer = t.makeVertexBuffer(isIndexed); const indirectBuffer = t.makeIndirectBuffer(isIndexed, indirectOffset); const pipeline = t.device.createRenderPipeline({ layout: 'auto', vertex: { module: t.device.createShaderModule({ code: `@vertex fn main(@location(0) pos : vec2) -> @builtin(position) vec4 { return vec4(pos, 0.0, 1.0); }`, }), entryPoint: 'main', buffers: [ { attributes: [ { shaderLocation: 0, format: 'float32x2', offset: 0, }, ], arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, }, ], }, fragment: { module: t.device.createShaderModule({ code: `@fragment fn main() -> @location(0) vec4 { return vec4(0.0, 1.0, 0.0, 1.0); }`, }), entryPoint: 'main', targets: [ { format: kRenderTargetFormat, }, ], }, }); const renderTarget = t.createTextureTracked({ size: [4, 4], usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, format: kRenderTargetFormat, }); const commandEncoder = t.device.createCommandEncoder(); const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [ { view: renderTarget.createView(), clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store', }, ], }); renderPass.setPipeline(pipeline); renderPass.setVertexBuffer(0, vertexBuffer, 0); if (isIndexed) { renderPass.setIndexBuffer(t.makeIndexBuffer(), 'uint32', 0); renderPass.drawIndexedIndirect(indirectBuffer, indirectOffset); } else { renderPass.drawIndirect(indirectBuffer, indirectOffset); } renderPass.end(); t.queue.submit([commandEncoder.finish()]); ttu.expectSinglePixelComparisonsAreOkInTexture(t, { texture: renderTarget }, [ // The bottom left area is filled { coord: { x: 0, y: 1 }, exp: filled }, // The top right area is not filled { coord: { x: 1, y: 0 }, exp: notFilled }, ]); });