Skip to content

Commit

Permalink
feat(webgl): migrate multipass() & types from webgl-shadertoy pkg, reorg
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Sep 20, 2019
1 parent a45725a commit 2aa31ce
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 10 deletions.
63 changes: 63 additions & 0 deletions packages/webgl/src/api/multipass.ts
@@ -0,0 +1,63 @@
import { IObjectOf } from "@thi.ng/api";
import { AttribPool } from "@thi.ng/vector-pools";
import { IFbo, IndexBufferSpec } from "./buffers";
import { InstancingSpec, ModelAttributeSpecs, ModelSpec } from "./model";
import {
ShaderFn,
ShaderState,
ShaderVaryingSpecs,
UniformDecl,
UniformValues
} from "./shader";
import { ITexture, TextureOpts } from "./texture";

export interface Multipass {
start(): void;
stop(): void;
update(time?: number): void;

fbos: IFbo[];
models: ModelSpec[];
passes: PassOpts[];
textures: IObjectOf<ITexture>;
}

export interface MultipassOpts {
gl: WebGLRenderingContext;
textures: IObjectOf<Partial<TextureOpts>>;
passes: PassOpts[];
width: number;
height: number;
uniforms?: Partial<PassUniforms>;
uniformVals?: UniformValues;
}

export interface PassOpts {
vs?: string | ShaderFn;
fs: string | ShaderFn;
model?: PassModelSpec;
inputs: string[];
outputs: string[];
varying?: ShaderVaryingSpecs;
uniforms?: Partial<PassUniforms>;
uniformVals?: UniformValues;
state?: ShaderState;
}

export interface PassUniforms {
inputs: never;
outputs: never;
resolution: "vec2";
time: "float";
[id: string]: UniformDecl;
}

export interface PassModelSpec {
attribs: ModelAttributeSpecs;
attribPool?: AttribPool;
uniforms?: UniformValues;
indices?: IndexBufferSpec;
instances?: InstancingSpec;
mode?: GLenum;
num: number;
}
10 changes: 3 additions & 7 deletions packages/webgl/src/gpgpu.ts
Expand Up @@ -24,8 +24,8 @@ import { isGL2Context } from "./checks";
import { draw } from "./draw";
import { FBO } from "./fbo";
import { quad } from "./geo/quad";
import { FX_SHADER_SPEC_UV } from "./pipeline";
import { shader } from "./shader";
import { FX_SHADER_SPEC_UV } from "./shaders/pipeline";
import { floatTexture, texture } from "./texture";

