Sync docs to Wiki / wiki (push) Has been cancelled
Signed-off-by: Stan Grams <sjg@haxx.space>
536 lines
18 KiB
JavaScript
536 lines
18 KiB
JavaScript
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||
//
|
||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
||
(function initTrxWebGl(global) {
|
||
"use strict";
|
||
|
||
const cssColorCache = new Map();
|
||
let cssColorProbe = null;
|
||
|
||
function clearCssColorCache() {
|
||
cssColorCache.clear();
|
||
}
|
||
|
||
function ensureCssColorProbe() {
|
||
if (cssColorProbe) return cssColorProbe;
|
||
const el = document.createElement("span");
|
||
el.style.position = "absolute";
|
||
el.style.left = "-9999px";
|
||
el.style.top = "-9999px";
|
||
el.style.pointerEvents = "none";
|
||
el.style.opacity = "0";
|
||
document.body.appendChild(el);
|
||
cssColorProbe = el;
|
||
return cssColorProbe;
|
||
}
|
||
|
||
function parseRgbString(value) {
|
||
const m = /^rgba?\(([^)]+)\)$/.exec(String(value || "").trim());
|
||
if (!m) return null;
|
||
const parts = m[1].split(",").map((p) => p.trim());
|
||
if (parts.length < 3) return null;
|
||
const r = Number(parts[0]);
|
||
const g = Number(parts[1]);
|
||
const b = Number(parts[2]);
|
||
const a = parts.length > 3 ? Number(parts[3]) : 1;
|
||
if (![r, g, b, a].every(Number.isFinite)) return null;
|
||
return [
|
||
Math.max(0, Math.min(1, r / 255)),
|
||
Math.max(0, Math.min(1, g / 255)),
|
||
Math.max(0, Math.min(1, b / 255)),
|
||
Math.max(0, Math.min(1, a)),
|
||
];
|
||
}
|
||
|
||
function parseHexColor(value) {
|
||
const raw = String(value || "").trim();
|
||
if (!/^#([0-9a-f]{3,8})$/i.test(raw)) return null;
|
||
let hex = raw.slice(1);
|
||
if (hex.length === 3 || hex.length === 4) {
|
||
hex = hex.split("").map((ch) => ch + ch).join("");
|
||
}
|
||
if (!(hex.length === 6 || hex.length === 8)) return null;
|
||
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
||
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
||
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
||
const a = hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1;
|
||
return [r, g, b, a];
|
||
}
|
||
|
||
function parseCssColor(value) {
|
||
const key = String(value ?? "");
|
||
if (cssColorCache.has(key)) return cssColorCache.get(key).slice();
|
||
|
||
let parsed = parseHexColor(key) || parseRgbString(key);
|
||
if (!parsed) {
|
||
const probe = ensureCssColorProbe();
|
||
probe.style.color = "";
|
||
probe.style.color = key;
|
||
const computed = getComputedStyle(probe).color;
|
||
parsed = parseRgbString(computed) || [0, 0, 0, 1];
|
||
}
|
||
cssColorCache.set(key, parsed.slice());
|
||
return parsed.slice();
|
||
}
|
||
|
||
function hslToRgba(h, s, l, a = 1) {
|
||
const hue = ((((Number(h) || 0) % 360) + 360) % 360) / 360;
|
||
const sat = Math.max(0, Math.min(1, (Number(s) || 0) / 100));
|
||
const lig = Math.max(0, Math.min(1, (Number(l) || 0) / 100));
|
||
|
||
const q = lig < 0.5 ? lig * (1 + sat) : lig + sat - lig * sat;
|
||
const p = 2 * lig - q;
|
||
const hueToRgb = (t) => {
|
||
let tt = t;
|
||
if (tt < 0) tt += 1;
|
||
if (tt > 1) tt -= 1;
|
||
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
||
if (tt < 1 / 2) return q;
|
||
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
||
return p;
|
||
};
|
||
|
||
const r = sat === 0 ? lig : hueToRgb(hue + 1 / 3);
|
||
const g = sat === 0 ? lig : hueToRgb(hue);
|
||
const b = sat === 0 ? lig : hueToRgb(hue - 1 / 3);
|
||
return [r, g, b, Math.max(0, Math.min(1, Number(a)))];
|
||
}
|
||
|
||
function normalizeColor(input, alphaMul = 1) {
|
||
let rgba;
|
||
if (Array.isArray(input)) {
|
||
const arr = input.map((v) => Number(v));
|
||
if (arr.length >= 4) {
|
||
rgba = [arr[0], arr[1], arr[2], arr[3]];
|
||
} else {
|
||
rgba = [0, 0, 0, 1];
|
||
}
|
||
} else if (typeof input === "string") {
|
||
rgba = parseCssColor(input);
|
||
} else if (input && typeof input === "object") {
|
||
rgba = [
|
||
Number(input.r) || 0,
|
||
Number(input.g) || 0,
|
||
Number(input.b) || 0,
|
||
Number(input.a ?? 1),
|
||
];
|
||
} else {
|
||
rgba = [0, 0, 0, 1];
|
||
}
|
||
const out = [
|
||
Math.max(0, Math.min(1, rgba[0])),
|
||
Math.max(0, Math.min(1, rgba[1])),
|
||
Math.max(0, Math.min(1, rgba[2])),
|
||
Math.max(0, Math.min(1, rgba[3] * alphaMul)),
|
||
];
|
||
return out;
|
||
}
|
||
|
||
function compileShader(gl, type, source) {
|
||
const shader = gl.createShader(type);
|
||
gl.shaderSource(shader, source);
|
||
gl.compileShader(shader);
|
||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||
const log = gl.getShaderInfoLog(shader) || "shader compile error";
|
||
gl.deleteShader(shader);
|
||
throw new Error(log);
|
||
}
|
||
return shader;
|
||
}
|
||
|
||
function createProgram(gl, vertexSrc, fragmentSrc) {
|
||
const vs = compileShader(gl, gl.VERTEX_SHADER, vertexSrc);
|
||
const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSrc);
|
||
const program = gl.createProgram();
|
||
gl.attachShader(program, vs);
|
||
gl.attachShader(program, fs);
|
||
gl.linkProgram(program);
|
||
gl.deleteShader(vs);
|
||
gl.deleteShader(fs);
|
||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||
const log = gl.getProgramInfoLog(program) || "program link error";
|
||
gl.deleteProgram(program);
|
||
throw new Error(log);
|
||
}
|
||
return program;
|
||
}
|
||
|
||
function pushColoredVertex(target, x, y, rgba) {
|
||
target.push(x, y, rgba[0], rgba[1], rgba[2], rgba[3]);
|
||
}
|
||
|
||
function segmentToQuadVertices(out, x0, y0, x1, y1, halfW, rgba) {
|
||
const dx = x1 - x0;
|
||
const dy = y1 - y0;
|
||
const len = Math.hypot(dx, dy);
|
||
if (!(len > 0.0001)) return;
|
||
const nx = (-dy / len) * halfW;
|
||
const ny = (dx / len) * halfW;
|
||
|
||
const ax = x0 - nx, ay = y0 - ny;
|
||
const bx = x0 + nx, by = y0 + ny;
|
||
const cx = x1 + nx, cy = y1 + ny;
|
||
const dx2 = x1 - nx, dy2 = y1 - ny;
|
||
|
||
pushColoredVertex(out, ax, ay, rgba);
|
||
pushColoredVertex(out, bx, by, rgba);
|
||
pushColoredVertex(out, cx, cy, rgba);
|
||
|
||
pushColoredVertex(out, ax, ay, rgba);
|
||
pushColoredVertex(out, cx, cy, rgba);
|
||
pushColoredVertex(out, dx2, dy2, rgba);
|
||
}
|
||
|
||
class TrxWebGlRenderer {
|
||
constructor(canvas, options = {}) {
|
||
this.canvas = canvas;
|
||
this.options = { alpha: true, premultipliedAlpha: false, ...options };
|
||
this.gl =
|
||
canvas?.getContext("webgl", this.options) ||
|
||
canvas?.getContext("experimental-webgl", this.options) ||
|
||
null;
|
||
this.ready = !!this.gl;
|
||
this.textures = new Map();
|
||
// Reusable scratch buffers — avoids per-draw-call Float32Array allocation
|
||
// and lets us use bufferSubData instead of bufferData (no GPU realloc).
|
||
this._colorScratch = new Float32Array(4096 * 6); // grows as needed
|
||
this._colorGpuSize = 0; // current GPU buffer size (floats)
|
||
this._texScratch = new Float32Array(6 * 4); // fixed: 6 verts × (xy+uv)
|
||
if (!this.ready) return;
|
||
|
||
const gl = this.gl;
|
||
gl.disable(gl.DEPTH_TEST);
|
||
gl.disable(gl.CULL_FACE);
|
||
gl.enable(gl.BLEND);
|
||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||
|
||
const colorVertexSrc =
|
||
"attribute vec2 a_pos;\n" +
|
||
"attribute vec4 a_color;\n" +
|
||
"uniform vec2 u_resolution;\n" +
|
||
"varying vec4 v_color;\n" +
|
||
"void main() {\n" +
|
||
" vec2 zeroToOne = a_pos / u_resolution;\n" +
|
||
" vec2 clip = zeroToOne * 2.0 - 1.0;\n" +
|
||
" gl_Position = vec4(clip * vec2(1.0, -1.0), 0.0, 1.0);\n" +
|
||
" v_color = a_color;\n" +
|
||
"}\n";
|
||
const colorFragmentSrc =
|
||
"precision mediump float;\n" +
|
||
"varying vec4 v_color;\n" +
|
||
"void main() {\n" +
|
||
" gl_FragColor = v_color;\n" +
|
||
"}\n";
|
||
|
||
const textureVertexSrc =
|
||
"attribute vec2 a_pos;\n" +
|
||
"attribute vec2 a_uv;\n" +
|
||
"uniform vec2 u_resolution;\n" +
|
||
"varying vec2 v_uv;\n" +
|
||
"void main() {\n" +
|
||
" vec2 zeroToOne = a_pos / u_resolution;\n" +
|
||
" vec2 clip = zeroToOne * 2.0 - 1.0;\n" +
|
||
" gl_Position = vec4(clip * vec2(1.0, -1.0), 0.0, 1.0);\n" +
|
||
" v_uv = a_uv;\n" +
|
||
"}\n";
|
||
const textureFragmentSrc =
|
||
"precision mediump float;\n" +
|
||
"varying vec2 v_uv;\n" +
|
||
"uniform sampler2D u_tex;\n" +
|
||
"uniform float u_alpha;\n" +
|
||
"void main() {\n" +
|
||
" vec4 c = texture2D(u_tex, v_uv);\n" +
|
||
" gl_FragColor = vec4(c.rgb, c.a * u_alpha);\n" +
|
||
"}\n";
|
||
|
||
this.colorProgram = createProgram(gl, colorVertexSrc, colorFragmentSrc);
|
||
this.colorBuffer = gl.createBuffer();
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
|
||
gl.bufferData(gl.ARRAY_BUFFER, this._colorScratch, gl.DYNAMIC_DRAW);
|
||
this._colorGpuSize = this._colorScratch.length;
|
||
this.colorLoc = {
|
||
pos: gl.getAttribLocation(this.colorProgram, "a_pos"),
|
||
color: gl.getAttribLocation(this.colorProgram, "a_color"),
|
||
resolution: gl.getUniformLocation(this.colorProgram, "u_resolution"),
|
||
};
|
||
|
||
this.textureProgram = createProgram(gl, textureVertexSrc, textureFragmentSrc);
|
||
this.textureBuffer = gl.createBuffer();
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
|
||
gl.bufferData(gl.ARRAY_BUFFER, this._texScratch, gl.DYNAMIC_DRAW);
|
||
this.textureLoc = {
|
||
pos: gl.getAttribLocation(this.textureProgram, "a_pos"),
|
||
uv: gl.getAttribLocation(this.textureProgram, "a_uv"),
|
||
resolution: gl.getUniformLocation(this.textureProgram, "u_resolution"),
|
||
alpha: gl.getUniformLocation(this.textureProgram, "u_alpha"),
|
||
tex: gl.getUniformLocation(this.textureProgram, "u_tex"),
|
||
};
|
||
}
|
||
|
||
ensureSize(cssWidth, cssHeight, dpr = (window.devicePixelRatio || 1)) {
|
||
if (!this.ready) return false;
|
||
const nextW = Math.max(1, Math.round(cssWidth * dpr));
|
||
const nextH = Math.max(1, Math.round(cssHeight * dpr));
|
||
const changed = this.canvas.width !== nextW || this.canvas.height !== nextH;
|
||
if (changed) {
|
||
this.canvas.width = nextW;
|
||
this.canvas.height = nextH;
|
||
}
|
||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||
return changed;
|
||
}
|
||
|
||
clear(color) {
|
||
if (!this.ready) return;
|
||
const gl = this.gl;
|
||
const rgba = normalizeColor(color);
|
||
gl.clearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||
}
|
||
|
||
drawTriangles(vertices) {
|
||
this._drawColorGeometry(vertices, this.gl.TRIANGLES);
|
||
}
|
||
|
||
drawTriangleStrip(vertices) {
|
||
this._drawColorGeometry(vertices, this.gl.TRIANGLE_STRIP);
|
||
}
|
||
|
||
_drawColorGeometry(vertices, mode) {
|
||
if (!this.ready || !vertices || vertices.length === 0) return;
|
||
const gl = this.gl;
|
||
const count = vertices.length;
|
||
|
||
// Grow scratch buffer if needed (doubles each time to amortise copies).
|
||
if (count > this._colorScratch.length) {
|
||
let newLen = this._colorScratch.length;
|
||
while (newLen < count) newLen *= 2;
|
||
this._colorScratch = new Float32Array(newLen);
|
||
}
|
||
|
||
// Copy into scratch (set() is a fast typed memcpy; avoids new allocation).
|
||
this._colorScratch.set(vertices);
|
||
const view = this._colorScratch.subarray(0, count);
|
||
|
||
gl.useProgram(this.colorProgram);
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
|
||
|
||
// Only reallocate the GPU buffer when it is too small; otherwise use
|
||
// bufferSubData which avoids a GPU reallocation (Safari is sensitive to this).
|
||
if (count > this._colorGpuSize) {
|
||
gl.bufferData(gl.ARRAY_BUFFER, this._colorScratch, gl.DYNAMIC_DRAW);
|
||
this._colorGpuSize = this._colorScratch.length;
|
||
} else {
|
||
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
|
||
}
|
||
|
||
gl.enableVertexAttribArray(this.colorLoc.pos);
|
||
gl.vertexAttribPointer(this.colorLoc.pos, 2, gl.FLOAT, false, 24, 0);
|
||
gl.enableVertexAttribArray(this.colorLoc.color);
|
||
gl.vertexAttribPointer(this.colorLoc.color, 4, gl.FLOAT, false, 24, 8);
|
||
gl.uniform2f(this.colorLoc.resolution, this.canvas.width, this.canvas.height);
|
||
gl.drawArrays(mode, 0, count / 6);
|
||
}
|
||
|
||
fillRect(x, y, w, h, color) {
|
||
if (w <= 0 || h <= 0) return;
|
||
const rgba = normalizeColor(color);
|
||
const v = [];
|
||
pushColoredVertex(v, x, y, rgba);
|
||
pushColoredVertex(v, x + w, y, rgba);
|
||
pushColoredVertex(v, x + w, y + h, rgba);
|
||
pushColoredVertex(v, x, y, rgba);
|
||
pushColoredVertex(v, x + w, y + h, rgba);
|
||
pushColoredVertex(v, x, y + h, rgba);
|
||
this._drawColorGeometry(v, this.gl.TRIANGLES);
|
||
}
|
||
|
||
fillGradientRect(x, y, w, h, colorTL, colorTR, colorBR, colorBL) {
|
||
if (w <= 0 || h <= 0) return;
|
||
const tl = normalizeColor(colorTL);
|
||
const tr = normalizeColor(colorTR);
|
||
const br = normalizeColor(colorBR);
|
||
const bl = normalizeColor(colorBL);
|
||
const v = [];
|
||
pushColoredVertex(v, x, y, tl);
|
||
pushColoredVertex(v, x + w, y, tr);
|
||
pushColoredVertex(v, x + w, y + h, br);
|
||
pushColoredVertex(v, x, y, tl);
|
||
pushColoredVertex(v, x + w, y + h, br);
|
||
pushColoredVertex(v, x, y + h, bl);
|
||
this._drawColorGeometry(v, this.gl.TRIANGLES);
|
||
}
|
||
|
||
drawPolyline(points, color, width = 1) {
|
||
if (!Array.isArray(points) || points.length < 4) return;
|
||
const rgba = normalizeColor(color);
|
||
const halfW = Math.max(0.5, Number(width) || 1) / 2;
|
||
const verts = [];
|
||
for (let i = 0; i < points.length - 2; i += 2) {
|
||
segmentToQuadVertices(
|
||
verts,
|
||
points[i], points[i + 1],
|
||
points[i + 2], points[i + 3],
|
||
halfW,
|
||
rgba,
|
||
);
|
||
}
|
||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||
}
|
||
|
||
drawSegments(segments, color, width = 1) {
|
||
if (!Array.isArray(segments) || segments.length < 4) return;
|
||
const rgba = normalizeColor(color);
|
||
const halfW = Math.max(0.5, Number(width) || 1) / 2;
|
||
const verts = [];
|
||
for (let i = 0; i < segments.length - 3; i += 4) {
|
||
segmentToQuadVertices(
|
||
verts,
|
||
segments[i], segments[i + 1],
|
||
segments[i + 2], segments[i + 3],
|
||
halfW,
|
||
rgba,
|
||
);
|
||
}
|
||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||
}
|
||
|
||
drawFilledArea(points, baselineY, color) {
|
||
if (!Array.isArray(points) || points.length < 4) return;
|
||
const rgba = normalizeColor(color);
|
||
const verts = [];
|
||
for (let i = 0; i < points.length; i += 2) {
|
||
pushColoredVertex(verts, points[i], baselineY, rgba);
|
||
pushColoredVertex(verts, points[i], points[i + 1], rgba);
|
||
}
|
||
this._drawColorGeometry(verts, this.gl.TRIANGLE_STRIP);
|
||
}
|
||
|
||
drawPoints(points, size, color) {
|
||
if (!Array.isArray(points) || points.length < 2) return;
|
||
const radius = Math.max(1, Number(size) || 1);
|
||
const rgba = normalizeColor(color);
|
||
const verts = [];
|
||
for (let i = 0; i < points.length; i += 2) {
|
||
const x = points[i] - radius;
|
||
const y = points[i + 1] - radius;
|
||
const w = radius * 2;
|
||
const h = radius * 2;
|
||
pushColoredVertex(verts, x, y, rgba);
|
||
pushColoredVertex(verts, x + w, y, rgba);
|
||
pushColoredVertex(verts, x + w, y + h, rgba);
|
||
pushColoredVertex(verts, x, y, rgba);
|
||
pushColoredVertex(verts, x + w, y + h, rgba);
|
||
pushColoredVertex(verts, x, y + h, rgba);
|
||
}
|
||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||
}
|
||
|
||
drawDashedVerticalLine(x, y0, y1, dashLen, gapLen, color, width = 1) {
|
||
const dash = Math.max(1, Number(dashLen) || 1);
|
||
const gap = Math.max(1, Number(gapLen) || 1);
|
||
const top = Math.min(y0, y1);
|
||
const bottom = Math.max(y0, y1);
|
||
const segments = [];
|
||
for (let y = top; y < bottom; y += dash + gap) {
|
||
const segEnd = Math.min(bottom, y + dash);
|
||
segments.push(x, y, x, segEnd);
|
||
}
|
||
this.drawSegments(segments, color, width);
|
||
}
|
||
|
||
uploadRgbaTexture(name, width, height, data, filter = "linear") {
|
||
if (!this.ready || !name || !data) return null;
|
||
const gl = this.gl;
|
||
let entry = this.textures.get(name);
|
||
if (!entry) {
|
||
const texture = gl.createTexture();
|
||
entry = { texture, width: 0, height: 0 };
|
||
this.textures.set(name, entry);
|
||
}
|
||
gl.bindTexture(gl.TEXTURE_2D, entry.texture);
|
||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||
const mode = filter === "nearest" ? gl.NEAREST : gl.LINEAR;
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mode);
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mode);
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||
if (entry.width !== width || entry.height !== height) {
|
||
gl.texImage2D(
|
||
gl.TEXTURE_2D,
|
||
0,
|
||
gl.RGBA,
|
||
width,
|
||
height,
|
||
0,
|
||
gl.RGBA,
|
||
gl.UNSIGNED_BYTE,
|
||
data,
|
||
);
|
||
entry.width = width;
|
||
entry.height = height;
|
||
} else {
|
||
gl.texSubImage2D(
|
||
gl.TEXTURE_2D,
|
||
0,
|
||
0,
|
||
0,
|
||
width,
|
||
height,
|
||
gl.RGBA,
|
||
gl.UNSIGNED_BYTE,
|
||
data,
|
||
);
|
||
}
|
||
return entry.texture;
|
||
}
|
||
|
||
drawTexture(name, x, y, w, h, alpha = 1, flipY = true) {
|
||
if (!this.ready || !name || w <= 0 || h <= 0) return;
|
||
const entry = this.textures.get(name);
|
||
if (!entry) return;
|
||
const gl = this.gl;
|
||
const s = this._texScratch;
|
||
const x2 = x + w, y2 = y + h;
|
||
if (flipY) {
|
||
s[0]=x; s[1]=y; s[2]=0; s[3]=1;
|
||
s[4]=x2; s[5]=y; s[6]=1; s[7]=1;
|
||
s[8]=x2; s[9]=y2; s[10]=1;s[11]=0;
|
||
s[12]=x; s[13]=y; s[14]=0;s[15]=1;
|
||
s[16]=x2;s[17]=y2;s[18]=1;s[19]=0;
|
||
s[20]=x; s[21]=y2;s[22]=0;s[23]=0;
|
||
} else {
|
||
s[0]=x; s[1]=y; s[2]=0; s[3]=0;
|
||
s[4]=x2; s[5]=y; s[6]=1; s[7]=0;
|
||
s[8]=x2; s[9]=y2; s[10]=1;s[11]=1;
|
||
s[12]=x; s[13]=y; s[14]=0;s[15]=0;
|
||
s[16]=x2;s[17]=y2;s[18]=1;s[19]=1;
|
||
s[20]=x; s[21]=y2;s[22]=0;s[23]=1;
|
||
}
|
||
gl.useProgram(this.textureProgram);
|
||
gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
|
||
gl.bufferSubData(gl.ARRAY_BUFFER, 0, s);
|
||
gl.enableVertexAttribArray(this.textureLoc.pos);
|
||
gl.vertexAttribPointer(this.textureLoc.pos, 2, gl.FLOAT, false, 16, 0);
|
||
gl.enableVertexAttribArray(this.textureLoc.uv);
|
||
gl.vertexAttribPointer(this.textureLoc.uv, 2, gl.FLOAT, false, 16, 8);
|
||
gl.uniform2f(this.textureLoc.resolution, this.canvas.width, this.canvas.height);
|
||
gl.uniform1f(this.textureLoc.alpha, Math.max(0, Math.min(1, Number(alpha) || 0)));
|
||
gl.activeTexture(gl.TEXTURE0);
|
||
gl.bindTexture(gl.TEXTURE_2D, entry.texture);
|
||
gl.uniform1i(this.textureLoc.tex, 0);
|
||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||
}
|
||
}
|
||
|
||
function createRenderer(canvas, options) {
|
||
return new TrxWebGlRenderer(canvas, options);
|
||
}
|
||
|
||
global.trxParseCssColor = parseCssColor;
|
||
global.trxHslToRgba = hslToRgba;
|
||
global.createTrxWebGlRenderer = createRenderer;
|
||
global.trxClearCssColorCache = clearCssColorCache;
|
||
})(window);
|