Skip to content
Merged
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
34 changes: 20 additions & 14 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ import type { ExampleSettings } from './common/ExampleSettings.js';

(async () => {
// URL params
// - driver: main | threadx (default: threadx)
// - test: <test name> (default: test)
// - showOverlay: true | false (default: true)
const urlParams = new URLSearchParams(window.location.search);
let driverName = urlParams.get('driver');
const test = urlParams.get('test') || 'test';
const showOverlay = urlParams.get('overlay') !== 'false';

if (driverName !== 'main' && driverName !== 'threadx') {
driverName = 'threadx';
Expand Down Expand Up @@ -72,20 +76,22 @@ import type { ExampleSettings } from './common/ExampleSettings.js';

assertTruthy(canvas instanceof HTMLCanvasElement);

const overlayText = renderer.createTextNode({
color: 0xff0000ff,
text: `Test: ${test} | Driver: ${driverName}`,
zIndex: 99999,
parent: renderer.root,
fontSize: 50,
});
overlayText.once(
'textLoaded',
(target: any, { width, height }: Dimensions) => {
overlayText.x = appDimensions.width - width - 20;
overlayText.y = appDimensions.height - height - 20;
},
);
if (showOverlay) {
const overlayText = renderer.createTextNode({
color: 0xff0000ff,
text: `Test: ${test} | Driver: ${driverName}`,
zIndex: 99999,
parent: renderer.root,
fontSize: 50,
});
overlayText.once(
'textLoaded',
(target: any, { width, height }: Dimensions) => {
overlayText.x = appDimensions.width - width - 20;
overlayText.y = appDimensions.height - height - 20;
},
);
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const module = await import(`./tests/${test}.ts`);
Expand Down
7 changes: 5 additions & 2 deletions src/core/renderers/CoreShader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ export abstract class CoreShader {
return {};
}

abstract bindRenderOp(renderOp: CoreRenderOp): void;
abstract bindProps(props: Record<string, unknown>): void;
abstract bindRenderOp(
renderOp: CoreRenderOp,
props: Record<string, unknown> | null,
): void;
protected abstract bindProps(props: Record<string, unknown>): void;
abstract attach(): void;
abstract detach(): void;
}
3 changes: 1 addition & 2 deletions src/core/renderers/webgl/WebGlCoreRenderOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ export class WebGlCoreRenderOp extends CoreRenderOp {

const { shManager } = options;
shManager.useShader(shader);
shader.bindRenderOp(this);
shader.bindProps(shaderProps);
shader.bindRenderOp(this, shaderProps);

// TODO: Reduce calculations required
const quadIdx = (this.bufferIdx / 24) * 6 * 2;
Expand Down
28 changes: 24 additions & 4 deletions src/core/renderers/webgl/WebGlCoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import {
assertTruthy,
createWebGLContext,
hasOwn,
mergeColorAlphaPremultiplied,
} from '../../../utils.js';
import {
Expand Down Expand Up @@ -48,9 +49,10 @@ import type {
import { CoreShaderManager } from '../../CoreShaderManager.js';
import type { CoreShader } from '../CoreShader.js';
import { BufferCollection } from './internal/BufferCollection.js';
import { getNormalizedRgbaComponents } from '../../lib/utils.js';
import { getNormalizedRgbaComponents, type Rect } from '../../lib/utils.js';
import type { Dimensions } from '../../../common/CommonTypes.js';
import { WebGlCoreShader } from './WebGlCoreShader.js';
import { RoundedRectangle } from './shaders/RoundedRectangle.js';

const WORDS_PER_QUAD = 24;
const BYTES_PER_QUAD = WORDS_PER_QUAD * 4;
Expand Down Expand Up @@ -94,7 +96,7 @@ export class WebGlCoreRenderer extends CoreRenderer {
renderables: Array<QuadOptions | WebGlCoreRenderOp> = [];

//// Default Shader
defaultShader: CoreShader;
defaultShader: WebGlCoreShader;
quadBufferCollection: BufferCollection;

/**
Expand Down Expand Up @@ -231,6 +233,17 @@ export class WebGlCoreRenderer extends CoreRenderer {
} = params;
let { texture } = params;

/**
* If the shader props contain any automatic properties, update it with the
* current dimensions that will be used to render the quad.
*/
if (shaderProps && hasOwn(shaderProps, '$dimensions')) {
const dimensions = shaderProps.$dimensions as Dimensions;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
dimensions.width = width;
dimensions.height = height;
}

texture = texture ?? this.defaultTexture;
assertTruthy(texture instanceof Texture, 'Invalid texture type');

Expand All @@ -241,13 +254,20 @@ export class WebGlCoreRenderer extends CoreRenderer {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
};
const targetShader = shader || this.defaultShader;
assertTruthy(targetShader instanceof WebGlCoreShader);
if (curRenderOp) {
// If the current render op is not the same shader, create a new one
// If the current render op's shader props are not compatible with the
// the new shader props, create a new one render op.
if (curRenderOp.shader !== targetShader) {
curRenderOp = null;
} else if (
curRenderOp.shader !== this.defaultShader &&
(curRenderOp.shaderProps !== shaderProps ||
curRenderOp.dimensions !== targetDims)
(!shaderProps ||
!curRenderOp.shader.canBatchShaderProps(
curRenderOp.shaderProps,
shaderProps,
))
) {
curRenderOp = null;
}
Expand Down
73 changes: 63 additions & 10 deletions src/core/renderers/webgl/WebGlCoreShader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
* limitations under the License.
*/

import { assertTruthy } from '../../../utils.js';
import type { Dimensions } from '../../../common/CommonTypes.js';
import { assertTruthy, hasOwn } from '../../../utils.js';
import { CoreShader } from '../CoreShader.js';
import type { WebGlCoreCtxTexture } from './WebGlCoreCtxTexture.js';
import type { WebGlCoreRenderOp } from './WebGlCoreRenderOp.js';
Expand All @@ -34,6 +35,24 @@ import {
} from './internal/ShaderUtils.js';
import { isWebGl2 } from './internal/WebGlUtils.js';

/**
* Automatic shader prop for the dimensions of the Node being rendered
*
* @remarks
* Shader's who's rendering depends on the dimensions of the Node being rendered
* should extend this interface from their Prop interface type.
*/
export interface DimensionsShaderProp {
/**
* Dimensions of the Node being rendered (Auto-set by the renderer)
*
* @remarks
* DO NOT SET THIS. It is set automatically by the renderer.
* Any values set here will be ignored.
*/
$dimensions?: Dimensions;
}

export abstract class WebGlCoreShader extends CoreShader {
protected boundBufferCollection: BufferCollection | null = null;
protected buffersBound = false;
Expand Down Expand Up @@ -197,12 +216,52 @@ export abstract class WebGlCoreShader extends CoreShader {
this.boundBufferCollection = null;
}

bindRenderOp(renderOp: WebGlCoreRenderOp) {
/**
* Given two sets of Shader props destined for this Shader, determine if they can be batched together
* to reduce the number of draw calls.
*
* @remarks
* This is used by the {@link WebGlCoreRenderer} to determine if it can batch multiple consecutive draw
* calls into a single draw call.
*
* By default, this returns false (meaning no batching is allowed), but can be
* overridden by child classes to provide more efficient batching.
*
* @param propsA
* @param propsB
* @returns
*/
canBatchShaderProps(
propsA: Record<string, unknown>,
propsB: Record<string, unknown>,
): boolean {
return false;
}

bindRenderOp(
renderOp: WebGlCoreRenderOp,
props: Record<string, unknown> | null,
) {
this.bindBufferCollection(renderOp.buffers);
if (renderOp.textures.length > 0) {
this.bindTextures(renderOp.textures);
}
this.bindUniforms(renderOp);
const { gl } = renderOp;
// Bind standard automatic uniforms
this.setUniform('u_resolution', [gl.canvas.width, gl.canvas.height]); // !!!
this.setUniform('u_pixelRatio', renderOp.options.pixelRatio);
if (props) {
// Bind optional automatic uniforms
// These are only bound if their keys are present in the props.
if (hasOwn(props, '$dimensions')) {
let dimensions = props.$dimensions as Dimensions | null;
if (!dimensions) {
dimensions = renderOp.dimensions;
}
this.setUniform('u_dimensions', [dimensions.width, dimensions.height]);
}
this.bindProps(props);
}
}

setUniform(name: string, value: any): void {
Expand All @@ -228,20 +287,14 @@ export abstract class WebGlCoreShader extends CoreShader {
this.boundBufferCollection = buffer;
}

override bindProps(props: Record<string, unknown>) {
protected override bindProps(props: Record<string, unknown>) {
// Implement in child class
}

bindTextures(textures: WebGlCoreCtxTexture[]) {
// no defaults
}

bindUniforms(renderOp: WebGlCoreRenderOp) {
const { gl } = renderOp;
this.setUniform('u_resolution', [gl.canvas.width, gl.canvas.height]);
this.setUniform('u_pixelRatio', renderOp.options.pixelRatio);
}

override attach(): void {
this.gl.useProgram(this.program);
if (isWebGl2(this.gl) && this.vao) {
Expand Down
34 changes: 18 additions & 16 deletions src/core/renderers/webgl/shaders/DynamicShader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
*/
import type { ExtractProps } from '../../../CoreTextureManager.js';
import type { WebGlCoreRenderer } from '../WebGlCoreRenderer.js';
import { WebGlCoreShader } from '../WebGlCoreShader.js';
import {
WebGlCoreShader,
type DimensionsShaderProp,
} from '../WebGlCoreShader.js';
import type { UniformInfo } from '../internal/ShaderUtils.js';
import type { WebGlCoreRenderOp } from '../WebGlCoreRenderOp.js';
import type { WebGlCoreCtxTexture } from '../WebGlCoreCtxTexture.js';
Expand All @@ -33,7 +36,7 @@ import { BorderBottomEffect } from './effects/BorderBottomEffect.js';
import { BorderLeftEffect } from './effects/BorderLeftEffect.js';
import { GlitchEffect } from './effects/GlitchEffect.js';

export interface DynamicShaderProps {
export interface DynamicShaderProps extends DimensionsShaderProp {
effects?: EffectDesc[];
}

Expand Down Expand Up @@ -98,13 +101,7 @@ export class DynamicShader extends WebGlCoreShader {
gl.bindTexture(gl.TEXTURE_2D, textures[0]!.ctxTexture);
}

override bindUniforms(renderOp: WebGlCoreRenderOp) {
super.bindUniforms(renderOp);
const { width = 100, height = 100 } = renderOp.dimensions;
this.setUniform('u_dimensions', [width, height]);
}

override bindProps(props: DynamicShaderProps): void {
protected override bindProps(props: Required<DynamicShaderProps>): void {
props.effects?.forEach((eff, index) => {
const effect = this.effects[index]!;
const fxClass = Effects[effect.name as keyof EffectMap];
Expand Down Expand Up @@ -313,13 +310,18 @@ export class DynamicShader extends WebGlCoreShader {
}

static override resolveDefaults(
props: DynamicShaderProps = {},
): Record<string, unknown> {
props.effects = (props.effects ?? []).map((effect) => ({
type: effect.type,
props: Effects[effect.type].resolveDefaults(effect.props || {}),
}));
return props as Record<string, unknown>;
props: DynamicShaderProps,
): Required<DynamicShaderProps> {
return {
effects: (props.effects ?? []).map((effect) => ({
type: effect.type,
props: Effects[effect.type].resolveDefaults(effect.props || {}),
})),
$dimensions: {
width: 0,
height: 0,
},
};
}

static override makeCacheKey(props: DynamicShaderProps): string {
Expand Down
34 changes: 21 additions & 13 deletions src/core/renderers/webgl/shaders/RoundedRectangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@
*/

import type { WebGlCoreRenderer } from '../WebGlCoreRenderer.js';
import { WebGlCoreShader } from '../WebGlCoreShader.js';
import {
WebGlCoreShader,
type DimensionsShaderProp,
} from '../WebGlCoreShader.js';
import type { WebGlCoreCtxTexture } from '../WebGlCoreCtxTexture.js';
import type { ShaderProgramSources } from '../internal/ShaderUtils.js';
import type { WebGlCoreRenderOp } from '../WebGlCoreRenderOp.js';

/**
* Properties of the {@link RoundedRectangle} shader
*/
export interface RoundedRectangleProps {
export interface RoundedRectangleProps extends DimensionsShaderProp {
/**
* Corner radius, in pixels, to cut out of the corners
*
* @default 10
* @defaultValue 10
*/
radius?: number;
}
Expand Down Expand Up @@ -66,6 +68,10 @@ export class RoundedRectangle extends WebGlCoreShader {
): Required<RoundedRectangleProps> {
return {
radius: props.radius || 10,
$dimensions: {
width: 0,
height: 0,
},
};
}

Expand All @@ -75,17 +81,19 @@ export class RoundedRectangle extends WebGlCoreShader {
gl.bindTexture(gl.TEXTURE_2D, textures[0]!.ctxTexture);
}

override bindUniforms(renderOp: WebGlCoreRenderOp) {
super.bindUniforms(renderOp);
const { width = 100, height = 100 } = renderOp.dimensions;
this.setUniform('u_dimensions', [width, height]);
protected override bindProps(props: Required<RoundedRectangleProps>): void {
this.setUniform('u_radius', props.radius);
}

override bindProps(props: RoundedRectangleProps): void {
for (const key in props) {
// @ts-expect-error to fancy code
this.setUniform(`u_${key}`, props[key]);
}
override canBatchShaderProps(
propsA: Required<RoundedRectangleProps>,
propsB: Required<RoundedRectangleProps>,
): boolean {
return (
propsA.radius === propsB.radius &&
propsA.$dimensions.width === propsB.$dimensions.width &&
propsA.$dimensions.height === propsB.$dimensions.height
);
}

static override shaderSources: ShaderProgramSources = {
Expand Down
2 changes: 1 addition & 1 deletion src/core/renderers/webgl/shaders/SdfShader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class SdfShader extends WebGlCoreShader {
gl.bindTexture(gl.TEXTURE_2D, textures[0]!.ctxTexture);
}

override bindProps(props: SdfShaderProps): void {
protected override bindProps(props: SdfShaderProps): void {
const resolvedProps = SdfShader.resolveDefaults(props);
for (const key in resolvedProps) {
if (key === 'offset') {
Expand Down
Loading