gpu = {
const CHECKERBOARD_BACKGROUND_CSS = `
background-image:
linear-gradient(45deg, #eee 25%, transparent 25%),
linear-gradient(-45deg, #eee 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #eee 75%),
linear-gradient(-45deg, transparent 75%, #eee 75%);
background-size: 32px 32px;
background-position: 0 0, 0 16px, 16px -16px, -16px 0px;
`;
const context = (width = 512, height = 512, contextType = 'webgpu') => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.cssText = CHECKERBOARD_BACKGROUND_CSS;
const context = canvas.getContext(contextType);
return context;
};
const format = () => navigator.gpu.getPreferredCanvasFormat();
const device = async () => {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
return { adapter, device, format };
};
const init = async (width = 512, height = 512) => {
const ctx = context(width, height, 'webgpu') ;
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const format = navigator.gpu.getPreferredCanvasFormat();
ctx.configure({ device, format, alphaMode: 'premultiplied' });
return { context: ctx, adapter, device, format };
};
const sampler = (device , options = {}) => {
return device.createSampler({
magFilter: 'linear',
minFilter: 'linear',
mipmapFilter: 'linear',
...options,
});
};
const createTexture = (
device ,
{
width,
height,
format = 'rgba8unorm',
usage = GPUTextureUsage.COPY_DST |
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.TEXTURE_BINDING,
label,
}
) => {
return device.createTexture({
...(label && { label }),
format,
size: [width, height],
usage,
});
};
const canvasTexture = (
device ,
canvas ,
{
format = 'rgba8unorm',
flipY = true,
usage = GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.RENDER_ATTACHMENT,
label,
} = {},
) => {
const texture = device.createTexture({
...(label && { label }),
format,
size: [canvas.width, canvas.height],
usage,
});
device.queue.copyExternalImageToTexture(
{ source: canvas, flipY },
{ texture },
[canvas.width, canvas.height],
);
return texture;
};
const DefaultUsage = {
STORAGE: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
UNIFORM: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
};
const buffer = (
device ,
values ,
{
usage,
offset = 0,
skipInitialWrite = false,
} ,
) => {
const buffer = device.createBuffer({
size: values.byteLength,
usage,
});
if (!skipInitialWrite) {
device.queue.writeBuffer(buffer, offset, values);
}
return {
buffer,
write: (next = values) => device.queue.writeBuffer(buffer, offset, next),
};
};
const storageBuffer = (
device ,
values ,
options = {},
) => {
return buffer(device, values, {
...options,
usage: options.usage ?? DefaultUsage.STORAGE,
});
};
const uniformBuffer = (
device ,
values ,
options = {},
) => {
return buffer(device, values, {
...options,
usage: options.usage ?? DefaultUsage.UNIFORM,
});
};
const contextDimensions = context => ({ width: context.canvas.width, height: context.canvas.height });
const defaultRegex = (key, value) => '\\$\\{' + key + '\\}';
const injectShader = (
shader ,
injections = {},
regexStr = defaultRegex
) => {
return Object.entries(injections).reduce((shader, [k, v]) => {
const regex = new RegExp(regexStr(k, v), 'g');
return shader.replace(regex, String(v));
}, shader);
};
const prependShader = (shader, header) => header + '\n\n' + shader;
const bindGroupEntry = (
binding ,
resource ,
) => {
let r;
if (resource instanceof GPUSampler) r = resource;
else if (resource instanceof GPUTexture) r = resource.createView();
else if (resource instanceof GPUBuffer) r = { buffer: resource };
else if (resource.hasOwnProperty('buffer')) r = { buffer: resource.buffer };
else r = resource;
return { binding, resource: r };
};
const mapBuffer = (device, arr , bufferOptions = {}) => {
const Constructor = arr.constructor;
const buffer = device.createBuffer({
size: arr.byteLength,
mappedAtCreation: true,
...bufferOptions,
});
new Constructor(buffer.getMappedRange()).set(arr);
buffer.unmap();
return buffer;
};
const Blending = {
SourceOver: {
color: {
operation: 'add',
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
},
alpha: {
operation: 'add',
srcFactor: 'one',
dstFactor: 'one-minus-src-alpha',
},
},
DestinationOver: {
color: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'one',
},
alpha: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'one',
},
},
Additive: {
color: {
operation: 'add',
srcFactor: 'one',
dstFactor: 'one',
},
alpha: {
operation: 'add',
srcFactor: 'one',
dstFactor: 'one',
},
},
SourceIn: {
color: {
operation: 'add',
srcFactor: 'dst-alpha',
dstFactor: 'zero',
},
alpha: {
operation: 'add',
srcFactor: 'dst-alpha',
dstFactor: 'zero',
},
},
DestinationIn: {
color: {
operation: 'add',
srcFactor: 'zero',
dstFactor: 'src-alpha',
},
alpha: {
operation: 'add',
srcFactor: 'zero',
dstFactor: 'src-alpha',
},
},
SourceOut: {
color: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'zero',
},
alpha: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'zero',
},
},
DestinationOut: {
color: {
operation: 'add',
srcFactor: 'zero',
dstFactor: 'one-minus-src-alpha',
},
alpha: {
operation: 'add',
srcFactor: 'zero',
dstFactor: 'one-minus-src-alpha',
},
},
SourceAtop: {
color: {
operation: 'add',
srcFactor: 'dst-alpha',
dstFactor: 'one-minus-src-alpha',
},
alpha: {
operation: 'add',
srcFactor: 'dst-alpha',
dstFactor: 'one-minus-src-alpha',
},
},
DestinationAtop: {
color: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'src-alpha',
},
alpha: {
operation: 'add',
srcFactor: 'one-minus-dst-alpha',
dstFactor: 'src-alpha',
},
},
};
const gpu = {
device,
context,
init,
format,
sampler,
canvasTexture,
createTexture,
storageBuffer,
uniformBuffer,
contextDimensions,
bindGroupEntry,
mapBuffer,
shader: {
inject: injectShader,
prepend: prependShader,
},
Usage: {
StorageTexture:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
CopyTexture:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
GPUTextureUsage.TEXTURE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
},
Blending,
LinearSampler: Object.freeze({
minFilter: 'linear',
magFilter: 'linear',
mipmapFilter: 'linear',
})
};
return gpu
}