Mirror: A frag-canvas custom element to apply Shadertoy fragment shaders to a canvas or image/video element

feat: Add vague Shadertoy preprocessing (#1)

* Add preprocessing for input shaders

* Add changeset

Changed files
+52 -6
.changeset
src
+5
.changeset/angry-dogs-win.md
···
+
---
+
'frag-canvas': patch
+
---
+
+
Add preprocessing matching Shadertoy input API vaguely
+47 -6
src/frag-canvas-element.ts
···
+
const VERSION_300 = '#version 300 es';
+
const VS_SOURCE_100 =
'attribute vec2 vPos;\n' +
'void main() {\n' +
' gl_Position = vec4(vPos, 0.0, 1.0);\n' +
'}';
const VS_SOURCE_300 =
-
'#version 300 es\n' +
+
`${VERSION_300}\n` +
'in vec4 vPos;\n' +
'void main() {\n' +
' gl_Position = vPos;\n' +
···
DATE.getSeconds() +
DATE.getMilliseconds() * 0.001;
return [year, month, day, time] as const;
+
};
+
+
const preprocessShader = (source: string, isES300: boolean) => {
+
let header = '';
+
let output = source;
+
+
if (output.startsWith(VERSION_300)) {
+
output = output.slice(VERSION_300.length + 1);
+
header += `${VERSION_300}\n`;
+
}
+
+
if (!/^\s*precision /.test(output)) header += 'precision highp float;\n';
+
+
if (output.includes('iChannel0')) header += 'uniform sampler2D iChannel0;\n';
+
if (output.includes('iResolution')) header += 'uniform vec2 iResolution;\n';
+
if (output.includes('iChannelResolution'))
+
header += 'uniform vec3 iChannelResolution[1];\n';
+
if (output.includes('iTime')) header += 'uniform float iTime;\n';
+
if (output.includes('iTimeDelta')) header += 'uniform float iTimeDelta;\n';
+
if (output.includes('iFrame')) header += 'uniform float iFrame;\n';
+
if (output.includes('iChannel')) header += 'uniform float iChannel;\n';
+
if (output.includes('iDate')) header += 'uniform vec4 iDate;\n';
+
+
if (isES300 && output.includes('gl_FragColor'))
+
header += 'out vec4 gl_FragColor;\n';
+
if (isES300 && output.includes('gl_FragCoord'))
+
header += 'in vec2 gl_FragCoord;\n';
+
+
if (!/main\s*\(/.test(output)) {
+
const ioRe = /\(\s*out\s+vec4\s+(\S+)\s*,\s*in\s+vec2\s+(\S+)\s*\)/g;
+
const io = ioRe.exec(source);
+
output = output.replace(/mainImage\s*\(/, 'main(').replace(ioRe, '()');
+
if (io && io[1] !== 'gl_FragColor')
+
header += `#define ${io[1]} gl_FragColor\n`;
+
if (io && io[2] !== 'gl_FragCoord')
+
header += `#define ${io[2]} gl_FragCoord.xy\n`;
+
}
+
+
if (!isES300) output = output.replace(/texture\s+\(/g, 'texture2D(');
+
+
return `${header}\n${output}`;
};
interface InitState {
···
updateFragShader(fragSource: string) {
fragSource = fragSource.trim();
-
gl.shaderSource(fragShader, fragSource);
+
const isES300 = /\s+#version 300/i.test(fragSource);
+
gl.shaderSource(fragShader, preprocessShader(fragSource, isES300));
gl.compileShader(fragShader);
-
-
const vertShader = /\s+#version 300/i.test(fragSource)
-
? vertShader300
-
: vertShader100;
+
const vertShader = isES300 ? vertShader300 : vertShader100;
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);