| <script src="../../resources/js-test.js"></script> |
| <script> |
| |
| jsTestIsAsync = true; |
| |
| description("Regression test for bugs.webkit.org/show_bug.cgi?id=264219: a render bundle containing " + |
| "drawIndirect (non-indexed) must actually render, with its draw GPU-encoded into the bundle's " + |
| "persistent MTLIndirectCommandBuffer at executeBundles time. Renders a full-target triangle via " + |
| "a bundle of drawIndirect into a 1x1 texture and reads the pixel back, executing the bundle twice " + |
| "to exercise reuse."); |
| |
| async function main() { |
| let adapter = await navigator.gpu.requestAdapter({}); |
| let device = await adapter.requestDevice({}); |
| device.pushErrorScope('validation'); |
| |
| const format = 'rgba8unorm'; |
| let module = device.createShaderModule({ code: ` |
| @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { |
| var p = array(vec2f(-1, -3), vec2f(-1, 1), vec2f(3, 1)); |
| return vec4f(p[i], 0, 1); |
| } |
| @fragment fn fs() -> @location(0) vec4f { return vec4f(0, 1, 0, 1); } |
| ` }); |
| let pipeline = device.createRenderPipeline({ |
| layout: 'auto', |
| vertex: { module, entryPoint: 'vs' }, |
| fragment: { module, entryPoint: 'fs', targets: [{ format }] }, |
| primitive: { topology: 'triangle-list' }, |
| }); |
| |
| // MTLDrawPrimitivesIndirectArguments layout: vertexCount, instanceCount, firstVertex, firstInstance. |
| let args = new Uint32Array([3, 1, 0, 0]); |
| let argsBuffer = device.createBuffer({ size: args.byteLength, usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST }); |
| device.queue.writeBuffer(argsBuffer, 0, args); |
| |
| let enc = device.createRenderBundleEncoder({ colorFormats: [format] }); |
| enc.setPipeline(pipeline); |
| enc.drawIndirect(argsBuffer, 0); |
| let bundle = enc.finish(); |
| |
| let texture = device.createTexture({ size: [1, 1], format, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC }); |
| let readback = device.createBuffer({ size: 256, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); |
| |
| function drawAndReadback() { |
| let commandEncoder = device.createCommandEncoder(); |
| let pass = commandEncoder.beginRenderPass({ |
| colorAttachments: [{ view: texture.createView(), loadOp: 'clear', storeOp: 'store', clearValue: { r: 1, g: 0, b: 0, a: 1 } }], |
| }); |
| pass.executeBundles([bundle]); |
| pass.end(); |
| commandEncoder.copyTextureToBuffer({ texture }, { buffer: readback, bytesPerRow: 256 }, [1, 1]); |
| device.queue.submit([commandEncoder.finish()]); |
| } |
| |
| drawAndReadback(); |
| await device.queue.onSubmittedWorkDone(); |
| await readback.mapAsync(GPUMapMode.READ); |
| let frame0 = [...new Uint8Array(readback.getMappedRange(0, 4))]; |
| readback.unmap(); |
| |
| drawAndReadback(); |
| await device.queue.onSubmittedWorkDone(); |
| await readback.mapAsync(GPUMapMode.READ); |
| let frame1 = [...new Uint8Array(readback.getMappedRange(0, 4))]; |
| readback.unmap(); |
| |
| window.frame0 = frame0; |
| window.frame1 = frame1; |
| shouldBeEqualToString('frame0.join()', '0,255,0,255'); |
| shouldBeEqualToString('frame1.join()', '0,255,0,255'); |
| |
| let error = await device.popErrorScope(); |
| if (error) |
| testFailed(error.message); |
| } |
| |
| main().catch(e => { |
| testFailed("Exception: " + e); |
| }).finally(() => { |
| finishJSTest(); |
| }); |
| |
| </script> |