Source: rendering/renderers/shared/texture/TextureGCSystem.ts

import { extensions, ExtensionType } from '../../../../extensions/Extensions';

import type { Renderer } from '../../types';
import type { System } from '../system/System';

/**
 * Options for the TextureGCSystem.
 * @memberof rendering
 * @property {boolean} [textureGCActive=true] - If set to true, this will enable the garbage collector on the GPU.
 * @property {number} [textureGCAMaxIdle=60 * 60] -
 * The maximum idle frames before a texture is destroyed by garbage collection.
 * @property {number} [textureGCCheckCountMax=600] - Frames between two garbage collections.
 */
export interface TextureGCSystemOptions
{
    /**
     * If set to true, this will enable the garbage collector on the GPU.
     * @default true
     * @memberof rendering.SharedRendererOptions
     */
    textureGCActive: boolean;
    /**
     * The maximum idle frames before a texture is destroyed by garbage collection.
     * @default 60 * 60
     * @memberof rendering.SharedRendererOptions
     */
    textureGCAMaxIdle: number;
    /**
     * Frames between two garbage collections.
     * @default 600
     * @memberof rendering.SharedRendererOptions
     */
    textureGCCheckCountMax: number;
}
/**
 * System plugin to the renderer to manage texture garbage collection on the GPU,
 * ensuring that it does not get clogged up with textures that are no longer being used.
 * @memberof rendering
 */
export class TextureGCSystem implements System<TextureGCSystemOptions>
{
    /** @ignore */
    public static extension = {
        type: [
            ExtensionType.WebGLSystem,
            ExtensionType.WebGPUSystem,
        ],
        name: 'textureGC',
    } as const;

    /** default options for the TextureGCSystem */
    public static defaultOptions: TextureGCSystemOptions = {
        /**
         * If set to true, this will enable the garbage collector on the GPU.
         * @default true
         */
        textureGCActive: true,
        /**
         * The maximum idle frames before a texture is destroyed by garbage collection.
         * @default 60 * 60
         */
        textureGCAMaxIdle: 60 * 60,
        /**
         * Frames between two garbage collections.
         * @default 600
         */
        textureGCCheckCountMax: 600,
    };

    /**
     * Frame count since started.
     * @readonly
     */
    public count: number;

    /**
     * Frame count since last garbage collection.
     * @readonly
     */
    public checkCount: number;

    /**
     * Maximum idle frames before a texture is destroyed by garbage collection.
     * @see TextureGCSystem.defaultMaxIdle
     */
    public maxIdle: number;

    /**
     * Frames between two garbage collections.
     * @see TextureGCSystem.defaultCheckCountMax
     */
    public checkCountMax: number;

    /**
     * Current garbage collection mode.
     * @see TextureGCSystem.defaultMode
     */
    public active: boolean;
    private _renderer: Renderer;

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

        this.count = 0;
        this.checkCount = 0;
    }

    public init(options: TextureGCSystemOptions): void
    {
        options = { ...TextureGCSystem.defaultOptions, ...options };

        this.checkCountMax = options.textureGCCheckCountMax;
        this.maxIdle = options.textureGCAMaxIdle;
        this.active = options.textureGCActive;
    }

    /**
     * Checks to see when the last time a texture was used.
     * If the texture has not been used for a specified amount of time, it will be removed from the GPU.
     */
    protected postrender(): void
    {
        if (!this._renderer.renderingToScreen)
        {
            return;
        }

        this.count++;

        if (!this.active) return;

        this.checkCount++;

        if (this.checkCount > this.checkCountMax)
        {
            this.checkCount = 0;

            this.run();
        }
    }

    /**
     * Checks to see when the last time a texture was used.
     * If the texture has not been used for a specified amount of time, it will be removed from the GPU.
     */
    public run(): void
    {
        const managedTextures = this._renderer.texture.managedTextures;

        for (let i = 0; i < managedTextures.length; i++)
        {
            const texture = managedTextures[i];

            // Only supports non generated textures at the moment!
            if (
                texture.autoGarbageCollect
                && texture.resource
                && texture._touched > -1
                && this.count - texture._touched > this.maxIdle
            )
            {
                texture._touched = -1;
                texture.unload();
            }
        }
    }

    public destroy(): void
    {
        this._renderer = null as any as Renderer;
    }
}

extensions.add(TextureGCSystem);