| async function getBasicDevice() { |
| const adapter = await navigator.gpu.requestAdapter({ powerPreference: "low-power" }); |
| const device = await adapter.requestDevice(); |
| return device; |
| } |
| |
| function drawWhiteSquareOnBlueBackgroundInSoftware(canvas) { |
| const context = canvas.getContext("2d"); |
| context.fillStyle = "blue"; |
| context.fillRect(0, 0, 400, 400); |
| context.fillStyle = "white"; |
| context.fillRect(100, 100, 200, 200); |
| } |
| |
| function drawBlackSquareOnBlueBackgroundInSoftware(canvas) { |
| const context = canvas.getContext("2d"); |
| context.fillStyle = "blue"; |
| context.fillRect(0, 0, 400, 400); |
| context.fillStyle = "black"; |
| context.fillRect(100, 100, 200, 200); |
| } |
| |
| function drawGreenSquareInSoftware(canvas) { |
| const context = canvas.getContext('2d'); |
| context.fillStyle = 'rgb(0, 255, 0)'; |
| context.fillRect(0, 0, canvas.width, canvas.height); |
| } |
| |
| function drawGreenAndBlueCheckerboardInSoftware(canvas) { |
| const context = canvas.getContext('2d'); |
| |
| context.fillStyle = 'rgb(0, 255, 0)'; |
| context.fillRect(0, 0, canvas.width, canvas.height); |
| |
| const numColumns = 4; |
| const numRows = 4; |
| context.beginPath(); |
| context.fillStyle = 'rgb(0, 0, 255)'; |
| for (let x = 0; x < numColumns; ++x) { |
| for (let y = 0; y < numRows; ++y) { |
| if ((x + y) % 2 == 0) |
| context.rect( |
| x * canvas.width / numColumns, |
| y * canvas.height / numRows, |
| canvas.width / numColumns, |
| canvas.height / numRows |
| ); |
| } |
| } |
| context.fill(); |
| } |
| |
| function createBasicSwapChain(canvas, device) { |
| const context = canvas.getContext("gpu"); |
| return context.configureSwapChain({ device: device, format: "bgra8unorm" }); |
| } |
| |
| function createBasicDepthStateDescriptor() { |
| return { |
| depthWriteEnabled: true, |
| depthCompare: "less" |
| }; |
| } |
| |
| function createBasicDepthTexture(canvas, device) { |
| const depthSize = { |
| width: canvas.width, |
| height: canvas.height, |
| depth: 1 |
| }; |
| |
| return device.createTexture({ |
| size: depthSize, |
| format: "depth32float-stencil8", |
| usage: GPUTextureUsage.OUTPUT_ATTACHMENT |
| }); |
| } |
| |
| function createBasicPipeline(shaderModule, device, colorStates, pipelineLayout, vertexInputDescriptor, depthStateDescriptor, primitiveTopology = "triangle-strip") { |
| const vertexStageDescriptor = { |
| module: shaderModule, |
| entryPoint: "vertex_main" |
| }; |
| |
| const fragmentStageDescriptor = { |
| module: shaderModule, |
| entryPoint: "fragment_main" |
| }; |
| |
| if (!colorStates) { |
| colorStates = [{ |
| format: "bgra8unorm", |
| alphaBlend: {}, |
| colorBlend: {} |
| }]; |
| } |
| |
| if (!vertexInputDescriptor) |
| vertexInputDescriptor = { vertexBuffers: [] }; |
| |
| const pipelineDescriptor = { |
| vertexStage: vertexStageDescriptor, |
| fragmentStage: fragmentStageDescriptor, |
| primitiveTopology: primitiveTopology, |
| colorStates: colorStates, |
| vertexInput: vertexInputDescriptor |
| }; |
| |
| if (pipelineLayout) |
| pipelineDescriptor.layout = pipelineLayout; |
| |
| if (depthStateDescriptor) |
| pipelineDescriptor.depthStencilState = depthStateDescriptor; |
| |
| return device.createRenderPipeline(pipelineDescriptor); |
| } |
| |
| function beginBasicRenderPass(swapChain, commandEncoder) { |
| const basicAttachment = { |
| attachment: swapChain.getCurrentTexture().createDefaultView(), |
| loadOp: "clear", |
| storeOp: "store", |
| clearColor: { r: 1.0, g: 0, b: 0, a: 1.0 } |
| }; |
| |
| // FIXME: Flesh out the rest of GPURenderPassDescriptor. |
| return commandEncoder.beginRenderPass({ colorAttachments : [basicAttachment] }); |
| } |
| |
| function encodeBasicCommands(renderPassEncoder, renderPipeline, vertexBuffer) { |
| if (vertexBuffer) |
| renderPassEncoder.setVertexBuffers(0, [vertexBuffer], [0]); |
| renderPassEncoder.setPipeline(renderPipeline); |
| renderPassEncoder.draw(4, 1, 0, 0); |
| renderPassEncoder.endPass(); |
| } |
| |
| function createBufferWithData(device, descriptor, data, offset = 0) { |
| const mappedBuffer = device.createBufferMapped(descriptor); |
| const dataArray = new Uint8Array(mappedBuffer[1]); |
| dataArray.set(new Uint8Array(data), offset); |
| mappedBuffer[0].unmap(); |
| return mappedBuffer[0]; |
| } |
| |
| async function mapWriteDataToBuffer(buffer, data, offset = 0) { |
| const arrayBuffer = await buffer.mapWriteAsync(); |
| const writeArray = new Uint8Array(arrayBuffer); |
| writeArray.set(new Uint8Array(data), offset); |
| buffer.unmap(); |
| } |
| |
| /*** Functions below this line require WPT testharness.js and testharnessreport.js. ***/ |
| |
| function runTestsWithDevice(tests) { |
| window.addEventListener("load", async () => { |
| try { |
| var device = await getBasicDevice(); |
| } catch (e) { /* WebGPU is not supported. */ } |
| |
| for (let name in tests) { |
| if (!name.startsWith("_")) |
| devicePromiseTest(device, tests[name], name); |
| } |
| }); |
| } |
| |
| function devicePromiseTest(device, func, name) { |
| promise_test(async () => { |
| if (device === undefined) |
| return Promise.resolve(); |
| return func(device); |
| }, name); |
| }; |
| |
| // Asserting errors. |
| |
| const popValidationError = device => device.popErrorScope().then(error => assertValidationError(error)); |
| const popMemoryError = device => device.popErrorScope().then(error => assertMemoryError(error)); |
| const popNullError = device => device.popErrorScope().then(error => assertNull(error)); |
| const assertNull = error => { |
| let assertionMsg = "No error expected!"; |
| if (error && error.message) |
| assertionMsg += " Got: " + error.message; |
| assert_true(error === null, assertionMsg); |
| }; |
| const assertValidationError = error => { |
| let assertionMsg = "Expected validation error: "; |
| if (error && error.message) |
| assertionMsg += error.message; |
| assert_true(error instanceof GPUValidationError, assertionMsg); |
| }; |
| const assertMemoryError = error => assert_true(error instanceof GPUOutOfMemoryError, "Expected out-of-memory error!"); |