export const gpgpu = (opts: GPGPUOpts) => new GPGPU(opts);
Expand Down Expand Up @@ -189,9 +189,7 @@ export class GPGPUJob implements IRelease {
const expectedSize = ctx.inputSize(i);
assert(
tex.length >= expectedSize,
`input #${i} too small (got ${
tex.length
}, expected ${expectedSize})`
`input #${i} too small (got ${tex.length}, expected ${expectedSize})`
);
const input = ctx.inputs[i];
input.tex.configure({
Expand Down Expand Up @@ -250,9 +248,7 @@ export class GPGPUJob implements IRelease {
if (opts.src) {
shaderSpec = {
...FX_SHADER_SPEC_UV,
pre: `#define WIDTH (${ctx.width})\n#define SIZE (ivec2(${
ctx.width
}))`,
pre: `#define WIDTH (${ctx.width})\n#define SIZE (ivec2(${ctx.width}))`,
fs: opts.src,
uniforms: { ...opts.uniforms },
outputs: transduce(
Expand Down
3 changes: 2 additions & 1 deletion packages/webgl/src/index.ts
Expand Up @@ -18,7 +18,7 @@ export * from "./fbo";
export * from "./gpgpu";
export * from "./material";
export * from "./matrices";
export * from "./pipeline";
export * from "./multipass";
export * from "./rbo";
export * from "./shader";
export * from "./syntax";
Expand All @@ -27,6 +27,7 @@ export * from "./utils";

export * from "./shaders/lambert";
export * from "./shaders/phong";
export * from "./shaders/pipeline";

export * from "./textures/checkerboard";
export * from "./textures/stripes";
Expand Down
157 changes: 157 additions & 0 deletions packages/webgl/src/multipass.ts
@@ -0,0 +1,157 @@
import { assert, IObjectOf } from "@thi.ng/api";
import {
assocObj,
map,
range,
some,
transduce
} from "@thi.ng/transducers";
import { ExtensionBehaviors } from "./api/ext";
import { Multipass, MultipassOpts, PassOpts } from "./api/multipass";
import { ShaderSpec, ShaderUniformSpecs } from "./api/shader";
import { ITexture } from "./api/texture";
import { compileModel } from "./buffer";
import { isFloatTexture, isGL2Context } from "./checks";
import { draw } from "./draw";
import { fbo } from "./fbo";
import { quad } from "./geo/quad";
import { shader } from "./shader";
import { PASSTHROUGH_VS } from "./shaders/pipeline";
import { texture } from "./texture";

export const multipass = (opts: MultipassOpts) => {
const gl = opts.gl;
const isGL2 = isGL2Context(gl);
const numPasses = opts.passes.length;
assert(numPasses > 0, "require at least one shader pass");

const initShader = (pass: PassOpts) => {
const numIns = pass.inputs.length;
const numOuts = pass.outputs.length;
const ext: ExtensionBehaviors = {};
const spec: ShaderSpec = {
vs: pass.vs || PASSTHROUGH_VS,
fs: pass.fs,
attribs: {
position: "vec2"
},
varying: pass.varying,
uniforms: <ShaderUniformSpecs>{
...pass.uniforms,
...(numIns
? {
inputs: ["sampler2D[]", numIns, [...range(numIns)]]
}
: null)
},
outputs: numOuts
? transduce(
map((i) => [`output${i}`, ["vec4", i]]),
assocObj(),
range(numOuts)
)
: undefined,
state: pass.state,
ext
};
const floatIn = some((id) => isFloatTexture(textures[id]), pass.inputs);
const floatOut = some(
(id) => isFloatTexture(textures[id]),
pass.outputs
);
if (!isGL2) {
floatIn && (ext.OES_texture_float = "require");
numOuts > 1 && (ext.WEBGL_draw_buffers = "require");
}
if (floatOut) {
ext[isGL2 ? "EXT_color_buffer_float" : "WEBGL_color_buffer_float"] =
"require";
}
return shader(gl, spec);
};

const textures = Object.keys(opts.textures).reduce(
(acc, id) => {
acc[id] = texture(gl, {
width: opts.width,
height: opts.height,
filter: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
image: null,
...opts.textures[id]
});
return acc;
},
<IObjectOf<ITexture>>{}
);

const model = compileModel(gl, quad(false));
const models = opts.passes.map((pass) => {
const m = pass.model ? compileModel(gl, <any>pass.model) : { ...model };
m.shader = initShader(pass);
m.uniforms = { ...pass.uniformVals };
pass.inputs.length > 0 &&
(m.textures = pass.inputs.map((id) => textures[id]));
return m;
});

const useMainBuffer = !opts.passes[numPasses - 1].outputs.length;
const fbos = (useMainBuffer
? opts.passes.slice(0, numPasses - 1)
: opts.passes
).map((pass) => fbo(gl, { tex: pass.outputs.map((id) => textures[id]) }));

const drawPass = (i: number, time: number) => {
const pass = opts.passes[i];
const model = models[i];
const shader = model.shader;
const size = pass.outputs.length
? textures[pass.outputs[0]].size
: [gl.drawingBufferWidth, gl.drawingBufferHeight];
shader.uniforms.resolution && (model.uniforms!.resolution = size);
shader.uniforms.time && (model.uniforms!.time = time);
gl.viewport(0, 0, size[0], size[1]);
draw(model);
};

const update = (time: number) => {
for (let i = 0; i < fbos.length; i++) {
fbos[i].bind();
drawPass(i, time);
fbos[i].unbind();
}
useMainBuffer && drawPass(numPasses - 1, time);
};

const updateRAF = () => {
update((Date.now() - t0) * 1e-3);
active && (rafID = requestAnimationFrame(updateRAF));
};

let active: boolean;
let t0 = Date.now();
let rafID: number;

const instance: Multipass = {
start() {
t0 = Date.now();
active = true;
rafID = requestAnimationFrame(updateRAF);
},
stop() {
if (active) {
active = false;
cancelAnimationFrame(rafID);
}
},
update(time: number) {
update(time);
},
passes: opts.passes,
fbos,
models,
textures
};

return instance;
};
Expand Up @@ -7,8 +7,7 @@ import {
texture,
vec4
} from "@thi.ng/shader-ast";
import { ShaderFn, ShaderSpec } from "./api/shader";
import { texture as _texture } from "./texture";
import { ShaderFn, ShaderSpec } from "../api/shader";

export const PASSTHROUGH_VS: ShaderFn = (gl, _, ins) => [
defMain(() => [assign(gl.gl_Position, vec4(ins.position, FLOAT0, FLOAT1))])
Expand Down

0 comments on commit 2aa31ce

Please sign in to comment.