Skip to content

added timing functionality to shaders #573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions examples/tests/shader-time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { WebGlShaderType } from '../../dist/exports/webgl-shaders.js';
import type { ExampleSettings } from '../common/ExampleSettings.js';

export default async function ({ renderer, testRoot }: ExampleSettings) {
renderer.stage.shManager.registerShaderType('Spinner', Spinner);

renderer.createNode({
x: 90,
y: 90,
width: 90,
height: 90,
color: 0xff0000ff,
shader: renderer.createShader('Spinner'),
parent: testRoot,
});

renderer.createNode({
x: 290,
y: 90,
width: 90,
height: 90,
color: 0xff0000ff,
shader: renderer.createShader('Spinner', {
clockwise: false,
}),
parent: testRoot,
});

renderer.createNode({
x: 490,
y: 90,
width: 90,
height: 90,
color: 0xff0000ff,
shader: renderer.createShader('Spinner', {
period: 0.4,
}),
parent: testRoot,
});
}

export const Spinner: WebGlShaderType = {
props: {
clockwise: true,
period: 1,
},
update() {
this.uniform1f('u_clockwise', this.props!.clockwise === true ? 1 : -1);
this.uniform1f('u_period', this.props!.period as number);
},
time: true,
vertex: `
# ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
# else
precision mediump float;
# endif

attribute vec2 a_position;
attribute vec2 a_textureCoords;
attribute vec4 a_color;
attribute vec2 a_nodeCoords;

uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform vec2 u_dimensions;
uniform float u_time;

uniform float u_period;

varying vec4 v_color;
varying vec2 v_textureCoords;
varying vec2 v_nodeCoords;

varying vec2 v_innerSize;
varying vec2 v_halfDimensions;
varying float v_time;

void main() {
vec2 normalized = a_position * u_pixelRatio;
vec2 screenSpace = vec2(2.0 / u_resolution.x, -2.0 / u_resolution.y);

v_color = a_color;
v_nodeCoords = a_nodeCoords;
v_textureCoords = a_textureCoords;

v_halfDimensions = u_dimensions * 0.5;

v_time = u_time / 1000.0 / u_period;

gl_Position = vec4(normalized.x * screenSpace.x - 1.0, normalized.y * -abs(screenSpace.y) + 1.0, 0.0, 1.0);
gl_Position.y = -sign(screenSpace.y) * gl_Position.y;
}
`,
fragment: `
# ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
# else
precision mediump float;
# endif

#define PI 3.14159265359

uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_alpha;
uniform vec2 u_dimensions;
uniform sampler2D u_texture;

uniform float u_clockwise;

varying vec4 v_color;
varying vec2 v_nodeCoords;
varying vec2 v_textureCoords;

varying vec2 v_halfDimensions;
varying float v_time;

float circleDist(vec2 p, float radius){
return length(p) - radius;
}

float fillMask(float dist){
return clamp(-dist, 0.0, 1.0);
}

void main() {
vec4 color = texture2D(u_texture, v_textureCoords) * v_color;
vec2 uv = v_nodeCoords.xy * u_dimensions - v_halfDimensions;

float c = max(-circleDist(uv, v_halfDimensions.x - 10.0), circleDist(uv, v_halfDimensions.x));
float r = -v_time * 6.0 * u_clockwise;

uv *= mat2(cos(r), sin(r), -sin(r), cos(r));

float a = u_clockwise * atan(uv.x, uv.y) * PI * 0.05 + 0.45;

gl_FragColor = mix(vec4(0.0), color, fillMask(c) * a);
}
`,
};
11 changes: 10 additions & 1 deletion src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,8 @@ export class CoreNode extends EventEmitter {
readonly props: CoreNodeProps;

private hasShaderUpdater = false;
public hasShaderTimeFn = false;
private hasColorProps = false;

public updateType = UpdateType.All;
public childUpdateType = UpdateType.None;

Expand Down Expand Up @@ -1735,9 +1735,17 @@ export class CoreNode extends EventEmitter {
framebufferDimensions: this.parentHasRenderTexture
? this.parentFramebufferDimensions
: null,
time: this.hasShaderTimeFn === true ? this.getTimerValue() : null,
});
}

getTimerValue(): number {
if (typeof this.shader!.time === 'function') {
return this.shader!.time(this.stage);
}
return this.stage.elapsedTime;
}

