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