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
489 lines (484 loc) • 19.2 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 { PrimitiveFloatUniform, PrimitiveIntUniform, PrimitiveUintUniform } from "../PrimitiveType";
import { DepthStencilTexture } from "../pipelines/resources/textures/DepthStencilTexture";
import { XGPU } from "../XGPU";
import { Bindgroup } from "./Bindgroup";
import { CubeMapTexture } from "./resources/CubeMapTexture";
import { ImageTexture } from "./resources/ImageTexture";
import { TextureSampler } from "./resources/TextureSampler";
import { UniformBuffer } from "./resources/UniformBuffer";
import { VertexBuffer } from "./resources/VertexBuffer";
import { VideoTexture } from "./resources/VideoTexture";
import { ImageTextureArray } from "./resources/ImageTextureArray";
import { CubeMapTextureArray } from "./resources/CubeMapTextureArray";
import { DepthTextureArray } from "../pipelines/resources/textures/DepthTextureArray";
export class Bindgroups {
pipeline;
parent;
groups = [];
_name;
constructor(pipeline, name) {
this._name = name;
this.pipeline = pipeline;
}
get name() { return this._name; }
clearAfterDeviceLost() {
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].clearAfterDeviceLost();
}
}
build(autoLayout = false) {
const description = {};
const layouts = [];
const bindgroups = [];
this.groups = this.groups.sort((a, b) => {
if (a.useInstances && !b.useInstances)
return 1;
if (!a.useInstances && b.useInstances)
return -1;
return 0;
});
if (this.groups.length) {
this.groups[this.groups.length - 1].applyDraw = true;
}
else {
this.groups[0] = new Bindgroup();
this.groups[0].parent = this;
this.groups[0].applyDraw = true;
}
for (let i = 0; i < this.groups.length; i++) {
//console.log(i, " group = ", this.groups[i])
if (!autoLayout)
layouts[i] = this.groups[i].layout;
bindgroups[i] = this.groups[i].group;
}
if (autoLayout)
description.layout = "auto";
else {
//console.log("pipelineLayout = ", layouts)
description.layout = XGPU.createPipelineLayout({ bindGroupLayouts: layouts });
}
const { vertexLayouts, buffers, nbVertex } = this.createVertexBufferLayout();
//console.log(this.pipeline.type,vertexLayouts)
description.vertex = {
buffers: vertexLayouts
};
return {
description,
bindgroups,
buffers,
nbVertex
};
}
getBindgroupByResource(resource) {
let group, element;
for (let i = 0; i < this.groups.length; i++) {
group = this.groups[i];
for (let j = 0; j < group.elements.length; j++) {
element = group.elements[j].resource;
if (element === resource)
return group;
}
}
return null;
}
apply(passEncoder) {
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].apply(passEncoder);
}
/*if (passEncoder instanceof GPURenderPassEncoder) {
this.drawConfig.draw(passEncoder);
}*/
}
update() {
//console.log("Bindgroups.update groupLen = ", this.groups.length)
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].update(this.pipeline);
}
}
temp;
getVertexShaderDeclaration(fromFragmentShader = false) {
if (fromFragmentShader)
return this.temp;
//console.log("getVertexShaderDeclaration")
let result = "";
let group;
let resources;
let resource;
let name;
let k = 0;
const obj = { result: "", variables: "" };
/*
const structNames: string[] = [];
const removeAlreadyDefinedStruct = (source: string) => {
let lines: string[] = source.split("\n");
let result: string = "";
let openStruct: boolean = false;
let canWrite: boolean = true;
let line: string;
let delay: number = 0;
for (let i = 0; i < lines.length; i++) {
line = lines[i].trim();
if (line.substring(0, "struct ".length) === "struct ") {
const name = line.split(" ")[1].split("{")[0];
openStruct = true;
if (structNames.indexOf(name) === -1) {
structNames.push(name);
canWrite = true;
} else {
canWrite = false;
}
} else {
if (openStruct && line.indexOf("}") !== -1) {
openStruct = false;
if (!canWrite) delay = 1;
canWrite = true;
}
}
if (canWrite && delay-- <= 0) result += lines[i] + "\n";
}
return result;
}
*/
//---------------
for (let i = 0; i < this.groups.length; i++) {
group = this.groups[i];
resources = group.elements;
k = 0;
for (let j = 0; j < resources.length; j++) {
//console.log(i, j, resources[j]);
resource = resources[j].resource;
if (resource instanceof VertexBuffer)
continue;
name = resources[j].name;
if (resource instanceof UniformBuffer) {
/*if (resource.group.name) {
resource.group = resource.group.clone();
resource.group.name = undefined;
}*/
const s = resource.createStruct(name);
obj.variables += s.localVariables;
//result += removeAlreadyDefinedStruct(s.struct);
result += s.struct;
}
result += resource.createDeclaration(name, k++, i) + "\n";
}
}
obj.result = result;
this.temp = obj;
return obj;
}
getFragmentShaderDeclaration() {
let result = "";
let group;
let resources;
let resource;
let name;
let k = 0;
const obj = { result: "", variables: "" };
for (let i = 0; i < this.groups.length; i++) {
group = this.groups[i];
resources = group.elements;
k = 0;
for (let j = 0; j < resources.length; j++) {
resource = resources[j].resource;
if (resource instanceof VertexBuffer)
continue;
name = resources[j].name;
if (resource instanceof UniformBuffer) {
let item;
for (let z in resource.items) {
item = resource.items[z];
let _name = name.substring(0, 1).toLowerCase() + name.slice(1);
if (item.propertyNames)
result += item.createStruct() + "\n";
if (item.createVariableInsideMain)
obj.variables += item.createVariable(_name) + "\n";
}
result += resource.createStruct(name).struct + "\n";
}
result += resource.createDeclaration(name, k++, i) + "\n";
}
}
obj.result = result;
return obj;
}
getComputeShaderDeclaration() {
let result = "";
let group;
let resources;
let resource;
let name;
let k = 0;
const obj = { result: "", variables: "" };
//console.log("#################################################################")
for (let i = 0; i < this.groups.length; i++) {
group = this.groups[i];
//console.log(group)
resources = group.elements;
k = 0;
for (let j = 0; j < resources.length; j++) {
resource = resources[j].resource;
name = resources[j].name;
if (resource instanceof VertexBuffer) {
}
else if (resource instanceof UniformBuffer) {
//console.log(resource)
let item;
for (let z in resource.items) {
item = resource.items[z];
let _name = name.substring(0, 1).toLowerCase() + name.slice(1);
//if (item.propertyNames) result += item.createStruct() + "\n";
if (item.createVariableInsideMain)
obj.variables += item.createVariable(_name) + "\n";
}
const struct = resource.createStruct(name).struct + "\n";
//console.log(" struct => ", struct)
result += struct;
}
const declaration = resource.createDeclaration(name, k++, i) + "\n";
//console.log("declaration = ", declaration)
result += declaration;
}
}
//console.log("getComputeShaderDeclaration result = ", result)
//console.log("#################################################################")
obj.result = result;
return obj;
}
createVertexBufferLayout() {
const vertexLayouts = [];
const buffers = [];
let group;
let resources;
let resource;
let k = 0;
let builtin = 0;
let nbVertex = 0;
for (let j = 0; j < this.groups.length; j++) {
group = this.groups[j];
resources = group.elements;
for (let i = 0; i < resources.length; i++) {
resource = resources[i].resource;
if (resource instanceof VertexBuffer) {
nbVertex = Math.max(nbVertex, resource.nbVertex);
buffers[k] = resource;
vertexLayouts[k++] = resource.createVertexBufferLayout(builtin);
builtin += resource.vertexArrays.length;
}
}
}
return {
vertexLayouts,
buffers,
nbVertex
};
}
handleRenderPipelineResourceIOs() {
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].handleRenderPipelineResourceIOs();
}
}
handleComputePipelineResourceIOs() {
for (let i = 0; i < this.groups.length; i++) {
//console.log(i, this.groups[i])
this.groups[i].handleComputePipelineResourceIOs();
}
}
_resources = {};
get resources() { return this._resources; }
add(bindgroup) {
bindgroup.parent = this;
let resource = this._resources;
if (!this._resources.all)
this._resources.all = [];
if (!this._resources.types)
this._resources.types = {};
const types = this._resources.types;
//console.warn("add bindgroup ", bindgroup)
const addResources = (res, elements) => {
//console.warn("call addResource ", elements)
let element;
let r;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
//console.log(element.name)
if (res[element.name])
continue;
//console.log("addResources ", i, element)
r = element.resource;
if (this._resources.all.indexOf(r) === -1)
this._resources.all.push(r);
res[element.name] = element.resource;
if (r instanceof DepthTextureArray) {
if (!types.depthTextureArrays)
types.depthTextureArrays = [];
if (types.depthTextureArrays.indexOf(element) === -1)
types.depthTextureArrays.push(element);
}
else if (r instanceof CubeMapTextureArray) {
if (!types.cubeMapTextureArrays)
types.cubeMapTextureArrays = [];
if (types.cubeMapTextureArrays.indexOf(element) === -1)
types.cubeMapTextureArrays.push(element);
}
else if (r instanceof ImageTextureArray) {
if (!types.imageTextureArrays)
types.imageTextureArrays = [];
if (types.imageTextureArrays.indexOf(element) === -1)
types.imageTextureArrays.push(element);
}
else if (r instanceof UniformBuffer) {
if (!types.uniformBuffers)
types.uniformBuffers = [];
if (types.uniformBuffers.indexOf(element) === -1)
types.uniformBuffers.push(element);
}
else if (r instanceof VertexBuffer) {
if (!types.vertexBuffers)
types.vertexBuffers = [];
if (types.vertexBuffers.indexOf(element) === -1)
types.vertexBuffers.push(element);
}
else if (r instanceof CubeMapTexture) {
if (!types.cubeMapTexture)
types.cubeMapTexture = [];
if (types.cubeMapTexture.indexOf(element) === -1)
types.cubeMapTexture.push(element);
}
else if (r instanceof ImageTexture) {
if (!types.imageTextures)
types.imageTextures = [];
if (types.imageTextures.indexOf(element) === -1)
types.imageTextures.push(element);
}
else if (r instanceof VideoTexture) {
if (!types.videoTexture)
types.videoTexture = [];
if (types.videoTexture.indexOf(element) === -1)
types.videoTexture.push(element);
}
else if (r instanceof TextureSampler) {
if (!types.sampler)
types.sampler = [];
if (types.sampler.indexOf(element) === -1)
types.sampler.push(element);
}
else if (r instanceof DepthStencilTexture) {
if (!types.depthStencilTextures)
types.depthStencilTextures = [];
if (types.depthStencilTextures.indexOf(element) === -1)
types.depthStencilTextures.push(element);
}
}
//console.log("this.resources = ", this.resources.all.length)
};
const addGroup = (o) => {
//console.log("addGroup ", o.name, o.elements)
const res = resource[o.name] = {};
if (!res.types)
res.types = {};
addResources(res, o.elements);
this.groups.push(o);
};
if (bindgroup instanceof Bindgroup) {
if (this.groups.indexOf(bindgroup) === -1)
addGroup(bindgroup);
else {
//console.log("=> ", bindgroup.name)
addResources(resource[bindgroup.name], bindgroup.elements);
}
}
else {
bindgroup.pipeline = this.pipeline;
resource = resource[bindgroup.name] = {};
let o;
for (let i = 0; i < bindgroup.groups.length; i++) {
o = bindgroup.groups[i];
if (this.groups.indexOf(o) === -1)
addGroup(o);
}
}
return bindgroup;
}
copy(options) {
const obj = new Bindgroups(this.pipeline, this._name);
const groups = this.groups.concat();
if (options) {
for (let i = 0; i < options.oldGroups.length; i++) {
groups.splice(groups.indexOf(options.oldGroups[i]), 1, options.replacedGroup[i]);
}
}
obj.groups = groups;
return obj;
}
propertyNameIsUsed(propertyName) {
for (let i = 0; i < this.groups.length; i++) {
if (this.groups[i].get(propertyName))
return true;
}
return false;
}
get(propertyName) {
let o;
for (let i = 0; i < this.groups.length; i++) {
o = this.groups[i].get(propertyName);
if (o)
return o;
}
return null;
}
getGroupByPropertyName(name) {
let o;
for (let i = 0; i < this.groups.length; i++) {
o = this.groups[i].get(name);
if (o)
return this.groups[i];
}
return null;
}
getGroupByName(name) {
for (let i = 0; i < this.groups.length; i++) {
if (this.groups[i].name === name)
return this.groups[i];
}
return null;
}
getNameByResource(resource) {
if (resource instanceof PrimitiveFloatUniform || resource instanceof PrimitiveIntUniform || resource instanceof PrimitiveUintUniform) {
return resource.name;
}
let elements;
for (let i = 0; i < this.groups.length; i++) {
elements = this.groups[i].elements;
for (let j = 0; j < elements.length; j++) {
if (elements[j].resource === resource) {
return elements[j].name;
}
}
}
return null;
}
setupDraw(force = false) {
for (let i = 0; i < this.groups.length; i++) {
if (!this.groups[i].setupDrawCompleted) {
this.groups[i].setupDraw(force);
}
}
}
get drawConfig() {
return this.pipeline.drawConfig || null;
}
get current() {
return this.groups[this.groups.length - 1];
}
destroy() {
for (let i = 0; i < this.groups.length; i++) {
this.groups[i].destroy();
this.groups[i] = undefined;
}
this.groups = [];
this._resources = {};
}
}