183 lines
5.7 KiB
JavaScript
183 lines
5.7 KiB
JavaScript
import EventEmitter from 'eventemitter3';
|
|
import { GlProgram } from '../../gl/shader/GlProgram.mjs';
|
|
import { BindGroup } from '../../gpu/shader/BindGroup.mjs';
|
|
import { GpuProgram } from '../../gpu/shader/GpuProgram.mjs';
|
|
import { RendererType } from '../../types.mjs';
|
|
import { UniformGroup } from './UniformGroup.mjs';
|
|
|
|
"use strict";
|
|
class Shader extends EventEmitter {
|
|
constructor(options) {
|
|
super();
|
|
/**
|
|
* A record of the uniform groups and resources used by the shader.
|
|
* This is used by WebGL renderer to sync uniform data.
|
|
* @internal
|
|
* @ignore
|
|
*/
|
|
this._uniformBindMap = /* @__PURE__ */ Object.create(null);
|
|
this._ownedBindGroups = [];
|
|
let {
|
|
gpuProgram,
|
|
glProgram,
|
|
groups,
|
|
resources,
|
|
compatibleRenderers,
|
|
groupMap
|
|
} = options;
|
|
this.gpuProgram = gpuProgram;
|
|
this.glProgram = glProgram;
|
|
if (compatibleRenderers === void 0) {
|
|
compatibleRenderers = 0;
|
|
if (gpuProgram)
|
|
compatibleRenderers |= RendererType.WEBGPU;
|
|
if (glProgram)
|
|
compatibleRenderers |= RendererType.WEBGL;
|
|
}
|
|
this.compatibleRenderers = compatibleRenderers;
|
|
const nameHash = {};
|
|
if (!resources && !groups) {
|
|
resources = {};
|
|
}
|
|
if (resources && groups) {
|
|
throw new Error("[Shader] Cannot have both resources and groups");
|
|
} else if (!gpuProgram && groups && !groupMap) {
|
|
throw new Error("[Shader] No group map or WebGPU shader provided - consider using resources instead.");
|
|
} else if (!gpuProgram && groups && groupMap) {
|
|
for (const i in groupMap) {
|
|
for (const j in groupMap[i]) {
|
|
const uniformName = groupMap[i][j];
|
|
nameHash[uniformName] = {
|
|
group: i,
|
|
binding: j,
|
|
name: uniformName
|
|
};
|
|
}
|
|
}
|
|
} else if (gpuProgram && groups && !groupMap) {
|
|
const groupData = gpuProgram.structsAndGroups.groups;
|
|
groupMap = {};
|
|
groupData.forEach((data) => {
|
|
groupMap[data.group] = groupMap[data.group] || {};
|
|
groupMap[data.group][data.binding] = data.name;
|
|
nameHash[data.name] = data;
|
|
});
|
|
} else if (resources) {
|
|
groups = {};
|
|
groupMap = {};
|
|
if (gpuProgram) {
|
|
const groupData = gpuProgram.structsAndGroups.groups;
|
|
groupData.forEach((data) => {
|
|
groupMap[data.group] = groupMap[data.group] || {};
|
|
groupMap[data.group][data.binding] = data.name;
|
|
nameHash[data.name] = data;
|
|
});
|
|
}
|
|
let bindTick = 0;
|
|
for (const i in resources) {
|
|
if (nameHash[i])
|
|
continue;
|
|
if (!groups[99]) {
|
|
groups[99] = new BindGroup();
|
|
this._ownedBindGroups.push(groups[99]);
|
|
}
|
|
nameHash[i] = { group: 99, binding: bindTick, name: i };
|
|
groupMap[99] = groupMap[99] || {};
|
|
groupMap[99][bindTick] = i;
|
|
bindTick++;
|
|
}
|
|
for (const i in resources) {
|
|
const name = i;
|
|
let value = resources[i];
|
|
if (!value.source && !value._resourceType) {
|
|
value = new UniformGroup(value);
|
|
}
|
|
const data = nameHash[name];
|
|
if (data) {
|
|
if (!groups[data.group]) {
|
|
groups[data.group] = new BindGroup();
|
|
this._ownedBindGroups.push(groups[data.group]);
|
|
}
|
|
groups[data.group].setResource(value, data.binding);
|
|
}
|
|
}
|
|
}
|
|
this.groups = groups;
|
|
this._uniformBindMap = groupMap;
|
|
this.resources = this._buildResourceAccessor(groups, nameHash);
|
|
}
|
|
/**
|
|
* Sometimes a resource group will be provided later (for example global uniforms)
|
|
* In such cases, this method can be used to let the shader know about the group.
|
|
* @param name - the name of the resource group
|
|
* @param groupIndex - the index of the group (should match the webGPU shader group location)
|
|
* @param bindIndex - the index of the bind point (should match the webGPU shader bind point)
|
|
*/
|
|
addResource(name, groupIndex, bindIndex) {
|
|
var _a, _b;
|
|
(_a = this._uniformBindMap)[groupIndex] || (_a[groupIndex] = {});
|
|
(_b = this._uniformBindMap[groupIndex])[bindIndex] || (_b[bindIndex] = name);
|
|
if (!this.groups[groupIndex]) {
|
|
this.groups[groupIndex] = new BindGroup();
|
|
this._ownedBindGroups.push(this.groups[groupIndex]);
|
|
}
|
|
}
|
|
_buildResourceAccessor(groups, nameHash) {
|
|
const uniformsOut = {};
|
|
for (const i in nameHash) {
|
|
const data = nameHash[i];
|
|
Object.defineProperty(uniformsOut, data.name, {
|
|
get() {
|
|
return groups[data.group].getResource(data.binding);
|
|
},
|
|
set(value) {
|
|
groups[data.group].setResource(value, data.binding);
|
|
}
|
|
});
|
|
}
|
|
return uniformsOut;
|
|
}
|
|
/**
|
|
* Use to destroy the shader when its not longer needed.
|
|
* It will destroy the resources and remove listeners.
|
|
* @param destroyPrograms - if the programs should be destroyed as well.
|
|
* Make sure its not being used by other shaders!
|
|
*/
|
|
destroy(destroyPrograms = false) {
|
|
this.emit("destroy", this);
|
|
if (destroyPrograms) {
|
|
this.gpuProgram?.destroy();
|
|
this.glProgram?.destroy();
|
|
}
|
|
this.gpuProgram = null;
|
|
this.glProgram = null;
|
|
this.removeAllListeners();
|
|
this._uniformBindMap = null;
|
|
this._ownedBindGroups.forEach((bindGroup) => {
|
|
bindGroup.destroy();
|
|
});
|
|
this._ownedBindGroups = null;
|
|
this.resources = null;
|
|
this.groups = null;
|
|
}
|
|
static from(options) {
|
|
const { gpu, gl, ...rest } = options;
|
|
let gpuProgram;
|
|
let glProgram;
|
|
if (gpu) {
|
|
gpuProgram = GpuProgram.from(gpu);
|
|
}
|
|
if (gl) {
|
|
glProgram = GlProgram.from(gl);
|
|
}
|
|
return new Shader({
|
|
gpuProgram,
|
|
glProgram,
|
|
...rest
|
|
});
|
|
}
|
|
}
|
|
|
|
export { Shader };
|
|
//# sourceMappingURL=Shader.mjs.map
|