xgpu
Version:
XGPU is an extendable library for WebGPU that provides a higher-level, easy-to-use interface for building rendering engines or processing numeric data. It handles automatic data binding, buffer alignment, variable declarations in shaders, and more. XGPU i
177 lines (176 loc) • 7.12 kB
JavaScript
// Copyright (c) 2023 Thomas Le Coz. All rights reserved.
// This code is governed by an MIT license that can be found in the LICENSE file.
import { BuiltIns } from "./BuiltIns";
import { EventDispatcher } from "./EventDispatcher";
import { XGPU } from "./XGPU";
import { RenderPipeline } from "./pipelines/RenderPipeline";
import { ImageTexture } from "./shader/resources/ImageTexture";
import { TextureSampler } from "./shader/resources/TextureSampler";
export class GPURenderer extends EventDispatcher {
static ON_DRAW_END = "ON_DRAW_END";
domElement;
canvasView;
ctx;
currentWidth;
currentHeight;
dimensionChanged = false;
deviceId;
frameId = 0;
nbColorAttachment = 0;
renderPipelines = [];
static texturedQuadPipeline;
texturedQuadPipeline;
constructor() {
super();
if (!GPURenderer.texturedQuadPipeline) {
GPURenderer.texturedQuadPipeline = new RenderPipeline();
GPURenderer.texturedQuadPipeline.initFromObject({
vertexCount: 6,
vertexId: BuiltIns.vertexInputs.vertexIndex,
image: new ImageTexture({ source: null }),
imgSampler: new TextureSampler(),
uv: BuiltIns.vertexOutputs.Vec2,
vertexShader: {
constants: `
const pos = array<vec2<f32>([
vec2(-0.5,-0.5),
vec2(+0.5,-0.5),
vec2(-0.5,+0.5),
vec2(+0.5,-0.5),
vec2(+0.5,+0.5),
vec2(-0.5,+0.5),
]);
`,
main: `
output.position = vec4(pos[vertexId],0.0,1.0);
output.uv = 0.5 + output.position.xy;
`
},
fragmentShader: `
output.color = textureSample(image,imgSampler,uv);
`,
});
}
this.texturedQuadPipeline = GPURenderer.texturedQuadPipeline;
}
resize(w, h) {
this.domElement.width = w;
this.domElement.height = h;
this.dimensionChanged = true;
}
destroy() {
for (let i = 0; i < this.renderPipelines.length; i++) {
this.renderPipelines[i].destroy();
}
this.renderPipelines = [];
for (let z in this) {
this[z] = null;
}
}
gpuCtxConfiguration;
initCanvas(canvas, alphaMode = "opaque") {
this.domElement = canvas;
return new Promise(async (resolve, error) => {
await XGPU.init();
this.deviceId = XGPU.deviceId;
if (this.domElement == null)
return;
this.currentWidth = this.domElement.width;
this.currentHeight = this.domElement.height;
try {
this.gpuCtxConfiguration = {
device: XGPU.device,
format: XGPU.getPreferredCanvasFormat(),
alphaMode: alphaMode,
colorSpace: "srgb",
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING,
};
this.ctx = this.domElement.getContext("webgpu");
this.ctx.configure(this.gpuCtxConfiguration);
resolve(canvas);
}
catch (e) {
error(e);
}
});
}
get resized() { return this.dimensionChanged; }
get firstPipeline() { return this.renderPipelines[0]; }
get texture() { return this.ctx.getCurrentTexture(); }
get view() { return this.ctx.getCurrentTexture().createView(); }
get width() { return this.domElement.width; }
get height() { return this.domElement.height; }
get canvas() { return this.domElement; }
addPipeline(pipeline, offset = null) {
pipeline.renderer = this;
if (pipeline.renderPassDescriptor.colorAttachments[0])
this.nbColorAttachment++;
if (offset === null)
this.renderPipelines.push(pipeline);
else
this.renderPipelines.splice(offset, 0, pipeline);
return pipeline;
}
removePipeline(pipeline) {
if (pipeline.renderPassDescriptor.colorAttachments[0])
this.nbColorAttachment--;
const id = this.renderPipelines.indexOf(pipeline);
if (id != -1) {
this.renderPipelines.splice(id, 1);
}
pipeline.renderer = null;
return pipeline;
}
get nbPipeline() { return this.renderPipelines.length; }
get useSinglePipeline() { return this.nbColorAttachment === 1; }
configure(textureUsage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING, alphaMode = "opaque") {
this.gpuCtxConfiguration = {
device: XGPU.device,
format: XGPU.getPreferredCanvasFormat(),
alphaMode: alphaMode,
colorSpace: "srgb",
usage: textureUsage
};
this.ctx.configure(this.gpuCtxConfiguration);
}
commandEncoder = null;
async update() {
if (!this.ctx)
return;
if (!XGPU.ready || this.renderPipelines.length === 0 || this.deviceId === undefined)
return;
if (XGPU.deviceId != this.deviceId) {
this.ctx.configure({ ...this.gpuCtxConfiguration, device: XGPU.device });
}
if (this.canvas.width != this.currentWidth || this.canvas.height != this.currentHeight) {
this.currentWidth = this.canvas.width;
this.currentHeight = this.canvas.height;
this.dimensionChanged = true;
}
let deviceChanged = XGPU.deviceId != this.deviceId;
if (deviceChanged) {
this.deviceId = XGPU.deviceId;
for (let i = 0; i < this.renderPipelines.length; i++) {
this.renderPipelines[i].clearAfterDeviceLostAndRebuild();
}
}
this.commandEncoder = XGPU.device.createCommandEncoder();
let pipeline, renderPass;
for (let i = 0; i < this.renderPipelines.length; i++) {
pipeline = this.renderPipelines[i];
pipeline.update();
renderPass = pipeline.beginRenderPass(this.commandEncoder, this.view, 0);
for (let j = 0; j < pipeline.pipelineCount; j++) {
pipeline.dispatchEvent(RenderPipeline.ON_DRAW, j);
pipeline.draw(renderPass);
}
pipeline.end(this.commandEncoder, renderPass);
}
const commandBuffer = this.commandEncoder.finish();
this.commandEncoder = null;
XGPU.device.queue.submit([commandBuffer]);
this.dimensionChanged = false;
this.dispatchEvent(GPURenderer.ON_DRAW_END);
this.frameId++;
}
}