2021-03-01 05:31:13 -05:00
|
|
|
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
|
|
|
|
|
|
|
let isCI: boolean;
|
|
|
|
try {
|
|
|
|
isCI = (Deno.env.get("CI")?.length ?? 0) > 0;
|
|
|
|
} catch {
|
|
|
|
isCI = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip this test on linux CI, because the vulkan emulator is not good enough
|
|
|
|
// yet, and skip on macOS because these do not have virtual GPUs.
|
|
|
|
unitTest({
|
2021-09-22 19:50:50 -04:00
|
|
|
permissions: { read: true, env: true },
|
2021-03-01 05:31:13 -05:00
|
|
|
ignore: (Deno.build.os === "linux" || Deno.build.os === "darwin") && isCI,
|
|
|
|
}, async function webgpuComputePass() {
|
|
|
|
const adapter = await navigator.gpu.requestAdapter();
|
|
|
|
assert(adapter);
|
|
|
|
|
|
|
|
const numbers = [1, 4, 3, 295];
|
|
|
|
|
|
|
|
const device = await adapter.requestDevice();
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
const shaderCode = await Deno.readTextFile(
|
2021-08-11 10:20:47 -04:00
|
|
|
"cli/tests/testdata/webgpu_computepass_shader.wgsl",
|
2021-03-01 05:31:13 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
const shaderModule = device.createShaderModule({
|
|
|
|
code: shaderCode,
|
|
|
|
});
|
|
|
|
|
|
|
|
const size = new Uint32Array(numbers).byteLength;
|
|
|
|
|
|
|
|
const stagingBuffer = device.createBuffer({
|
|
|
|
size: size,
|
|
|
|
usage: 1 | 8,
|
|
|
|
});
|
|
|
|
|
|
|
|
const storageBuffer = device.createBuffer({
|
|
|
|
label: "Storage Buffer",
|
|
|
|
size: size,
|
|
|
|
usage: 0x80 | 8 | 4,
|
|
|
|
mappedAtCreation: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
const buf = new Uint32Array(storageBuffer.getMappedRange());
|
|
|
|
|
|
|
|
buf.set(numbers);
|
|
|
|
|
|
|
|
storageBuffer.unmap();
|
|
|
|
|
2021-05-06 10:48:45 -04:00
|
|
|
const computePipeline = device.createComputePipeline({
|
|
|
|
compute: {
|
|
|
|
module: shaderModule,
|
|
|
|
entryPoint: "main",
|
|
|
|
},
|
2021-03-01 05:31:13 -05:00
|
|
|
});
|
2021-05-06 10:48:45 -04:00
|
|
|
const bindGroupLayout = computePipeline.getBindGroupLayout(0);
|
2021-03-01 05:31:13 -05:00
|
|
|
|
|
|
|
const bindGroup = device.createBindGroup({
|
|
|
|
layout: bindGroupLayout,
|
|
|
|
entries: [
|
|
|
|
{
|
|
|
|
binding: 0,
|
|
|
|
resource: {
|
|
|
|
buffer: storageBuffer,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
const encoder = device.createCommandEncoder();
|
|
|
|
|
|
|
|
const computePass = encoder.beginComputePass();
|
|
|
|
computePass.setPipeline(computePipeline);
|
|
|
|
computePass.setBindGroup(0, bindGroup);
|
|
|
|
computePass.insertDebugMarker("compute collatz iterations");
|
|
|
|
computePass.dispatch(numbers.length);
|
|
|
|
computePass.endPass();
|
|
|
|
|
|
|
|
encoder.copyBufferToBuffer(storageBuffer, 0, stagingBuffer, 0, size);
|
|
|
|
|
|
|
|
device.queue.submit([encoder.finish()]);
|
|
|
|
|
|
|
|
await stagingBuffer.mapAsync(1);
|
|
|
|
|
|
|
|
const data = stagingBuffer.getMappedRange();
|
|
|
|
|
|
|
|
assertEquals(new Uint32Array(data), new Uint32Array([0, 2, 7, 55]));
|
|
|
|
|
|
|
|
stagingBuffer.unmap();
|
|
|
|
|
|
|
|
device.destroy();
|
|
|
|
|
|
|
|
// TODO(lucacasonato): webgpu spec should add a explicit destroy method for
|
|
|
|
// adapters.
|
|
|
|
const resources = Object.keys(Deno.resources());
|
|
|
|
Deno.close(Number(resources[resources.length - 1]));
|
|
|
|
});
|
|
|
|
|
|
|
|
// Skip this test on linux CI, because the vulkan emulator is not good enough
|
|
|
|
// yet, and skip on macOS because these do not have virtual GPUs.
|
|
|
|
unitTest({
|
2021-09-22 19:50:50 -04:00
|
|
|
permissions: { read: true, env: true },
|
2021-03-01 05:31:13 -05:00
|
|
|
ignore: (Deno.build.os === "linux" || Deno.build.os === "darwin") && isCI,
|
|
|
|
}, async function webgpuHelloTriangle() {
|
|
|
|
const adapter = await navigator.gpu.requestAdapter();
|
|
|
|
assert(adapter);
|
|
|
|
|
|
|
|
const device = await adapter.requestDevice();
|
|
|
|
assert(device);
|
|
|
|
|
|
|
|
const shaderCode = await Deno.readTextFile(
|
2021-08-11 10:20:47 -04:00
|
|
|
"cli/tests/testdata/webgpu_hellotriangle_shader.wgsl",
|
2021-03-01 05:31:13 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
const shaderModule = device.createShaderModule({
|
|
|
|
code: shaderCode,
|
|
|
|
});
|
|
|
|
|
|
|
|
const pipelineLayout = device.createPipelineLayout({
|
|
|
|
bindGroupLayouts: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
const renderPipeline = device.createRenderPipeline({
|
|
|
|
layout: pipelineLayout,
|
|
|
|
vertex: {
|
|
|
|
module: shaderModule,
|
|
|
|
entryPoint: "vs_main",
|
|
|
|
},
|
|
|
|
fragment: {
|
|
|
|
module: shaderModule,
|
|
|
|
entryPoint: "fs_main",
|
|
|
|
targets: [
|
|
|
|
{
|
|
|
|
format: "rgba8unorm-srgb",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const dimensions = {
|
|
|
|
width: 200,
|
|
|
|
height: 200,
|
|
|
|
};
|
|
|
|
const unpaddedBytesPerRow = dimensions.width * 4;
|
|
|
|
const align = 256;
|
|
|
|
const paddedBytesPerRowPadding = (align - unpaddedBytesPerRow % align) %
|
|
|
|
align;
|
|
|
|
const paddedBytesPerRow = unpaddedBytesPerRow + paddedBytesPerRowPadding;
|
|
|
|
|
|
|
|
const outputBuffer = device.createBuffer({
|
|
|
|
label: "Capture",
|
|
|
|
size: paddedBytesPerRow * dimensions.height,
|
|
|
|
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
|
|
});
|
|
|
|
const texture = device.createTexture({
|
|
|
|
label: "Capture",
|
|
|
|
size: dimensions,
|
|
|
|
format: "rgba8unorm-srgb",
|
|
|
|
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
|
|
|
|
});
|
|
|
|
|
|
|
|
const encoder = device.createCommandEncoder();
|
2021-04-27 07:14:01 -04:00
|
|
|
const view = texture.createView();
|
2021-03-01 05:31:13 -05:00
|
|
|
const renderPass = encoder.beginRenderPass({
|
|
|
|
colorAttachments: [
|
|
|
|
{
|
2021-04-27 07:14:01 -04:00
|
|
|
view,
|
2021-03-01 05:31:13 -05:00
|
|
|
storeOp: "store",
|
|
|
|
loadValue: [0, 1, 0, 1],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
renderPass.setPipeline(renderPipeline);
|
|
|
|
renderPass.draw(3, 1);
|
|
|
|
renderPass.endPass();
|
|
|
|
|
|
|
|
encoder.copyTextureToBuffer(
|
|
|
|
{
|
|
|
|
texture,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
buffer: outputBuffer,
|
|
|
|
bytesPerRow: paddedBytesPerRow,
|
|
|
|
rowsPerImage: 0,
|
|
|
|
},
|
|
|
|
dimensions,
|
|
|
|
);
|
|
|
|
|
2021-04-27 07:14:01 -04:00
|
|
|
const bundle = encoder.finish();
|
|
|
|
device.queue.submit([bundle]);
|
2021-03-01 05:31:13 -05:00
|
|
|
|
|
|
|
await outputBuffer.mapAsync(1);
|
|
|
|
const data = new Uint8Array(outputBuffer.getMappedRange());
|
|
|
|
|
2021-08-11 10:20:47 -04:00
|
|
|
assertEquals(
|
|
|
|
data,
|
|
|
|
await Deno.readFile("cli/tests/testdata/webgpu_hellotriangle.out"),
|
|
|
|
);
|
2021-03-01 05:31:13 -05:00
|
|
|
|
|
|
|
outputBuffer.unmap();
|
|
|
|
|
|
|
|
device.destroy();
|
|
|
|
|
|
|
|
// TODO(lucacasonato): webgpu spec should add a explicit destroy method for
|
|
|
|
// adapters.
|
|
|
|
const resources = Object.keys(Deno.resources());
|
|
|
|
Deno.close(Number(resources[resources.length - 1]));
|
|
|
|
});
|