Skip to content
Open
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
151 changes: 112 additions & 39 deletions src/core/CoreNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
mergeColorAlphaPremultiplied,
} from '../utils.js';
import type { TextureOptions } from './CoreTextureManager.js';
import type { WebGlRenderer } from './renderers/webgl/WebGlRenderer.js';
import type { WebGlCtxTexture } from './renderers/webgl/WebGlCtxTexture.js';
import type { BufferCollection } from './renderers/webgl/internal/BufferCollection.js';
import type { CoreRenderer } from './renderers/CoreRenderer.js';
import type { Stage } from './Stage.js';
import {
Expand Down Expand Up @@ -738,6 +741,12 @@ export class CoreNode extends EventEmitter {
readonly children: CoreNode[] = [];
protected _id: number = getNewId();
readonly props: CoreNodeProps;
public readonly isCoreNode = true as const;

// WebGL Render Op State
public renderOpBufferIdx: number = 0;
public numQuads: number = 0;
public renderOpTextures: WebGlCtxTexture[] = [];

private hasShaderUpdater = false;
public hasShaderTimeFn = false;
Expand Down Expand Up @@ -794,6 +803,10 @@ export class CoreNode extends EventEmitter {
constructor(readonly stage: Stage, props: CoreNodeProps) {
super();
const p = (this.props = {} as CoreNodeProps);

// Initialize the renderOpTextures array with a capacity of 16 (typical max textures)
this.renderOpTextures = [];

//inital update type
let initialUpdateType =
UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState;
Expand Down Expand Up @@ -1800,47 +1813,43 @@ export class CoreNode extends EventEmitter {
return;
}

const p = this.props;
const t = this.globalTransform!;
const coords = this.renderCoords;
const texture = p.texture || this.stage.defaultTexture;
const textureCoords =
this.textureCoords || this.stage.renderer.defaultTextureCoords;
const texture = this.renderTexture;

// There is a race condition where the texture can be null
// with RTT nodes. Adding this defensively to avoid errors.
if (texture && texture.state !== 'loaded') {
// Also check if we have a valid texture or default texture to render
if (!texture || texture.state !== 'loaded') {
return;
}

renderer.addQuad({
width: p.w,
height: p.h,
colorTl: this.premultipliedColorTl,
colorTr: this.premultipliedColorTr,
colorBl: this.premultipliedColorBl,
colorBr: this.premultipliedColorBr,
texture,
textureOptions: p.textureOptions,
textureCoords: textureCoords,
shader: p.shader as CoreShaderNode<any>,
alpha: this.worldAlpha,
clippingRect: this.clippingRect,
tx: t.tx,
ty: t.ty,
ta: t.ta,
tb: t.tb,
tc: t.tc,
td: t.td,
renderCoords: coords,
rtt: p.rtt,
zIndex: this.calcZIndex,
parentHasRenderTexture: this.parentHasRenderTexture,
framebufferDimensions: this.parentHasRenderTexture
? this.parentFramebufferDimensions
: null,
time: this.hasShaderTimeFn === true ? this.getTimerValue() : null,
});
renderer.addQuad(this);
}

get renderTexture(): Texture | null {
return this.props.texture || this.stage.defaultTexture;
}

get renderTextureCoords(): TextureCoords | undefined {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very WebGl specific, handle this in WebGLRenderer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Canvas renderer needs this as well

return this.textureCoords || this.stage.renderer.defaultTextureCoords;
}

get quadBufferCollection(): BufferCollection {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very WebGl specific, handle this in WebGLRenderer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoreNodes are now renderOps.

return (this.stage.renderer as WebGlRenderer).quadBufferCollection;
}

get width(): number {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need these, use get w if you really must, but if its for internal use, avoid using getters and just use props.w / props.h

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of overhead. With the change to w / h it wasn't done throughout the code base. So now we face w => width and vice versa and same with height. That requires if statements in a lot of places. If you and the team want to spend the time to make it consistent through out the code base that would allow removing this.

return this.props.w;
}

get height(): number {
return this.props.h;
}

get time(): number {
if (this.hasShaderTimeFn === true) {
return this.getTimerValue();
}
return 0;
}

getTimerValue(): number {
Expand Down Expand Up @@ -2621,12 +2630,12 @@ export class CoreNode extends EventEmitter {
/**
* Returns the framebuffer dimensions of the RTT parent
*/
get parentFramebufferDimensions(): Dimensions {
get parentFramebufferDimensions(): Dimensions | null {
if (this.rttParent !== null) {
return this.rttParent.framebufferDimensions as Dimensions;
return this.rttParent.framebufferDimensions;
}
this.rttParent = this.findParentRTTNode() as CoreNode;
return this.rttParent.framebufferDimensions as Dimensions;
this.rttParent = this.findParentRTTNode();
return this.rttParent ? this.rttParent.framebufferDimensions : null;
}

/**
Expand Down Expand Up @@ -2719,5 +2728,69 @@ export class CoreNode extends EventEmitter {
// no-op
}

/**
* Add a texture to the current RenderOp.
*
* @param texture
* @returns Assigned Texture Index of the texture in the render op
*/
addTexture(texture: WebGlCtxTexture): number {
const textures = this.renderOpTextures;
const length = textures.length;

for (let i = 0; i < length; i++) {
if (textures[i] === texture) {
return i;
}
}

if (length >= 1) {
return 0xffffffff;
}

textures.push(texture);
return length;
}

draw(renderer: WebGlRenderer) {
const { glw, options, stage } = renderer;
const shader = this.props.shader as any;

stage.shManager.useShader(shader.program);
shader.program.bindRenderOp(this);

// Clipping
if (this.clippingRect.valid === true) {
const pixelRatio = this.parentHasRenderTexture ? 1 : stage.pixelRatio;

const clipX = Math.round(this.clippingRect.x * pixelRatio);
const clipWidth = Math.round(this.clippingRect.width * pixelRatio);
const clipHeight = Math.round(this.clippingRect.height * pixelRatio);
let clipY = Math.round(
options.canvas.height - clipHeight - this.clippingRect.y * pixelRatio,
);
// if parent has render texture, we need to adjust the scissor rect
// to be relative to the parent's framebuffer
if (this.parentHasRenderTexture) {
clipY = this.parentFramebufferDimensions
? this.parentFramebufferDimensions.h - this.props.h
: 0;
}

glw.setScissorTest(true);
glw.scissor(clipX, clipY, clipWidth, clipHeight);
} else {
glw.setScissorTest(false);
}

const quadIdx = (this.renderOpBufferIdx / 32) * 6 * 2;
glw.drawElements(
glw.TRIANGLES,
6 * this.numQuads,
glw.UNSIGNED_SHORT,
quadIdx,
);
}

//#endregion Properties
}
2 changes: 1 addition & 1 deletion src/core/renderers/CoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export abstract class CoreRenderer {

abstract reset(): void;
abstract render(surface?: 'screen' | CoreContextTexture): void;
abstract addQuad(quad: QuadOptions): void;
abstract addQuad(node: CoreNode): void;
abstract createCtxTexture(textureSource: Texture): CoreContextTexture;
abstract createShaderProgram(
shaderConfig: Readonly<CoreShaderType>,
Expand Down
110 changes: 67 additions & 43 deletions src/core/renderers/canvas/CanvasRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ export class CanvasRenderer extends CoreRenderer {
// noop
}

addQuad(quad: QuadOptions): void {
addQuad(node: CoreNode): void {
const ctx = this.context;
const { tx, ty, ta, tb, tc, td, clippingRect } = quad;
let texture = quad.texture;
const { tx, ty, ta, tb, tc, td } = node.globalTransform!;
const clippingRect = node.clippingRect;
let texture = node.renderTexture;
// The Canvas2D renderer only supports image textures, no textures are used for color blocks
if (texture !== null) {
const textureType = texture.type;
Expand All @@ -85,12 +86,12 @@ export class CanvasRenderer extends CoreRenderer {

const hasTransform = ta !== 1;
const hasClipping = clippingRect.width !== 0 && clippingRect.height !== 0;
const hasShader = quad.shader !== null;
const shader = node.props.shader;
const hasShader = shader !== null;

let saveAndRestore = hasTransform === true || hasClipping === true;
if (hasShader === true) {
saveAndRestore =
saveAndRestore || (quad.shader as CanvasShaderNode).applySNR;
saveAndRestore = saveAndRestore || (shader as CanvasShaderNode).applySNR;
}

if (saveAndRestore) {
Expand Down Expand Up @@ -121,35 +122,62 @@ export class CanvasRenderer extends CoreRenderer {

if (hasShader === true) {
let renderContext: (() => void) | null = () => {
this.renderContext(quad);
this.renderContext(node);
};
(quad.shader as CanvasShaderNode).render(ctx, quad, renderContext);
const quad: QuadOptions = {
width: node.props.w,
height: node.props.h,
colorTl: node.premultipliedColorTl,
colorTr: node.premultipliedColorTr,
colorBl: node.premultipliedColorBl,
colorBr: node.premultipliedColorBr,
texture: texture,
textureOptions: node.props.textureOptions,
textureCoords: node.renderTextureCoords,
zIndex: node.zIndex, // zIndex usage?
shader: shader,
alpha: node.worldAlpha,
clippingRect: clippingRect,
tx,
ty,
ta,
tb,
tc,
td,
renderCoords: node.renderCoords,
rtt: node.props.rtt,
parentHasRenderTexture: node.parentHasRenderTexture,
framebufferDimensions: node.parentHasRenderTexture
? node.parentFramebufferDimensions
: null,
};

(shader as CanvasShaderNode).render(ctx, quad, renderContext);
renderContext = null;
} else {
this.renderContext(quad);
this.renderContext(node);
}

if (saveAndRestore) {
ctx.restore();
}
}

renderContext(quad: QuadOptions) {
const color = quad.colorTl;
const texture = quad.texture!;
renderContext(node: CoreNode) {
const color = node.premultipliedColorTl;
const texture = node.renderTexture!;
const textureType = texture.type;
const tx = node.globalTransform!.tx;
const ty = node.globalTransform!.ty;
const width = node.props.w;
const height = node.props.h;

if (textureType !== TextureType.color) {
const tintColor = parseColor(color);
if (textureType !== TextureType.subTexture) {
const image = (texture.ctxTexture as CanvasTexture).getImage(tintColor);
this.context.globalAlpha = tintColor.a ?? quad.alpha;
this.context.drawImage(
image,
quad.tx,
quad.ty,
quad.width,
quad.height,
);
this.context.globalAlpha = tintColor.a ?? node.worldAlpha;
this.context.drawImage(image, tx, ty, width, height);
this.context.globalAlpha = 1;
return;
}
Expand All @@ -158,51 +186,47 @@ export class CanvasRenderer extends CoreRenderer {
).getImage(tintColor);
const props = (texture as SubTexture).props;

this.context.globalAlpha = tintColor.a ?? quad.alpha;
this.context.globalAlpha = tintColor.a ?? node.worldAlpha;
this.context.drawImage(
image,
props.x,
props.y,
props.w,
props.h,
quad.tx,
quad.ty,
quad.width,
quad.height,
tx,
ty,
width,
height,
);
this.context.globalAlpha = 1;
return;
}
const hasGradient =
quad.colorTl !== quad.colorTr || quad.colorTl !== quad.colorBr;
node.premultipliedColorTl !== node.premultipliedColorTr ||
node.premultipliedColorTl !== node.premultipliedColorBr;
if (hasGradient === true) {
let endX: number = quad.tx;
let endY: number = quad.ty;
let endX: number = tx;
let endY: number = ty;
let endColor: number;
if (quad.colorTl === quad.colorTr) {
if (node.premultipliedColorTl === node.premultipliedColorTr) {
// vertical
endX = quad.tx;
endY = quad.ty + quad.height;
endColor = quad.colorBr;
endX = tx;
endY = ty + height;
endColor = node.premultipliedColorBr;
} else {
// horizontal
endX = quad.tx + quad.width;
endY = quad.ty;
endColor = quad.colorTr;
endX = tx + width;
endY = ty;
endColor = node.premultipliedColorTr;
}
const gradient = this.context.createLinearGradient(
quad.tx,
quad.ty,
endX,
endY,
);
const gradient = this.context.createLinearGradient(tx, ty, endX, endY);
gradient.addColorStop(0, normalizeCanvasColor(color));
gradient.addColorStop(1, normalizeCanvasColor(endColor));
this.context.fillStyle = gradient;
this.context.fillRect(quad.tx, quad.ty, quad.width, quad.height);
this.context.fillRect(tx, ty, width, height);
} else {
this.context.fillStyle = normalizeCanvasColor(color);
this.context.fillRect(quad.tx, quad.ty, quad.width, quad.height);
this.context.fillRect(tx, ty, width, height);
}
}

Expand Down
Loading