//#region Properties
get id(): number {
return this._id;
Expand Down Expand Up @@ -2295,6 +2303,7 @@ export class CoreNode extends EventEmitter {
}
if (shader.shaderKey !== 'default') {
this.hasShaderUpdater = shader.update !== undefined;
this.hasShaderTimeFn = shader.time !== undefined;
shader.attachNode(this);
}
this.props.shader = shader;
Expand Down
16 changes: 15 additions & 1 deletion src/core/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,12 @@ export class Stage {
public readonly eventBus: EventEmitter;

/// State
startTime = 0;
deltaTime = 0;
lastFrameTime = 0;
currentFrameTime = 0;
elapsedTime = 0;
private timedNodes = 0;
private clrColor = 0x00000000;
private fpsNumFrames = 0;
private fpsElapsedTime = 0;
Expand Down Expand Up @@ -163,6 +166,8 @@ export class Stage {

this.platform = platform;

this.startTime = platform.getTimeStamp();

this.eventBus = options.eventBus;
this.txManager = new CoreTextureManager(this, {
numImageWorkers,
Expand Down Expand Up @@ -302,9 +307,10 @@ export class Stage {
}

updateFrameTime() {
const newFrameTime = this.platform!.getTimeStamp();
const newFrameTime = this.platform.getTimeStamp();
this.lastFrameTime = this.currentFrameTime;
this.currentFrameTime = newFrameTime;
this.elapsedTime = this.startTime - newFrameTime;
this.deltaTime = !this.lastFrameTime
? 100 / 6
: newFrameTime - this.lastFrameTime;
Expand Down Expand Up @@ -415,6 +421,11 @@ export class Stage {
if (renderRequested === true) {
this.renderRequested = false;
}

if (this.timedNodes > 0) {
this.timedNodes = 0;
this.requestRender();
}
}

/**
Expand Down Expand Up @@ -489,6 +500,9 @@ export class Stage {
// If the node is renderable and has a loaded texture, render it
if (node.isRenderable === true) {
node.renderQuads(this.renderer);
if (node.hasShaderTimeFn === true) {
this.timedNodes++;
}
}

for (let i = 0; i < node.children.length; i++) {
Expand Down
1 change: 1 addition & 0 deletions src/core/renderers/CoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface QuadOptions {
rtt: boolean;
parentHasRenderTexture: boolean;
framebufferDimensions: Dimensions | null;
time?: number | null;
}

export interface CoreRendererOptions {
Expand Down
7 changes: 7 additions & 0 deletions src/core/renderers/CoreShaderNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export interface CoreShaderType<Props extends object = any> {
* used for making a cache key to check for reusability, currently only used for webgl ShaderTypes but might be needed for other types of renderer
*/
getCacheMarkers?: (props: Props) => string;
/**
* timer that updates every loop, by default uses the stage elapsed time If you want to do a special calculation you can define a function.
* When you calculate your own value you can use the Stage timing values deltaTime, lastFrameTime, and currentFrameTime;
*/
time?: boolean | ((stage: Stage) => number);
}

/**
Expand All @@ -89,6 +94,7 @@ export class CoreShaderNode<Props extends object = Record<string, unknown>> {
readonly resolvedProps: Props | undefined = undefined;
protected definedProps: Props | undefined = undefined;
protected node: CoreNode | null = null;
readonly time: CoreShaderType['time'] = undefined;
update: (() => void) | undefined = undefined;

constructor(
Expand All @@ -99,6 +105,7 @@ export class CoreShaderNode<Props extends object = Record<string, unknown>> {
) {
this.stage = stage;
this.shaderType = type;
this.time = type.time;

if (props !== undefined) {
/**
Expand Down
5 changes: 4 additions & 1 deletion src/core/renderers/webgl/WebGlRenderOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ type ReqQuad =
| 'rtt'
| 'clippingRect'
| 'height'
| 'width';
| 'width'
| 'time';
type RenderOpQuadOptions = Pick<QuadOptions, ReqQuad> &
Partial<Omit<QuadOptions, ReqQuad>> & {
sdfShaderProps?: Record<string, unknown>;
Expand Down Expand Up @@ -65,6 +66,7 @@ export class WebGlRenderOp extends CoreRenderOp {
readonly framebufferDimensions?: Dimensions | null;
readonly alpha: number;
readonly pixelRatio: number;
readonly time?: number | null;

constructor(
readonly renderer: WebGlRenderer,
Expand All @@ -83,6 +85,7 @@ export class WebGlRenderOp extends CoreRenderOp {
this.alpha = quad.alpha;
this.pixelRatio =
this.parentHasRenderTexture === true ? 1 : renderer.stage.pixelRatio;
this.time = quad.time;

/**
* related to line 51
Expand Down
16 changes: 16 additions & 0 deletions src/core/renderers/webgl/WebGlShaderProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class WebGlShaderProgram implements CoreShaderProgram {
protected lifecycle: Pick<WebGlShaderType, 'update' | 'canBatch'>;
protected useSystemAlpha = false;
protected useSystemDimensions = false;
protected useTimeValue = false;
supportsIndexedTextures = false;

constructor(
Expand Down Expand Up @@ -122,6 +123,10 @@ export class WebGlShaderProgram implements CoreShaderProgram {
this.useSystemDimensions =
this.glw.getUniformLocation(program, 'u_dimensions') !== null;

this.useTimeValue =
this.glw.getUniformLocation(program, 'u_dimensions') !== null &&
config.time !== undefined;

this.lifecycle = {
update: config.update,
canBatch: config.canBatch,
Expand Down Expand Up @@ -149,6 +154,12 @@ export class WebGlShaderProgram implements CoreShaderProgram {
return this.lifecycle.canBatch(incomingQuad, currentRenderOp);
}

if (this.useTimeValue === true) {
if (incomingQuad.time !== currentRenderOp.time) {
return false;
}
}

if (this.useSystemAlpha === true) {
if (incomingQuad.alpha !== currentRenderOp.alpha) {
return false;
Expand All @@ -169,6 +180,7 @@ export class WebGlShaderProgram implements CoreShaderProgram {
if (incomingQuad.shader !== null) {
shaderPropsA = incomingQuad.shader.resolvedProps;
}

if (currentRenderOp.shader !== null) {
shaderPropsB = currentRenderOp.shader.resolvedProps;
}
Expand Down Expand Up @@ -221,6 +233,10 @@ export class WebGlShaderProgram implements CoreShaderProgram {
);
}

if (this.useTimeValue === true) {
this.glw.uniform1f('u_time', renderOp.time as number);
}

// if (this.useSystemAlpha) {
this.glw.uniform1f('u_alpha', renderOp.alpha);
// }
Expand Down