Source: scene/particle-container/shared/ParticleContainerPipe.ts

import { Matrix } from '../../../maths/matrix/Matrix';
import { UniformGroup } from '../../../rendering/renderers/shared/shader/UniformGroup';
import { getAdjustedBlendModeBlend } from '../../../rendering/renderers/shared/state/getAdjustedBlendModeBlend';
import { State } from '../../../rendering/renderers/shared/state/State';
import { color32BitToUniform } from '../../graphics/gpu/colorToUniform';
import { ParticleBuffer } from './ParticleBuffer';
import { ParticleShader } from './shader/ParticleShader';

import type { InstructionSet } from '../../../rendering/renderers/shared/instructions/InstructionSet';
import type { RenderPipe } from '../../../rendering/renderers/shared/instructions/RenderPipe';
import type { Shader } from '../../../rendering/renderers/shared/shader/Shader';
import type { Renderer } from '../../../rendering/renderers/types';
import type { Container } from '../../container/Container';
import type { ParticleContainer } from './ParticleContainer';

export interface ParticleContainerAdaptor
{
    execute(particleContainerPop: ParticleContainerPipe, container: ParticleContainer): void;
}

/**
 * Renderer for Particles that is designer for speed over feature set.
 * @memberof scene
 */
export class ParticleContainerPipe implements RenderPipe<ParticleContainer>
{
    /** The default shader that is used if a sprite doesn't have a more specific one. */
    public defaultShader: Shader;

    public adaptor: ParticleContainerAdaptor;
    public readonly state = State.for2d();
    public readonly renderer: Renderer;

    private _gpuBufferHash: Record<number, ParticleBuffer> = Object.create(null);
    // eslint-disable-next-line max-len
    private readonly _destroyRenderableBound = this.destroyRenderable.bind(this) as unknown as (renderable: Container) => void;

    public readonly localUniforms = new UniformGroup({
        uTranslationMatrix: { value: new Matrix(), type: 'mat3x3<f32>' },
        uColor: { value: new Float32Array(4), type: 'vec4<f32>' },
        uRound: { value: 1, type: 'f32' },
        uResolution: { value: [0, 0], type: 'vec2<f32>' },
    });

    /**
     * @param renderer - The renderer this sprite batch works for.
     * @param adaptor
     */
    constructor(renderer: Renderer, adaptor: ParticleContainerAdaptor)
    {
        this.renderer = renderer;

        this.adaptor = adaptor;

        this.defaultShader = new ParticleShader();

        this.state = State.for2d();
    }

    public validateRenderable(_renderable: ParticleContainer): boolean
    {
        // always fine :D
        return false;
    }

    public addRenderable(renderable: ParticleContainer, instructionSet: InstructionSet)
    {
        this.renderer.renderPipes.batch.break(instructionSet);
        instructionSet.add(renderable);
    }

    public getBuffers(renderable: ParticleContainer): ParticleBuffer
    {
        return this._gpuBufferHash[renderable.uid] || this._initBuffer(renderable);
    }

    private _initBuffer(renderable: ParticleContainer): ParticleBuffer
    {
        this._gpuBufferHash[renderable.uid] = new ParticleBuffer({
            size: renderable.particleChildren.length,
            properties: renderable._properties,
        });

        renderable.on('destroyed', this._destroyRenderableBound);

        return this._gpuBufferHash[renderable.uid];
    }

    public updateRenderable(_renderable: ParticleContainer)
    {
        // nothing to be done here!

    }

    public destroyRenderable(renderable: ParticleContainer)
    {
        const buffer = this._gpuBufferHash[renderable.uid];

        buffer.destroy();

        this._gpuBufferHash[renderable.uid] = null;

        renderable.off('destroyed', this._destroyRenderableBound);
    }

    public execute(container: ParticleContainer): void
    {
        const children = container.particleChildren;

        if (children.length === 0)
        {
            return;
        }

        const renderer = this.renderer;
        const buffer = this.getBuffers(container);

        container.texture ||= children[0].texture;

        const state = this.state;

        buffer.update(children, container._childrenDirty);
        container._childrenDirty = false;

        state.blendMode = getAdjustedBlendModeBlend(container.blendMode, container.texture._source);

        const uniforms = this.localUniforms.uniforms;

        const transformationMatrix = uniforms.uTranslationMatrix;

        container.worldTransform.copyTo(transformationMatrix);

        transformationMatrix.prepend(renderer.globalUniforms.globalUniformData.projectionMatrix);

        uniforms.uResolution = renderer.globalUniforms.globalUniformData.resolution;
        uniforms.uRound = renderer._roundPixels | container._roundPixels;

        color32BitToUniform(
            container.groupColorAlpha,
            uniforms.uColor,
            0
        );

        this.adaptor.execute(this, container);
    }

    /** Destroys the ParticleRenderer. */
    public destroy(): void
    {
        if (this.defaultShader)
        {
            this.defaultShader.destroy();
            this.defaultShader = null;
        }
    }
}