141 lines
4.9 KiB
JavaScript
141 lines
4.9 KiB
JavaScript
import { loadTriangleRenderer } from "./triangle.js"
|
|
import { loadTriangleTexturedRenderer } from "./triangleTextured.js"
|
|
|
|
if (!navigator.gpu) {
|
|
throw new Error("WebGPU not supported on this browser.");
|
|
}
|
|
|
|
const adapter = await navigator.gpu.requestAdapter();
|
|
if (!adapter) {
|
|
throw new Error("No WebGPU adapter");
|
|
}
|
|
|
|
// The next few lines, being nitpicky over bgra8unorm vs rgba8unorm is
|
|
// needed for using the canvas framebuffer as a storage texture, which
|
|
// is mostly only relevant in the specific case that you want to
|
|
// directly write to the canvas framebuffer from a compute shader.
|
|
//
|
|
// I very highly doubt this has any negative performance impact
|
|
// whatsoever on any device that has a WebGPU implementation, and
|
|
// certainly no desktop/laptop GPU is affected by this. Nevertheless,
|
|
// the WebGPU developers did deliberately decide to make
|
|
// 'bgra8unorm-storage' an optional feature, so perhaps there is some
|
|
// obscure device somewhere that actually cares about this.
|
|
const hasBgraStorage = adapter.features.has("bgra8unorm-storage");
|
|
const device = await adapter.requestDevice({
|
|
requiredFeatures: hasBgraStorage ? ["bgra8unorm-storage"] : []
|
|
});
|
|
const canvas = document.querySelector("canvas");
|
|
const context = canvas.getContext("webgpu");
|
|
const canvasFormat = hasBgraStorage ? navigator.gpu.getPreferredCanvasFormat() : "rgba8unorm";
|
|
context.configure({
|
|
device: device,
|
|
format: canvasFormat,
|
|
alphaMode: 'premultiplied',
|
|
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
|
|
});
|
|
|
|
// render targets, initialized in handleResize
|
|
var depthTexture = undefined;
|
|
|
|
function handleResize(canvasTexture)
|
|
{
|
|
// The internal color buffer returned by context.getCurrentTexture()
|
|
// is automatically resized with the canvas. All other render
|
|
// targets (e.g depth buffers) are not.
|
|
//
|
|
// If there were other off-screen buffers (deferred lighting,
|
|
// geometry buffers, etc..) those would also be recreated as needed here.
|
|
|
|
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
|
|
|
|
if (depthResized) {
|
|
if (depthTexture !== undefined) {
|
|
depthTexture.destroy();
|
|
}
|
|
|
|
depthTexture = device.createTexture({
|
|
format: 'depth24plus',
|
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
size: [canvasTexture.width, canvasTexture.height],
|
|
});
|
|
}
|
|
}
|
|
|
|
const triangleRenderer = await loadTriangleRenderer(device, canvasFormat);
|
|
const triangleTexturedRenderer = await loadTriangleTexturedRenderer(device, canvasFormat);
|
|
|
|
function render()
|
|
{
|
|
// `canvas.clientWidth` and `canvas.clientHeight` are the current
|
|
// size of the canvas element. The "canvas element" in this demo is
|
|
// sized by the `canvas` CSS rule, which scales the canvas element
|
|
// to 100% the width and height of the viewport.
|
|
//
|
|
// Web is a shit platform. The reported sizes are in "logical pixel"
|
|
// units, which are not the same size as the physical pixels of the
|
|
// device's screen.
|
|
//
|
|
// On the devices I've tested, multiplying clientWidth and
|
|
// clientHeight by window.devicePixelRatio does give the real
|
|
// dimensions of the canvas in physical screen pixels, and does
|
|
// result in sharper font rendering.
|
|
//
|
|
// On devices like Nico's phone though, devicePixelRatio
|
|
// multiplication results in ~12x more pixels being rendered per
|
|
// frame, which can be significantly slower for things like
|
|
// raymarching/raytracing.
|
|
//
|
|
// --
|
|
//
|
|
// This causes the canvas color buffer to be resized to the canvas
|
|
// element as sized by CSS, including the browser window being
|
|
// resized.
|
|
canvas.width = canvas.clientWidth * window.devicePixelRatio;
|
|
canvas.height = canvas.clientHeight * window.devicePixelRatio;
|
|
|
|
// possibly recreate any textures that depend on the size of the canvas
|
|
// changing.
|
|
const canvasTexture = context.getCurrentTexture();
|
|
handleResize(canvasTexture);
|
|
|
|
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
|
const colorAttachments = [
|
|
{
|
|
view: canvasTexture.createView(),
|
|
loadOp: "clear",
|
|
clearValue: clearValue,
|
|
storeOp: "store",
|
|
}
|
|
];
|
|
|
|
const encoder = device.createCommandEncoder();
|
|
const renderPass = encoder.beginRenderPass({
|
|
colorAttachments: colorAttachments,
|
|
depthStencilAttachment: {
|
|
view: depthTexture.createView(),
|
|
depthClearValue: 1.0,
|
|
depthLoadOp: 'clear',
|
|
depthStoreOp: 'store',
|
|
},
|
|
});
|
|
|
|
// Every time you want to use a different shader, WebGPU also
|
|
// requires you use a different GPURenderPipeline. It seems like a
|
|
// decent pattern to wrap each collection of
|
|
// buffers/pipelines/shaders in its own object:
|
|
|
|
// uncomment either of these to see the result of each render
|
|
// pipeline / shader
|
|
|
|
//triangleRenderer.render(renderPass);
|
|
triangleTexturedRenderer.render(renderPass);
|
|
|
|
renderPass.end();
|
|
|
|
const commandBuffer = encoder.finish();
|
|
device.queue.submit([commandBuffer]);
|
|
}
|
|
|
|
requestAnimationFrame(render);
|