@lightningtv/renderer
Version:
Lightning 3 Renderer
338 lines (297 loc) • 9.02 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { CoreTextureManager } from '../CoreTextureManager.js';
import type { SubTextureProps } from './SubTexture.js';
import type { Dimensions } from '../../common/CommonTypes.js';
import { EventEmitter } from '../../common/EventEmitter.js';
import type { CoreContextTexture } from '../renderers/CoreContextTexture.js';
/**
* Event handler for when a Texture is freed
*/
export type TextureFreedEventHandler = (target: any) => void;
/**
* Event handler for when a Texture is loading
*/
export type TextureLoadingEventHandler = (target: any) => void;
/**
* Event handler for when a Texture is loaded
*/
export type TextureLoadedEventHandler = (
target: any,
dimensions: Readonly<Dimensions>,
) => void;
/**
* Represents compressed texture data.
*/
interface CompressedData {
/**
* GLenum spcifying compression format
*/
glInternalFormat: number;
/**
* All mipmap levels
*/
mipmaps?: ArrayBuffer[];
/**
* Supported container types ('pvr' or 'ktx').
*/
type: 'pvr' | 'ktx';
/**
* The width of the compressed texture in pixels. Defaults to 0.
*
* @default 0
*/
width: number;
/**
* The height of the compressed texture in pixels.
**/
height: number;
}
/**
* Event handler for when a Texture fails to load
*/
export type TextureFailedEventHandler = (target: any, error: Error) => void;
/**
* TextureData that is used to populate a CoreContextTexture
*/
export interface TextureData {
/**
* The texture data
*/
data:
| ImageBitmap
| ImageData
| SubTextureProps
| CompressedData
| HTMLImageElement
| Uint8Array
| null;
/**
* Premultiply alpha when uploading texture data to the GPU
*
* @defaultValue `false`
*/
premultiplyAlpha?: boolean | null;
}
export type TextureState =
| 'initial' // Before anything is loaded
| 'fetching' // Fetching or generating texture source
| 'fetched' // Texture source is ready
| 'loading' // Uploading to GPU
| 'loaded' // Fully loaded and usable
| 'failed' // Failed to load
| 'freed'; // Released and must be reloaded
export enum TextureType {
'generic' = 0,
'color' = 1,
'image' = 2,
'noise' = 3,
'renderToTexture' = 4,
'subTexture' = 5,
}
/**
* Represents a source of texture data for a CoreContextTexture.
*
* @remarks
* Texture sources are used to populate a CoreContextTexture when that texture
* is loaded. Texture data retrieved by the CoreContextTexture by the
* `getTextureData` method. It's the responsibility of the concerete `Texture`
* subclass to implement this method appropriately.
*/
export abstract class Texture extends EventEmitter {
/**
* The dimensions of the texture
*
* @remarks
* Until the texture data is loaded for the first time the value will be
* `null`.
*/
readonly dimensions: Readonly<Dimensions> | null = null;
readonly error: Error | null = null;
// aggregate state
public state: TextureState = 'initial';
readonly renderableOwners = new Set<unknown>();
readonly renderable: boolean = false;
public type: TextureType = TextureType.generic;
public preventCleanup = false;
public ctxTexture: CoreContextTexture | undefined;
public textureData: TextureData | null = null;
constructor(protected txManager: CoreTextureManager) {
super();
}
/**
* Add/remove an owner to/from the Texture based on its renderability.
*
* @remarks
* Any object can own a texture, be it a CoreNode or even the state object
* from a Text Renderer.
*
* When the reference to the texture that an owner object holds is replaced
* or cleared it must call this with `renderable=false` to release the owner
* association.
*
* @param owner
* @param renderable
*/
setRenderableOwner(owner: unknown, renderable: boolean): void {
const oldSize = this.renderableOwners.size;
if (renderable === true) {
if (this.renderableOwners.has(owner) === false) {
// Add the owner to the set
this.renderableOwners.add(owner);
}
const newSize = this.renderableOwners.size;
if (newSize > oldSize && newSize === 1) {
(this.renderable as boolean) = true;
this.onChangeIsRenderable?.(true);
this.load();
}
} else {
this.renderableOwners.delete(owner);
const newSize = this.renderableOwners.size;
if (newSize < oldSize && newSize === 0) {
(this.renderable as boolean) = false;
this.onChangeIsRenderable?.(false);
this.txManager.orphanTexture(this);
}
}
}
load(): void {
this.txManager.loadTexture(this);
}
/**
* Event called when the Texture becomes renderable or unrenderable.
*
* @remarks
* Used by subclasses like SubTexture propogate then renderability of the
* Texture to other referenced Textures.
*
* @param isRenderable `true` if this Texture has renderable owners.
*/
onChangeIsRenderable?(isRenderable: boolean): void;
/**
* Load the core context texture for this Texture.
* The ctxTexture is created by the renderer and lives on the GPU.
*
* @returns
*/
loadCtxTexture(): CoreContextTexture {
if (this.ctxTexture === undefined) {
this.ctxTexture = this.txManager.renderer.createCtxTexture(this);
}
return this.ctxTexture;
}
/**
* Free the core context texture for this Texture.
*
* @remarks
* The ctxTexture is created by the renderer and lives on the GPU.
*/
free(): void {
this.ctxTexture?.free();
}
/**
* Free the source texture data for this Texture.
*
* @remarks
* The texture data is the source data that is used to populate the CoreContextTexture.
* e.g. ImageData that is downloaded from a URL.
*/
freeTextureData(): void {
this.textureData = null;
}
public setState(
state: TextureState,
errorOrDimensions?: Error | Dimensions,
): void {
if (this.state === state) {
return;
}
let payload: Error | Dimensions | null = null;
if (state === 'loaded') {
(this.dimensions as Dimensions) = errorOrDimensions as Dimensions;
payload = this.dimensions;
} else if (state === 'failed') {
(this.error as Error) = errorOrDimensions as Error;
payload = this.error;
}
// emit the new state
this.state = state;
this.emit(state, payload);
}
/**
* Get the texture data for this texture.
*
* @remarks
* This method is called by the CoreContextTexture when the texture is loaded.
* The texture data is then used to populate the CoreContextTexture.
*
* @returns
* The texture data for this texture.
*/
async getTextureData(): Promise<TextureData> {
if (this.textureData === null) {
this.textureData = await this.getTextureSource();
}
return this.textureData;
}
/**
* Get the texture source for this texture.
*
* @remarks
* This method is called by the CoreContextTexture when the texture is loaded.
* The texture source is then used to populate the CoreContextTexture.
*/
abstract getTextureSource(): Promise<TextureData>;
/**
* Make a cache key for this texture.
*
* @remarks
* Each concrete `Texture` subclass must implement this method to provide an
* appropriate cache key for the texture type including the texture's
* properties that uniquely identify a copy of the texture. If the texture
* type does not support caching, then this method should return `false`.
*
* @param props
* @returns
* A cache key for this texture or `false` if the texture type does not
* support caching.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static makeCacheKey(props: unknown): string | false {
return false;
}
/**
* Resolve the default values for the texture's properties.
*
* @remarks
* Each concrete `Texture` subclass must implement this method to provide
* default values for the texture's optional properties.
*
* @param props
* @returns
* The default values for the texture's properties.
*/
static resolveDefaults(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props: unknown,
): Record<string, any> {
return {};
}
}