Files
ditto.site/.opencode/scripts/init-hooks/shaders.js
T
2026-06-29 15:11:48 -07:00

98 lines
3.7 KiB
JavaScript

// Extract GLSL from WebGL1 + WebGL2 by intercepting shaderSource / compileShader / linkProgram / useProgram.
// Stashes into window.__CLONE_CAPTURE__.shaders as
// { programId, vertex, fragment, attributes, uniforms, canvasSelector }
(() => {
const CAPTURE = () => window.__CLONE_CAPTURE__;
const shaderMap = new WeakMap(); // WebGLShader -> { type, source }
const programMap = new WeakMap(); // WebGLProgram -> { vertex, fragment }
let nextProgramId = 1;
const contextCanvas = new WeakMap(); // WebGL(2)RenderingContext -> canvas
function patch(ctxProto, isGL2) {
const origShaderSource = ctxProto.shaderSource;
ctxProto.shaderSource = function (shader, source) {
shaderMap.set(shader, { source, type: this.getShaderParameter?.(shader, this.SHADER_TYPE) });
return origShaderSource.call(this, shader, source);
};
const origAttach = ctxProto.attachShader;
ctxProto.attachShader = function (program, shader) {
const info = shaderMap.get(shader);
const t = this.getShaderParameter(shader, this.SHADER_TYPE);
const entry = programMap.get(program) || {};
if (t === this.VERTEX_SHADER) entry.vertex = info?.source || '';
else if (t === this.FRAGMENT_SHADER) entry.fragment = info?.source || '';
programMap.set(program, entry);
return origAttach.call(this, program, shader);
};
const origLink = ctxProto.linkProgram;
ctxProto.linkProgram = function (program) {
const res = origLink.call(this, program);
const entry = programMap.get(program) || {};
if (!entry.id) entry.id = nextProgramId++;
const canvas = contextCanvas.get(this);
const sel = canvas ? cssPath(canvas) : null;
const attributes = [];
const uniforms = [];
try {
const aCount = this.getProgramParameter(program, this.ACTIVE_ATTRIBUTES);
for (let i = 0; i < aCount; i++) {
const info = this.getActiveAttrib(program, i);
if (info) attributes.push({ name: info.name, type: info.type, size: info.size });
}
const uCount = this.getProgramParameter(program, this.ACTIVE_UNIFORMS);
for (let i = 0; i < uCount; i++) {
const info = this.getActiveUniform(program, i);
if (info) uniforms.push({ name: info.name, type: info.type, size: info.size });
}
} catch {}
CAPTURE().shaders.push({
programId: entry.id,
vertex: entry.vertex || '',
fragment: entry.fragment || '',
attributes,
uniforms,
canvasSelector: sel,
isWebGL2: isGL2,
});
return res;
};
}
// Intercept getContext to remember which canvas owns which WebGL context.
const origGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function (type, ...rest) {
const ctx = origGetContext.call(this, type, ...rest);
if (ctx && (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl')) {
contextCanvas.set(ctx, this);
}
return ctx;
};
if (typeof WebGLRenderingContext !== 'undefined') patch(WebGLRenderingContext.prototype, false);
if (typeof WebGL2RenderingContext !== 'undefined') patch(WebGL2RenderingContext.prototype, true);
function cssPath(el) {
if (!el) return null;
const parts = [];
while (el && el.nodeType === 1 && parts.length < 10) {
let s = el.tagName.toLowerCase();
if (el.id) {
s += '#' + el.id;
parts.unshift(s);
break;
}
const parent = el.parentNode;
if (parent) {
const sibs = Array.from(parent.children).filter((c) => c.tagName === el.tagName);
if (sibs.length > 1) s += `:nth-of-type(${sibs.indexOf(el) + 1})`;
}
parts.unshift(s);
el = el.parentNode;
}
return parts.join(' > ');
}
})();