Source: rendering/renderers/autoDetectRenderer.ts

import { isWebGLSupported } from '../../utils/browser/isWebGLSupported';
import { isWebGPUSupported } from '../../utils/browser/isWebGPUSupported';
import { AbstractRenderer } from './shared/system/AbstractRenderer';

import type { WebGLOptions } from './gl/WebGLRenderer';
import type { WebGPUOptions } from './gpu/WebGPURenderer';
import type { Renderer, RendererOptions } from './types';

/**
 * Options for autoDetectRenderer.
 * @memberof rendering
 */
export interface AutoDetectOptions extends RendererOptions
{
    /** The preferred renderer type. WebGPU is recommended as its generally faster than WebGL. */
    preference?: 'webgl' | 'webgpu'// | 'canvas';
    /** Optional WebGPUOptions to pass only to WebGPU renderer. */
    webgpu?: Partial<WebGPUOptions>;
    /** Optional WebGLOptions to pass only to the WebGL renderer */
    webgl?: Partial<WebGLOptions>;
}

const renderPriority = ['webgl', 'webgpu', 'canvas'];

/**
 * Automatically determines the most appropriate renderer for the current environment.
 *
 * The function will prioritize the WebGL renderer as it is the most tested safe API to use.
 * In the near future as WebGPU becomes more stable and ubiquitous, it will be prioritized over WebGL.
 *
 * The selected renderer's code is then dynamically imported to optimize
 * performance and minimize the initial bundle size.
 *
 * To maximize the benefits of dynamic imports, it's recommended to use a modern bundler
 * that supports code splitting. This will place the renderer code in a separate chunk,
 * which is loaded only when needed.
 * @example
 *
 * // create a renderer
 * const renderer = await autoDetectRenderer({
 *   width: 800,
 *   height: 600,
 *   antialias: true,
 * });
 *
 * // custom for each renderer
 * const renderer = await autoDetectRenderer({
 *   width: 800,
 *   height: 600,
 *   webgpu:{
 *     antialias: true,
 *     backgroundColor: 'red'
 *   },
 *   webgl:{
 *     antialias: true,
 *     backgroundColor: 'green'
 *   }
 *  });
 * @param options - A partial configuration object based on the `AutoDetectOptions` type.
 * @returns A Promise that resolves to an instance of the selected renderer.
 * @memberof rendering
 */
export async function autoDetectRenderer(options: Partial<AutoDetectOptions>): Promise<Renderer>
{
    let preferredOrder: string[] = [];

    if (options.preference)
    {
        preferredOrder.push(options.preference);

        renderPriority.forEach((item) =>
        {
            if (item !== options.preference)
            {
                preferredOrder.push(item);
            }
        });
    }
    else
    {
        preferredOrder = renderPriority.slice();
    }

    let RendererClass: new () => Renderer;
    let finalOptions: Partial<AutoDetectOptions> = {};

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

        if (rendererType === 'webgpu' && (await isWebGPUSupported()))
        {
            const { WebGPURenderer } = await import('./gpu/WebGPURenderer');

            RendererClass = WebGPURenderer;

            finalOptions = { ...options, ...options.webgpu };

            break;
        }
        else if (
            rendererType === 'webgl'
            && isWebGLSupported(
                options.failIfMajorPerformanceCaveat
                    ?? AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat
            )
        )
        {
            const { WebGLRenderer } = await import('./gl/WebGLRenderer');

            RendererClass = WebGLRenderer;

            finalOptions = { ...options, ...options.webgl };

            break;
        }
        else if (rendererType === 'canvas')
        {
            finalOptions = { ...options };

            throw new Error('CanvasRenderer is not yet implemented');
        }
    }

    delete finalOptions.webgpu;
    delete finalOptions.webgl;

    if (!RendererClass)
    {
        throw new Error('No available renderer for the current environment');
    }

    const renderer = new RendererClass();

    await renderer.init(finalOptions);

    return renderer;
}