UNPKG

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

306 lines (305 loc) 11.8 kB
// 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 { XGPU } from "../XGPU"; import { Bindgroup } from "../shader/Bindgroup"; import { ComputeShader } from "../shader/ComputeShader"; import { ImageTextureIO } from "../shader/resources/ImageTextureIO"; import { VertexBuffer } from "../shader/resources/VertexBuffer"; import { VertexBufferIO } from "../shader/resources/VertexBufferIO"; import { ShaderStruct } from "../shader/shaderParts/ShaderStruct"; import { Pipeline } from "./Pipeline"; import { Bindgroups } from "../shader/Bindgroups"; import { HighLevelParser } from "../HighLevelParser"; export class ComputePipeline extends Pipeline { static ON_COMPUTE_SHADER_CODE_BUILT = "ON_COMPUTE_SHADER_CODE_BUILT"; static ON_COMPUTE_BEGIN = "ON_COMPUTE_BEGIN"; static ON_COMPUTE_END = "ON_COMPUTE_END"; static ON_GPU_PIPELINE_BUILT = "ON_GPU_PIPELINE_BUILT"; static ON_INIT_FROM_OBJECT = "ON_INIT_FROM_OBJECT"; static ON_DESTROY = "ON_DESTROY"; static ON_DEVICE_LOST = "ON_DEVICE_LOST"; static ON_UPDATE_RESOURCES = "ON_UPDATE_RESOURCES"; static ON_SUBMIT_QUEUE = "ON_SUBMIT_QUEUE"; computeShader; //public onReceiveData: (datas: Float32Array) => void; gpuComputePipeline; workgroups; dispatchWorkgroup; bufferSize; textureSize; // [width,height] stagingBuffer; bufferIOs; textureIOs; onComputeBegin; onComputeEnd; onShaderBuild; constructor() { super(); //this.computeShader = new ComputeShader(); this.type = "compute"; } set useRenderPipeline(b) { if (b) this.type = "compute_mixed"; else this.type = "compute"; } initFromObject(descriptor) { this._resources = {}; this.vertexShader = null; this.fragmentShader = null; this.bindGroups.destroy(); this.bindGroups = new Bindgroups(this, "pipeline"); descriptor = HighLevelParser.parse(descriptor, "compute"); super.initFromObject(descriptor); if (descriptor.bindgroups) { let group; for (let z in descriptor.bindgroups) { group = new Bindgroup(); group.name = z; group.initFromObject(descriptor.bindgroups[z]); this.bindGroups.add(group); } if (descriptor.bindgroups.default) { if (descriptor.bindgroups.default.buffer) { const attributes = descriptor.bindgroups.default.buffer.attributes; for (let z in attributes) { if (descriptor[z]) descriptor[z] = attributes[z]; } } } } const createArrayOfObjects = (obj) => { const result = []; let o; for (let z in obj) { o = obj[z]; result.push({ name: z, ...o }); } return result; }; this.computeShader = new ComputeShader(); if (typeof descriptor.computeShader === "string") { this.computeShader.main.text = descriptor.computeShader; } else { if (descriptor.computeShader instanceof ComputeShader) { this.computeShader = descriptor.computeShader; } else { if (descriptor.computeShader.constants) this.computeShader.constants.text = descriptor.computeShader.constants; this.computeShader.main.text = descriptor.computeShader.main; } this.computeShader.inputs = createArrayOfObjects(descriptor.computeShader.inputs); this.computeShader.outputs = createArrayOfObjects(descriptor.computeShader.outputs); } let vertexBufferReadyToUse = true; for (let bufferName in this.resources.bindgroups.io) { if (!this.resources.bindgroups.io[bufferName].data) { vertexBufferReadyToUse = false; } } if (vertexBufferReadyToUse) this.nextFrame(); this.dispatchEvent(ComputePipeline.ON_INIT_FROM_OBJECT, descriptor); return descriptor; } destroy() { this.bindGroups.destroy(); for (let z in this.description) this.description[z] = null; for (let z in this) { try { this[z].destroy(); } catch (e) { try { this[z].destroyGpuResource(); } catch (e) { } } this[z] = null; } this.dispatchEvent(ComputePipeline.ON_DESTROY); } setWorkgroups(x, y = 1, z = 1) { this.workgroups = [x, y, z]; } setDispatchWorkgroup(x = 1, y = 1, z = 1) { this.dispatchWorkgroup = [x, y, z]; } vertexBufferIOs = []; imageTextureIOs = []; resourceIOs = []; nbVertexMax = 0; widthMax = 0; heightMax = 0; initResourceIOs() { const resources = this.bindGroups.resources.io; if (!resources) return; let res; let io; //console.log("IOOOO = ", resources) for (let z in resources) { res = resources[z]; io = res.resourceIO; if (io instanceof VertexBufferIO) { if (this.vertexBufferIOs.indexOf(io) === -1) { this.resourceIOs.push(io); if (io.nbVertex > this.nbVertexMax) this.nbVertexMax = io.nbVertex; this.vertexBufferIOs.push(io); } } else if (io instanceof ImageTextureIO) { if (this.imageTextureIOs.indexOf(io) === -1) { this.resourceIOs.push(io); if (io.width > this.widthMax) this.widthMax = io.width; if (io.height > this.heightMax) this.heightMax = io.height; this.imageTextureIOs.push(io); } } else { } } } deviceId; lastFrameTime = -1; update() { if (!this.gpuComputePipeline) return; if (this.deviceId !== XGPU.deviceId) { this.deviceId = XGPU.deviceId; this.dispatchEvent(ComputePipeline.ON_DEVICE_LOST); this.clearAfterDeviceLostAndRebuild(); if (new Date().getTime() - this.lastFrameTime < 100) { this.nextFrame(); } } //console.log("update ", this.bindGroups) this.bindGroups.update(); this.lastFrameTime = new Date().getTime(); } setupDefaultWorkgroups() { if (this.vertexBufferIOs.length) { let n = 64; while (this.nbVertexMax / n >= 65536) n *= 2; this.setWorkgroups(n); this.setDispatchWorkgroup(Math.ceil(this.nbVertexMax / n)); } else { this.setWorkgroups(1); this.setDispatchWorkgroup(this.widthMax, this.heightMax); } } rebuildingAfterDeviceLost = false; clearAfterDeviceLostAndRebuild() { console.warn("ComputePipeline.clearAfterDeviceLostAndRebuild()"); this.gpuComputePipeline = null; this.rebuildingAfterDeviceLost = true; super.clearAfterDeviceLostAndRebuild(); //this.buildGpuPipeline(); } buildGpuPipeline() { //console.log("buildGpuPipeline ", this.gpuComputePipeline) if (this.gpuComputePipeline) return this.gpuComputePipeline; this.initPipelineResources(this); this.createLayouts(); this.bindGroups.handleComputePipelineResourceIOs(); this.initResourceIOs(); if (!this.workgroups) this.setupDefaultWorkgroups(); this.bindGroups.build(); const outputs = this.computeShader.outputs; const inputs = this.computeShader.inputs; for (let i = 0; i < outputs.length; i++) { if (outputs[i].type.createGpuResource) { //it's a pipeline resource outputs[i].isOutput = true; inputs.push(outputs[i]); } } let resources = this.bindGroups.resources.types; for (let type in resources) { resources[type].forEach((o) => { o.resource.gpuResource.label = o.name; }); } const inputStruct = new ShaderStruct("Input", [...inputs]); ; const shaderInfos = this.computeShader.build(this, inputStruct); this.dispatchEvent(ComputePipeline.ON_COMPUTE_SHADER_CODE_BUILT, shaderInfos); if (this.onShaderBuild) this.onShaderBuild(shaderInfos); this.description.compute = { module: XGPU.device.createShaderModule({ code: shaderInfos.code }), entryPoint: "main" }; this.description.layout = this.gpuPipelineLayout; //console.log("description = ", this.description) this.deviceId = XGPU.deviceId; this.gpuComputePipeline = XGPU.createComputePipeline(this.description); this.dispatchEvent(ComputePipeline.ON_GPU_PIPELINE_BUILT); return this.gpuComputePipeline; } firstFrame = true; processingFirstFrame = false; waitingFrame = false; async nextFrame() { if (this.processingFirstFrame) { this.waitingFrame = true; return; } this.dispatchEvent(ComputePipeline.ON_COMPUTE_BEGIN); if (this.onComputeBegin) this.onComputeBegin(); this.processingFirstFrame = this.firstFrame; this.update(); const commandEncoder = XGPU.device.createCommandEncoder(); const computePass = commandEncoder.beginComputePass(); computePass.setPipeline(this.buildGpuPipeline()); //console.log(this.dispatchWorkgroup[0]*64) this.bindGroups.update(); this.bindGroups.apply(computePass); computePass.dispatchWorkgroups(this.dispatchWorkgroup[0], this.dispatchWorkgroup[1], this.dispatchWorkgroup[2]); computePass.end(); this.dispatchEvent(ComputePipeline.ON_SUBMIT_QUEUE); XGPU.device.queue.submit([commandEncoder.finish()]); if (this.firstFrame) { await XGPU.device.queue.onSubmittedWorkDone(); } for (let i = 0; i < this.resourceIOs.length; i++) { this.resourceIOs[i].getOutputData(); } this.bindGroups.resources.all.forEach((o) => { if (o instanceof VertexBuffer) { if (o.resourceIO == null) { o.getOutputData(o.gpuResource); } } else if (o.getOutputData) o.getOutputData(o.gpuResource); }); /* for(let i=0;i<this.vertexBuffers.length;i++){ this.vertexBuffers[i].getOutputData(); } */ this.firstFrame = false; this.processingFirstFrame = false; this.dispatchEvent(ComputePipeline.ON_COMPUTE_END); if (this.onComputeEnd) this.onComputeEnd(); if (this.waitingFrame) { this.waitingFrame = false; this.nextFrame(); } } }