thi-ng/umbrella

View on GitHub
packages/webgl/src/draw.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import { isArray } from "@thi.ng/checks/is-array";
import type { ModelSpec } from "./api/model.js";
import { isGL2Context } from "./checks.js";
import { error } from "./error.js";
import { bindTextures, unbindTextures } from "./texture.js";

export interface DrawFlags {
    /**
     * Unless false (default: true), bind modelspec's textures
     */
    bindTex: boolean;
    /**
     * If true (default: false), unbind modelspec's textures after use
     */
    unbindTex: boolean;
    /**
     * Unless false (default: true), bind modelspec's shader before use
     */
    bindShader: boolean;
    /**
     * Unless false (default: true), unbind modelspec's shader after use
     */
    unbindShader: boolean;
    /**
     * Unless false (default: true), apply shader's `state` opts (if any)
     */
    shaderState: boolean;
}

export const draw = (
    specs: ModelSpec | ModelSpec[],
    opts: Partial<DrawFlags> = {}
) => {
    const _specs = isArray(specs) ? specs : [specs];
    for (let i = 0, n = _specs.length; i < n; i++) {
        const spec = _specs[i];
        const indices = spec.indices;
        const gl = spec.shader.gl;
        opts.bindTex !== false && bindTextures(spec.textures);
        opts.shaderState !== false && spec.shader.prepareState();
        opts.bindShader !== false && spec.shader.bind(spec);
        if (indices && indices.buffer) {
            indices.buffer.bind();
            if (spec.instances) {
                __drawInstanced(gl, spec);
            } else {
                gl.drawElements(
                    spec.mode!,
                    spec.num,
                    indices.data instanceof Uint32Array
                        ? gl.UNSIGNED_INT
                        : gl.UNSIGNED_SHORT,
                    0
                );
            }
        } else {
            if (spec.instances) {
                __drawInstanced(gl, spec);
            } else {
                gl.drawArrays(spec.mode!, 0, spec.num);
            }
        }
        opts.unbindShader !== false && spec.shader.unbind(<any>null);
        opts.unbindTex && unbindTextures(spec.textures);
    }
};

/** @internal */
const __drawInstanced = (gl: WebGLRenderingContext, spec: ModelSpec) => {
    const isGL2 = isGL2Context(gl);
    const ext = !isGL2 ? gl.getExtension("ANGLE_instanced_arrays") : undefined;
    if (!(isGL2 || ext)) {
        error("instancing not supported");
    }
    const sattribs = spec.shader.attribs;
    const iattribs = spec.instances!.attribs;
    spec.shader.bindAttribs(iattribs);
    for (let id in iattribs) {
        const attr = sattribs[id];
        if (attr) {
            let div = iattribs[id].divisor;
            div = div !== undefined ? div : 1;
            isGL2
                ? (<WebGL2RenderingContext>gl).vertexAttribDivisor(
                        attr.loc,
                        div
                  )
                : ext!.vertexAttribDivisorANGLE(attr.loc, div);
        }
    }
    if (spec.indices) {
        const type =
            spec.indices.data instanceof Uint32Array
                ? gl.UNSIGNED_INT
                : gl.UNSIGNED_SHORT;
        isGL2
            ? (<WebGL2RenderingContext>gl).drawElementsInstanced(
                    spec.mode!,
                    spec.num,
                    type,
                    0,
                    spec.instances!.num
              )
            : ext!.drawElementsInstancedANGLE(
                    spec.mode!,
                    spec.num,
                    type,
                    0,
                    spec.instances!.num
              );
    } else {
        isGL2
            ? (<WebGL2RenderingContext>gl).drawArraysInstanced(
                    spec.mode!,
                    0,
                    spec.num,
                    spec.instances!.num
              )
            : ext!.drawArraysInstancedANGLE(
                    spec.mode!,
                    0,
                    spec.num,
                    spec.instances!.num
              );
    }
    // reset attrib divisors to allow non-instanced draws later on
    for (let id in iattribs) {
        const attr = sattribs[id];
        attr &&
            (isGL2
                ? (<WebGL2RenderingContext>gl).vertexAttribDivisor(attr.loc, 0)
                : ext!.vertexAttribDivisorANGLE(attr.loc, 0));
    }
    spec.shader.unbind(<any>null);
};