/
githubmirror
/
pixijs
Обзор
Документация
Войти
/
githubmirror
/
pixijs
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
src/rendering/renderers/shared/shader/Shader.ts
523 строки
16 KB
Zyie
fix: prevent double-destroy of render targets on teardown (#12095)
08 июл 2026, 13:23
Не верифицирован
08 июл 2026, 13:23
767b016
Код
Авторство
О чём код?
import EventEmitter from 'eventemitter3'; import { uid } from '../../../../utils/data/uid'; import { warn } from '../../../../utils/logging/warn'; import { GlProgram } from '../../gl/shader/GlProgram'; import { BindGroup } from '../../gpu/shader/BindGroup'; import { GpuProgram } from '../../gpu/shader/GpuProgram'; import { RendererType } from '../../types'; import { ShaderOverrides } from './ShaderOverrides'; import { UniformGroup } from './UniformGroup'; import type { GlProgramOptions } from '../../gl/shader/GlProgram'; import type { BindResource } from '../../gpu/shader/BindResource'; import type { GpuProgramOptions } from '../../gpu/shader/GpuProgram'; /** * A record of {@link BindGroup}'s used by the shader. * * `Record<number, BindGroup>` * @category rendering * @advanced */ export type ShaderGroups = Record<number, BindGroup>; interface ShaderBase { /** The WebGL program used by the WebGL renderer. */ glProgram?: GlProgram /** The WebGPU program used by the WebGPU renderer. */ gpuProgram?: GpuProgram /** * A number that uses two bits on whether the shader is compatible with the WebGL renderer and/or the WebGPU renderer. * 0b00 - not compatible with either * 0b01 - compatible with WebGL * 0b10 - compatible with WebGPU * This is automatically set based on if a {@link GlProgram} or {@link GpuProgram} is provided. */ compatibleRenderers?: number /** The overrides used by the shader. */ overrides?: Record<string, number> | ShaderOverrides; } /** * A base interface for shaders that includes the common properties. * @category rendering * @advanced */ export interface GlShaderWith extends ShaderBase { /** The WebGL program used by the WebGL renderer. */ glProgram: GlProgram } /** * A base interface for shaders that includes the common properties. * @category rendering * @advanced */ export interface GpuShaderWith extends ShaderBase { /** The WebGPU program used by the WebGPU renderer. */ gpuProgram: GpuProgram } /** * A descriptor for a shader with groups. * This is used to define a shader that uses {@link BindGroup}'s. * @category rendering * @advanced */ export interface ShaderWithGroupsDescriptor { /** A record of {@link BindGroup}'s used by the shader. */ groups: ShaderGroups; /** an optional map of how to bind the groups. This is automatically generated by reading the WebGPU program */ groupMap?: Record<string, Record<string, any>>; } interface ShaderWithResourcesDescriptor { /** * A key value of uniform resources used by the shader. * Under the hood pixi will look at the provided shaders and figure out where * the resources are mapped. Its up to you to make sure the resource key * matches the uniform name in the webGPU program. WebGL is a little more forgiving! */ resources?: Record<string, any>; } interface GroupsData { group: number binding: number name: string } /** * A descriptor for a shader * @category rendering * @advanced */ export type ShaderWith = GlShaderWith | GpuShaderWith; /** * A descriptor for a shader with groups. * @category rendering * @advanced */ export type ShaderWithGroups = ShaderWithGroupsDescriptor & ShaderWith; /** * A descriptor for a shader with resources. This is an easier way to work with uniforms. * especially when you are not working with bind groups * @category rendering * @advanced */ export type ShaderWithResources = ShaderWithResourcesDescriptor & ShaderWith; /** * A shader that can be used with both WebGL and WebGPU. * @category rendering * @advanced */ export interface IShaderWithResources extends ShaderWithResourcesDescriptor, ShaderBase {} /** * A descriptor for a shader that can be used with both WebGL and WebGPU. * @category rendering * @advanced */ export type ShaderDescriptor = ShaderWithGroups & ShaderWithResources; /** * A descriptor for a shader with resources and groups. * @category rendering */ type GlShaderFromWith = { gpu?: GpuProgramOptions, gl: GlProgramOptions }; /** * A descriptor for a shader with groups and resources. * @category rendering */ type GpuShaderFromWith = { gpu: GpuProgramOptions, gl?: GlProgramOptions }; /** * A descriptor for a shader that can be used with both WebGL and WebGPU. * @category rendering * @advanced */ export type ShaderFromGroups = (GlShaderFromWith | GpuShaderFromWith) & Omit<ShaderWithGroups, 'glProgram' | 'gpuProgram'>; /** * A descriptor for a shader that can be used with both WebGL and WebGPU. * @category rendering * @advanced */ export type ShaderFromResources = (GlShaderFromWith | GpuShaderFromWith) & Omit<ShaderWithResources, 'glProgram' | 'gpuProgram'>; /** * The Shader class is an integral part of the PixiJS graphics pipeline. * Central to rendering in PixiJS are two key elements: A [shader] and a [geometry]. * The shader incorporates a {@link GlProgram} for WebGL or a {@link GpuProgram} for WebGPU, * instructing the respective technology on how to render the geometry. * * The primary goal of the Shader class is to offer a unified interface compatible with both WebGL and WebGPU. * When constructing a shader, you need to provide both a WebGL program and a WebGPU program due to the distinctions * between the two rendering engines. If only one is provided, the shader won't function with the omitted renderer. * * Both WebGL and WebGPU utilize the same resource object when passed into the shader. * Post-creation, the shader's interface remains consistent across both WebGL and WebGPU. * The sole distinction lies in whether a glProgram or a gpuProgram is employed. * * Modifying shader uniforms, which can encompass: * - TextureSampler {@link TextureStyle} * - TextureSource {@link TextureSource} * - UniformsGroups {@link UniformGroup} * @example * * const shader = new Shader({ * glProgram: glProgram, * gpuProgram: gpuProgram, * resources: { * uTexture: texture.source, * uSampler: texture.sampler, * uColor: [1, 0, 0, 1], * }, * }); * * // update the uniforms * shader.resources.uColor[1] = 1; * shader.resources.uTexture = texture2.source; * @class * @category rendering * @advanced */ export class Shader extends EventEmitter<{'destroy': Shader}> { /** A unique identifier for the shader */ public readonly uid: number = uid('shader'); /** An instance of the GPU program used by the WebGPU renderer */ public gpuProgram: GpuProgram; /** An instance of the GL program used by the WebGL renderer */ public glProgram: GlProgram; /** * A number that uses two bits on whether the shader is compatible with the WebGL renderer and/or the WebGPU renderer. * 0b00 - not compatible with either * 0b01 - compatible with WebGL * 0b10 - compatible with WebGPU * This is automatically set based on if a {@link GlProgram} or {@link GpuProgram} is provided. */ public readonly compatibleRenderers: number; /** */ public groups: Record<number, BindGroup>; /** A record of the resources used by the shader. */ public resources: Record<string, any>; /** * A record of the uniform groups and resources used by the shader. * This is used by WebGL renderer to sync uniform data. * @internal */ public _uniformBindMap: Record<number, Record<number, string>> = Object.create(null); private readonly _ownedBindGroups: BindGroup[] = []; /** @internal */ public readonly _overrides: ShaderOverrides; /** @internal */ public _destroyed: boolean = false; /** * Fired after rendering finishes. * @event Shader#destroy */ /** * There are two ways to create a shader. * one is to pass in resources which is a record of uniform groups and resources. * another is to pass in groups which is a record of {@link BindGroup}s. * this second method is really to make use of shared {@link BindGroup}s. * For most cases you will want to use resources as they are easier to work with. * USe Groups if you want to share {@link BindGroup}s between shaders. * you cannot mix and match - either use resources or groups. * @param options - The options for the shader */ constructor(options: ShaderWithResources); constructor(options: ShaderWithGroups); constructor(options: ShaderDescriptor) { super(); /* eslint-disable prefer-const */ let { gpuProgram, glProgram, groups, resources, compatibleRenderers, groupMap, overrides, } = options; /* eslint-enable prefer-const */ this._overrides = overrides ? ShaderOverrides.from(overrides) : null; this.gpuProgram = gpuProgram; this.glProgram = glProgram; if (compatibleRenderers === undefined) { compatibleRenderers = 0; if (gpuProgram)compatibleRenderers |= RendererType.WEBGPU; if (glProgram)compatibleRenderers |= RendererType.WEBGL; } this.compatibleRenderers = compatibleRenderers; const nameHash: Record<string, GroupsData> = {}; if (groupMap) { for (const i in groupMap) { for (const j in groupMap[i]) { const uniformName = groupMap[i][j]; nameHash[uniformName] = { group: i as unknown as number, binding: j as unknown as number, name: uniformName }; } } } 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) { 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; // with no GL program to belong to, an unmatched resource is a genuine // mistake (typo, or a leftover after editing the WGSL) — say so now, // at construction, rather than silently skipping it at draw time if (gpuProgram && !glProgram) { // #if _DEBUG warn(`[Shader] the resource '${i}' matches no binding in the WGSL source — is the name correct?`); // #endif } // build out a dummy bind group.. if (!groups[99]) { groups[99] = new BindGroup(); this._ownedBindGroups.push(groups[99]); } // Yes i know this is a little strange, but wil line up the shaders neatly // basically we want to be driven by how webGPU does things. // so making a fake group will work and not affect gpu as it means no gpu shader was provided.. 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 as BindResource)._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) */ public addResource(name: string, groupIndex: number, bindIndex: number): void { this._uniformBindMap[groupIndex] ||= {}; this._uniformBindMap[groupIndex][bindIndex] ||= name; if (!this.groups[groupIndex]) { this.groups[groupIndex] = new BindGroup(); this._ownedBindGroups.push(this.groups[groupIndex]); } } private _buildResourceAccessor(groups: ShaderGroups, nameHash: Record<string, GroupsData>) { const uniformsOut = {}; for (const i in nameHash) { const data = nameHash[i]; // add getter setter for uniforms 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! */ public destroy(destroyPrograms = false): void { if (this._destroyed) return; this._destroyed = true; 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 as null) = null; this.resources = null; this.groups = null; (this._overrides as null) = null; } /** * A short hand function to create a shader based of a vertex and fragment shader. * @param options * @returns A shiny new PixiJS shader! */ public static from(options: ShaderFromGroups): Shader; public static from(options: ShaderFromResources): Shader; public static from(options: ShaderFromGroups & ShaderFromResources): Shader { const { gpu, gl, ...rest } = options; let gpuProgram: GpuProgram; let glProgram: GlProgram; if (gpu) { gpuProgram = GpuProgram.from(gpu); } if (gl) { glProgram = GlProgram.from(gl); } return new Shader({ gpuProgram, glProgram, ...rest }); } }