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
4 changes: 2 additions & 2 deletions src/core/CoreTextNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
this._textRendererOverride = props.textRendererOverride;
this.textRenderer = textRenderer;
const textRendererState = this.createState({
x: this.absX,
y: this.absY,
x: 0,
y: 0,
width: props.width,
height: props.height,
textAlign: props.textAlign,
Expand Down
37 changes: 27 additions & 10 deletions src/core/CoreTextureManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,18 @@ export class CoreTextureManager extends EventEmitter {

// For non-image textures, upload immediately
if (texture.type !== TextureType.image) {
this.uploadTexture(texture);
this.uploadTexture(texture).catch((err) => {
console.error('Failed to upload non-image texture:', err);
texture.setState('failed');
});
} else {
// For image textures, queue for throttled upload
// If it's a priority texture, upload it immediately
if (priority === true) {
this.uploadTexture(texture);
this.uploadTexture(texture).catch((err) => {
console.error('Failed to upload priority texture:', err);
texture.setState('failed');
});
} else {
this.enqueueUploadTexture(texture);
}
Expand All @@ -410,8 +416,9 @@ export class CoreTextureManager extends EventEmitter {
* Upload a texture to the GPU
*
* @param texture Texture to upload
* @returns Promise that resolves when the texture is fully loaded
*/
uploadTexture(texture: Texture): void {
async uploadTexture(texture: Texture): Promise<void> {
if (
this.stage.txMemManager.doNotExceedCriticalThreshold === true &&
this.stage.txMemManager.criticalCleanupRequested === true
Expand All @@ -427,7 +434,7 @@ export class CoreTextureManager extends EventEmitter {
return;
}

coreContext.load();
await coreContext.load();
}

/**
Expand All @@ -442,7 +449,7 @@ export class CoreTextureManager extends EventEmitter {
*
* @param maxProcessingTime - The maximum processing time in milliseconds
*/
processSome(maxProcessingTime: number): void {
async processSome(maxProcessingTime: number): Promise<void> {
if (this.initialized === false) {
return;
}
Expand All @@ -456,18 +463,28 @@ export class CoreTextureManager extends EventEmitter {
) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const texture = this.priorityQueue.pop()!;
texture.getTextureData().then(() => {
this.uploadTexture(texture);
});
try {
await texture.getTextureData();
await this.uploadTexture(texture);
} catch (error) {
console.error('Failed to process priority texture:', error);
// Continue with next texture instead of stopping entire queue
}
}

// Process uploads
// Process uploads - await each upload to prevent GPU overload
while (
this.uploadTextureQueue.length > 0 &&
getTimeStamp() - startTime < maxProcessingTime
) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.uploadTexture(this.uploadTextureQueue.shift()!);
const texture = this.uploadTextureQueue.shift()!;
try {
await this.uploadTexture(texture);
} catch (error) {
console.error('Failed to upload texture:', error);
// Continue with next texture instead of stopping entire queue
}
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/core/Stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,13 @@ export class Stage {
this.root.update(this.deltaTime, this.root.clippingRect);
}

// Process some textures
this.txManager.processSome(this.options.textureProcessingTimeLimit);
// Process some textures asynchronously but don't block the frame
// Use a background task to prevent frame drops
this.txManager
.processSome(this.options.textureProcessingTimeLimit)
.catch((err) => {
console.error('Error processing textures:', err);
});

// Reset render operations and clear the canvas
renderer.reset();
Expand Down
2 changes: 1 addition & 1 deletion src/core/renderers/CoreContextTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export abstract class CoreContextTexture {
this.memManager.setTextureMemUse(this.textureSource, byteSize);
}

abstract load(): void;
abstract load(): Promise<void>;
abstract free(): void;

get renderable(): boolean {
Expand Down
22 changes: 11 additions & 11 deletions src/core/renderers/canvas/CanvasCoreTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ export class CanvasCoreTexture extends CoreContextTexture {
}
| undefined;

load(): void {
async load(): Promise<void> {
this.textureSource.setState('loading');

this.onLoadRequest()
.then((size) => {
this.textureSource.setState('loaded', size);
this.textureSource.freeTextureData();
this.updateMemSize();
})
.catch((err) => {
this.textureSource.setState('failed', err as Error);
this.textureSource.freeTextureData();
});
try {
const size = await this.onLoadRequest();
this.textureSource.setState('loaded', size);
this.textureSource.freeTextureData();
this.updateMemSize();
} catch (err) {
this.textureSource.setState('failed', err as Error);
this.textureSource.freeTextureData();
throw err;
}
}

free(): void {
Expand Down
5 changes: 5 additions & 0 deletions src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class WebGlCoreCtxRenderTexture extends WebGlCoreCtxTexture {
const { glw } = this;
const nativeTexture = (this._nativeCtxTexture =
this.createNativeCtxTexture());

if (!nativeTexture) {
throw new Error('Failed to create native texture for RenderTexture');
}

const { width, height } = this.textureSource;

// Create Framebuffer object
Expand Down
92 changes: 52 additions & 40 deletions src/core/renderers/webgl/WebGlCoreCtxTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,54 +78,65 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
* to force the texture to be pre-loaded prior to accessing the ctxTexture
* property.
*/
load() {
// If the texture is already loading or loaded, don't load it again.
async load(): Promise<void> {
// If the texture is already loading or loaded, return resolved promise
if (this.state === 'loading' || this.state === 'loaded') {
return;
return Promise.resolve();
}

this.state = 'loading';
this.textureSource.setState('loading');

// Await the native texture creation to ensure GPU buffer is fully allocated
this._nativeCtxTexture = this.createNativeCtxTexture();

if (this._nativeCtxTexture === null) {
this.state = 'failed';
this.textureSource.setState(
'failed',
new Error('Could not create WebGL Texture'),
);
const error = new Error('Could not create WebGL Texture');
this.textureSource.setState('failed', error);
console.error('Could not create WebGL Texture');
return;
throw error;
}

this.onLoadRequest()
.then(({ width, height }) => {
// If the texture has been freed while loading, return early.
if (this.state === 'freed') {
return;
}

this.state = 'loaded';
this._w = width;
this._h = height;
// Update the texture source's width and height so that it can be used
// for rendering.
this.textureSource.setState('loaded', { width, height });

// cleanup source texture data
this.textureSource.freeTextureData();
})
.catch((err) => {
// If the texture has been freed while loading, return early.
if (this.state === 'freed') {
return;
}

this.state = 'failed';
this.textureSource.setState('failed', err);
try {
const { width, height } = await this.onLoadRequest();

// If the texture has been freed while loading, return early.
// Type assertion needed because state could change during async operations
if ((this.state as string) === 'freed') {
return;
}

this.state = 'loaded';
this._w = width;
this._h = height;
// Update the texture source's width and height so that it can be used
// for rendering.
this.textureSource.setState('loaded', { width, height });

// cleanup source texture data next tick
// This is done using queueMicrotask to ensure it runs after the current
// event loop tick, allowing the texture to be fully loaded and bound
// to the GL context before freeing the source data.
// This is important to avoid issues with the texture data being
// freed while the texture is still being loaded or used.
queueMicrotask(() => {
this.textureSource.freeTextureData();
console.error(err);
});
} catch (err: unknown) {
// If the texture has been freed while loading, return early.
// Type assertion needed because state could change during async operations
if ((this.state as string) === 'freed') {
return;
}

this.state = 'failed';
const error = err instanceof Error ? err : new Error(String(err));
this.textureSource.setState('failed', error);
this.textureSource.freeTextureData();
console.error(err);
throw error; // Re-throw to propagate the error
}
}

/**
Expand Down Expand Up @@ -268,17 +279,17 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
}

/**
* Create native context texture
* Create native context texture asynchronously
*
* @remarks
* When this method returns the returned texture will be bound to the GL context state.
* When this method resolves, the returned texture will be bound to the GL context state
* and fully ready for use. This ensures proper GPU resource allocation timing.
*
* @param width
* @param height
* @returns
* @returns Promise that resolves to the native WebGL texture or null on failure
*/
protected createNativeCtxTexture() {
protected createNativeCtxTexture(): WebGLTexture | null {
const { glw } = this;

const nativeTexture = glw.createTexture();
if (!nativeTexture) {
return null;
Expand All @@ -296,6 +307,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
// texture wrapping method
glw.texParameteri(glw.TEXTURE_WRAP_S, glw.CLAMP_TO_EDGE);
glw.texParameteri(glw.TEXTURE_WRAP_T, glw.CLAMP_TO_EDGE);

return nativeTexture;
}
}
2 changes: 1 addition & 1 deletion src/main-api/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ export class RendererMain extends EventEmitter {
quadBufferSize: settings.quadBufferSize ?? 4 * 1024 * 1024,
fontEngines: settings.fontEngines,
strictBounds: settings.strictBounds ?? true,
textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 42,
canvas: settings.canvas || document.createElement('canvas'),
createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
};
Expand Down