Source: packages/canvas-renderer/src/CanvasObjectRendererSystem.ts

import {
    BLEND_MODES,
    utils,
    extensions,
    CanvasResource,
    ExtensionType } from '@pixi/core';

import type { CanvasRenderer } from './CanvasRenderer';
import type {
    Matrix,
    BaseRenderTexture,
    ExtensionMetadata,
    IRendererRenderOptions,
    ISystem,
    IRenderableObject,
    RenderTexture } from '@pixi/core';
import type { CrossPlatformCanvasRenderingContext2D } from './CanvasContextSystem';

/**
 * system that provides a render function that focussing on rendering Pixi Scene Graph objects
 * to either the main view or to a renderTexture. Used for Canvas `2d` contexts
 * @memberof PIXI
 */
export class CanvasObjectRendererSystem implements ISystem
{
    /** @ignore */
    static extension: ExtensionMetadata = {
        type:  ExtensionType.CanvasRendererSystem,
        name: 'objectRenderer',
    };

    /** A reference to the current renderer */
    private renderer: CanvasRenderer;
    renderingToScreen: boolean;
    lastObjectRendered: IRenderableObject;

    /** @param renderer - A reference to the current renderer */
    constructor(renderer: CanvasRenderer)
    {
        this.renderer = renderer;
    }

    /**
     * Renders the object to its Canvas view.
     * @param displayObject - The object to be rendered.
     * @param options - the options to be passed to the renderer
     */
    public render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void
    {
        const renderer = this.renderer;

        if (!renderer.view)
        {
            return;
        }

        const _context = renderer.canvasContext;

        let renderTexture: BaseRenderTexture | RenderTexture;
        let clear: boolean;
        let transform: Matrix;
        let skipUpdateTransform: boolean;

        if (options)
        {
            renderTexture = options.renderTexture;
            clear = options.clear;
            transform = options.transform;
            skipUpdateTransform = options.skipUpdateTransform;
        }

        // can be handy to know!
        this.renderingToScreen = !renderTexture;

        renderer.emit('prerender');

        const rootResolution = renderer.resolution;

        if (renderTexture)
        {
            renderTexture = renderTexture.castToBaseTexture() as BaseRenderTexture;

            if (!renderTexture._canvasRenderTarget)
            {
                renderTexture._canvasRenderTarget = new utils.CanvasRenderTarget(
                    renderTexture.width,
                    renderTexture.height,
                    renderTexture.resolution
                );

                renderTexture.resource = new CanvasResource(renderTexture._canvasRenderTarget.canvas);
                renderTexture.valid = true;
            }

            _context.activeContext = renderTexture._canvasRenderTarget.context as CrossPlatformCanvasRenderingContext2D;
            renderer.canvasContext.activeResolution = renderTexture._canvasRenderTarget.resolution;
        }
        else
        {
            _context.activeContext = _context.rootContext;
            _context.activeResolution = rootResolution;
        }

        const context2D = _context.activeContext;

        _context._projTransform = transform || null;

        if (!renderTexture)
        {
            this.lastObjectRendered = displayObject;
        }

        if (!skipUpdateTransform)
        {
            // update the scene graph
            const cacheParent = displayObject.enableTempParent();

            displayObject.updateTransform();
            displayObject.disableTempParent(cacheParent);
        }

        context2D.save();
        context2D.setTransform(1, 0, 0, 1, 0, 0);
        context2D.globalAlpha = 1;
        _context._activeBlendMode = BLEND_MODES.NORMAL;
        _context._outerBlend = false;
        context2D.globalCompositeOperation = _context.blendModes[BLEND_MODES.NORMAL];

        if (clear ?? renderer.background.clearBeforeRender)
        {
            if (this.renderingToScreen)
            {
                context2D.clearRect(0, 0, renderer.width, renderer.height);

                const background = renderer.background;

                if (background.alpha > 0)
                {
                    context2D.globalAlpha = background.alpha;
                    context2D.fillStyle = background.colorString;
                    context2D.fillRect(0, 0, renderer.width, renderer.height);
                    context2D.globalAlpha = 1;
                }
            }
            else
            {
                renderTexture = (renderTexture as BaseRenderTexture);
                renderTexture._canvasRenderTarget.clear();

                const clearColor = renderTexture.clearColor;

                if (clearColor[3] > 0)
                {
                    context2D.globalAlpha = clearColor[3] ?? 1;
                    context2D.fillStyle = utils.hex2string(utils.rgb2hex(clearColor));
                    context2D.fillRect(0, 0, renderTexture.realWidth, renderTexture.realHeight);
                    context2D.globalAlpha = 1;
                }
            }
        }

        // TODO RENDER TARGET STUFF HERE..
        const tempContext = _context.activeContext;

        _context.activeContext = context2D;
        displayObject.renderCanvas(renderer);
        _context.activeContext = tempContext;

        context2D.restore();

        _context.activeResolution = rootResolution;
        _context._projTransform = null;

        renderer.emit('postrender');
    }

    public destroy(): void
    {
        this.lastObjectRendered = null;
        this.render = null;
    }
}

extensions.add(CanvasObjectRendererSystem);