refactor
This commit is contained in:
parent
51a539b73b
commit
850304b6f7
8 changed files with 190 additions and 46 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.{ts,json}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
node_modules
|
||||
dist
|
||||
deno.d.ts
|
||||
.vscode
|
||||
.local
|
||||
.output
|
||||
web/*.js
|
||||
|
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# typescript/shaderview
|
||||
|
||||
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/shaderview?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=shaderview)
|
19
run
Executable file
19
run
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
# Simplified run tool for deno commands
|
||||
|
||||
if test $# -eq 0
|
||||
then
|
||||
echo "Usage: $0 [file or command]"
|
||||
exit 1
|
||||
elif echo $1 | grep -q '.*.ts'
|
||||
then
|
||||
denocmd=run
|
||||
denoargs=$1
|
||||
shift
|
||||
else
|
||||
denocmd=$1
|
||||
shift
|
||||
fi
|
||||
|
||||
denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@"
|
||||
exec deno $denocmd $denoargs
|
54
src/shaderview.test.ts
Normal file
54
src/shaderview.test.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { assertEquals } from "jsr:@std/assert";
|
||||
import { ShaderView } from "./shaderview.ts";
|
||||
|
||||
Deno.test("list uniforms from shader code", () => {
|
||||
assertEquals(
|
||||
new ShaderView("void main() {}", { noRender: true }).listUniforms(),
|
||||
{
|
||||
iFrame: {
|
||||
type: "int",
|
||||
},
|
||||
iMouse: {
|
||||
type: "vec4",
|
||||
},
|
||||
iResolution: {
|
||||
type: "vec3",
|
||||
},
|
||||
iTime: {
|
||||
type: "float",
|
||||
},
|
||||
iTimeDelta: {
|
||||
type: "float",
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(
|
||||
new ShaderView(
|
||||
"uniform vec3 test1 ;\nuniform float test2; void main() {}",
|
||||
{ noRender: true },
|
||||
).listUniforms(),
|
||||
{
|
||||
iFrame: {
|
||||
type: "int",
|
||||
},
|
||||
iMouse: {
|
||||
type: "vec4",
|
||||
},
|
||||
iResolution: {
|
||||
type: "vec3",
|
||||
},
|
||||
iTime: {
|
||||
type: "float",
|
||||
},
|
||||
iTimeDelta: {
|
||||
type: "float",
|
||||
},
|
||||
test1: {
|
||||
type: "vec3",
|
||||
},
|
||||
test2: {
|
||||
type: "float",
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
|
@ -1,34 +1,24 @@
|
|||
export class ShaderView {
|
||||
readonly finalShader: string;
|
||||
uniforms: Record<string, any> = {};
|
||||
canvas: HTMLCanvasElement;
|
||||
gl: WebGLRenderingContext;
|
||||
vertexShader: WebGLShader;
|
||||
fragmentShader: WebGLShader;
|
||||
program: WebGLProgram;
|
||||
vertices: Float32Array;
|
||||
buffer: WebGLBuffer;
|
||||
mousedown = false;
|
||||
lastTime = 0;
|
||||
render?: ShaderViewRender;
|
||||
|
||||
constructor(
|
||||
shaderString: string,
|
||||
options?: {
|
||||
parent?: HTMLElement;
|
||||
noRender?: boolean;
|
||||
noAutoStart?: boolean;
|
||||
size?: { width: number; height: number };
|
||||
},
|
||||
) {
|
||||
// shadertoy differences
|
||||
const ioTest = /\(\s*out\s+vec4\s+(\S+)\s*,\s*in\s+vec2\s+(\S+)\s*\)/;
|
||||
const io = shaderString.match(ioTest);
|
||||
shaderString = shaderString.replace("mainImage", "main");
|
||||
shaderString = shaderString.replace(ioTest, "()");
|
||||
const size = options?.size ?? { width: 128, height: 128 };
|
||||
|
||||
// shadertoy built in uniforms
|
||||
const uniforms = this.uniforms = {
|
||||
this.uniforms = {
|
||||
iResolution: {
|
||||
type: "vec3",
|
||||
value: [window.innerWidth, window.innerHeight, 0],
|
||||
value: [size.width, size.height, 0],
|
||||
},
|
||||
iTime: {
|
||||
type: "float",
|
||||
|
@ -48,21 +38,97 @@ export class ShaderView {
|
|||
},
|
||||
};
|
||||
|
||||
this.finalShader = this.preprocessShaderCode(shaderString);
|
||||
|
||||
if (!options?.noRender) {
|
||||
this.render = new ShaderViewRender(this.finalShader, this.uniforms);
|
||||
}
|
||||
|
||||
// attach to parent
|
||||
if (options?.parent) {
|
||||
this.setParent(options.parent);
|
||||
}
|
||||
|
||||
// initial size
|
||||
this.resize(size.width, size.height);
|
||||
|
||||
// auto render unless otherwise specified
|
||||
if (this.render && !options?.noAutoStart) {
|
||||
this.render.start();
|
||||
}
|
||||
}
|
||||
|
||||
preprocessShaderCode(shaderString: string): string {
|
||||
// shadertoy differences
|
||||
const ioTest = /\(\s*out\s+vec4\s+(\S+)\s*,\s*in\s+vec2\s+(\S+)\s*\)/;
|
||||
const io = shaderString.match(ioTest);
|
||||
shaderString = shaderString.replace("mainImage", "main");
|
||||
shaderString = shaderString.replace(ioTest, "()");
|
||||
|
||||
// create default string values
|
||||
shaderString =
|
||||
(io
|
||||
? `#define ${io[1]} gl_FragColor\n#define ${io[2]} gl_FragCoord.xy\n`
|
||||
: "") + shaderString;
|
||||
shaderString = Object.keys(uniforms)
|
||||
shaderString = Object.keys(this.uniforms)
|
||||
.map((key) => ({
|
||||
name: key,
|
||||
type: uniforms[key].type,
|
||||
type: this.uniforms[key].type,
|
||||
}))
|
||||
.reduce((a, uniform) => (
|
||||
a + `uniform ${uniform.type} ${uniform.name};\n`
|
||||
), "") + shaderString;
|
||||
shaderString = "precision highp float;\n" + shaderString;
|
||||
return shaderString;
|
||||
}
|
||||
|
||||
listUniforms(): Record<string, { type: string }> {
|
||||
const result: Record<string, { type: string }> = {};
|
||||
|
||||
for (
|
||||
const uniform of this.finalShader.match(/\buniform\s+\w+\s+\w+/g) ?? []
|
||||
) {
|
||||
const [type, name] = uniform.replaceAll(/\s+/g, " ").split(" ").slice(1);
|
||||
result[name] = { type };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
setUniform(name: string, value: any): void {
|
||||
this.uniforms[name];
|
||||
}
|
||||
|
||||
setParent(parent: HTMLElement): void {
|
||||
if (this.render) {
|
||||
parent.appendChild(this.render.canvas);
|
||||
}
|
||||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
this.uniforms.iResolution.value[0] = width;
|
||||
this.uniforms.iResolution.value[1] = height;
|
||||
if (this.render) {
|
||||
this.render.resize(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShaderViewRender {
|
||||
canvas: HTMLCanvasElement;
|
||||
gl: WebGLRenderingContext;
|
||||
vertexShader: WebGLShader;
|
||||
fragmentShader: WebGLShader;
|
||||
program: WebGLProgram;
|
||||
vertices: Float32Array;
|
||||
buffer: WebGLBuffer;
|
||||
mousedown = false;
|
||||
lastTime = 0;
|
||||
|
||||
constructor(
|
||||
readonly shaderCode: string,
|
||||
readonly uniforms: Record<string, any> = {},
|
||||
) {
|
||||
// create canvas
|
||||
const canvas = this.canvas = document.createElement("canvas");
|
||||
|
||||
|
@ -89,7 +155,7 @@ export class ShaderView {
|
|||
const fragmentShader = this.fragmentShader = check(gl.createShader(
|
||||
gl.FRAGMENT_SHADER,
|
||||
));
|
||||
gl.shaderSource(fragmentShader, shaderString);
|
||||
gl.shaderSource(fragmentShader, this.shaderCode);
|
||||
gl.compileShader(fragmentShader);
|
||||
|
||||
// make program from shaders
|
||||
|
@ -136,25 +202,6 @@ export class ShaderView {
|
|||
canvas.addEventListener("mousedown", (ev) => this.mouseDown(ev));
|
||||
canvas.addEventListener("mousemove", (ev) => this.mouseDown(ev));
|
||||
canvas.addEventListener("mouseup", (ev) => this.mouseUp(ev));
|
||||
|
||||
// attach to parent
|
||||
if (options?.parent) {
|
||||
this.setParent(options.parent);
|
||||
}
|
||||
|
||||
// initial size
|
||||
if (options?.size) {
|
||||
this.resize(options.size.width, options.size.height);
|
||||
}
|
||||
|
||||
// auto render unless otherwise specified
|
||||
if (!options?.noAutoStart) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent: HTMLElement): void {
|
||||
parent.appendChild(this.canvas);
|
||||
}
|
||||
|
||||
mouseDown(e) {
|
||||
|
@ -229,10 +276,10 @@ export class ShaderView {
|
|||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
this.canvas.width = this.uniforms.iResolution.value[0] = width;
|
||||
this.canvas.height = this.uniforms.iResolution.value[1] = height;
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.gl.viewport(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "ESNext",
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"preserveConstEnums": true,
|
||||
"lib": ["dom"]
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
<script type="module">
|
||||
import { ShaderView } from "./mod.js";
|
||||
const shader = `
|
||||
void main()
|
||||
void main()
|
||||
{
|
||||
vec2 uv = (gl_FragCoord.xy * 2. - iResolution.xy) / iResolution.y;
|
||||
float d = length(uv);
|
||||
|
@ -12,8 +12,7 @@
|
|||
d = smoothstep(0.95, 1., 1. - d);
|
||||
vec3 color = d * vec3(1., 2., 3.);
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
`.trim();
|
||||
}`.trim();
|
||||
new ShaderView(shader, {
|
||||
parent: document.getElementById("container"),
|
||||
size: { width: 512, height: 512 },
|
||||
|
|
Loading…
Reference in a new issue