Source: packages/core/src/transformFeedback/TransformFeedbackSystem.ts

import { extensions, ExtensionType } from '@pixi/extensions';

import type { DRAW_MODES } from '@pixi/constants';
import type { ExtensionMetadata } from '@pixi/extensions';
import type { IRenderingContext } from '../IRenderer';
import type { Renderer } from '../Renderer';
import type { Shader } from '../shader/Shader';
import type { ISystem } from '../system/ISystem';
import type { TransformFeedback } from './TransformFeedback';

/**
 * TransformFeedbackSystem provides TransformFeedback of WebGL2
 * https://developer.mozilla.org/en-US/docs/Web/API/WebGLTransformFeedback
 *
 * For example, you can use TransformFeedbackSystem to implement GPU Particle or
 * general purpose computing on GPU (aka GPGPU).
 *
 * It also manages a lifetime of GLTransformFeedback object
 * @memberof PIXI
 */
export class TransformFeedbackSystem implements ISystem
{
    /** @ignore */
    static extension: ExtensionMetadata = {
        type:  ExtensionType.RendererSystem,
        name: 'transformFeedback',
    };

    CONTEXT_UID: number;
    gl: IRenderingContext;

    private renderer: Renderer;

    /**
     * @param renderer - The renderer this System works for.
     */
    constructor(renderer: Renderer)
    {
        this.renderer = renderer;
    }

    /** Sets up the renderer context and necessary buffers. */
    protected contextChange(): void
    {
        this.gl = this.renderer.gl;

        // TODO fill out...
        this.CONTEXT_UID = this.renderer.CONTEXT_UID;
    }

    /**
     * Bind TransformFeedback and buffers
     * @param transformFeedback - TransformFeedback to bind
     */
    bind(transformFeedback: TransformFeedback)
    {
        const { gl, CONTEXT_UID } = this;

        const glTransformFeedback = transformFeedback._glTransformFeedbacks[CONTEXT_UID]
          || this.createGLTransformFeedback(transformFeedback);

        gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, glTransformFeedback);
    }

    /** Unbind TransformFeedback */
    unbind()
    {
        const { gl } = this;

        gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
    }

    /**
     * Begin TransformFeedback
     * @param drawMode - DrawMode for TransformFeedback
     * @param shader - A Shader used by TransformFeedback. Current bound shader will be used if not provided.
     */
    beginTransformFeedback(drawMode: DRAW_MODES, shader?: Shader)
    {
        const { gl, renderer } = this;

        if (shader)
        {
            renderer.shader.bind(shader);
        }

        gl.beginTransformFeedback(drawMode);
    }

    /** End TransformFeedback */
    endTransformFeedback()
    {
        const { gl } = this;

        gl.endTransformFeedback();
    }

    /**
     * Create TransformFeedback and bind buffers
     * @param tf - TransformFeedback
     * @returns WebGLTransformFeedback
     */
    protected createGLTransformFeedback(tf: TransformFeedback)
    {
        const { gl, renderer, CONTEXT_UID } = this;

        const glTransformFeedback = gl.createTransformFeedback();

        tf._glTransformFeedbacks[CONTEXT_UID] = glTransformFeedback;
        gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, glTransformFeedback);
        for (let i = 0; i < tf.buffers.length; i++)
        {
            const buffer = tf.buffers[i];

            if (!buffer) continue;

            renderer.buffer.update(buffer);
            buffer._glBuffers[CONTEXT_UID].refCount++;

            gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, i, buffer._glBuffers[CONTEXT_UID].buffer || null);
        }
        gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);

        tf.disposeRunner.add(this);

        return glTransformFeedback;
    }

    /**
     * Disposes TransfromFeedback
     * @param {PIXI.TransformFeedback} tf - TransformFeedback
     * @param {boolean} [contextLost=false] - If context was lost, we suppress delete TransformFeedback
     */
    disposeTransformFeedback(tf: TransformFeedback, contextLost?: boolean): void
    {
        const glTF = tf._glTransformFeedbacks[this.CONTEXT_UID];
        const gl = this.gl;

        tf.disposeRunner.remove(this);

        const bufferSystem = this.renderer.buffer;

        // bufferSystem may have already been destroyed..
        // if this is the case, there is no need to destroy the geometry buffers...
        // they already have been!
        if (bufferSystem)
        {
            for (let i = 0; i < tf.buffers.length; i++)
            {
                const buffer = tf.buffers[i];

                if (!buffer) continue;

                const buf = buffer._glBuffers[this.CONTEXT_UID];

                // my be null as context may have changed right before the dispose is called
                if (buf)
                {
                    buf.refCount--;
                    if (buf.refCount === 0 && !contextLost)
                    {
                        bufferSystem.dispose(buffer, contextLost);
                    }
                }
            }
        }

        if (!glTF)
        {
            return;
        }

        if (!contextLost)
        {
            gl.deleteTransformFeedback(glTF);
        }

        delete tf._glTransformFeedbacks[this.CONTEXT_UID];
    }

    destroy(): void
    {
        // @TODO: Destroy managed TransformFeedbacks
        this.renderer = null;
    }
}

extensions.add(TransformFeedbackSystem);