// what we are building is a platform and a framework.
// import { Matrix } from '../../shared/maths/Matrix';
import { uid } from '../../../../utils/data/uid';
import { TextureSource } from '../texture/sources/TextureSource';
import { Texture } from '../texture/Texture';
import type { BindableTexture } from '../texture/Texture';
/**
* Options for creating a render target.
* @memberof rendering
*/
export interface RenderTargetOptions
{
/** the width of the RenderTarget */
width?: number;
/** the height of the RenderTarget */
height?: number;
/** the resolution of the RenderTarget */
resolution?: number;
/** an array of textures, or a number indicating how many color textures there should be */
colorTextures?: BindableTexture[] | number;
/** should this render target have a stencil buffer? */
stencil?: boolean;
/** should this render target have a depth buffer? */
depth?: boolean;
/** a depth stencil texture that the depth and stencil outputs will be written to */
depthStencilTexture?: BindableTexture | boolean;
/** should this render target be antialiased? */
antialias?: boolean;
/** is this a root element, true if this is gl context owners render target */
isRoot?: boolean;
}
/**
* A class that describes what the renderers are rendering to.
* This can be as simple as a Texture, or as complex as a multi-texture, multi-sampled render target.
* Support for stencil and depth buffers is also included.
*
* If you need something more complex than a Texture to render to, you should use this class.
* Under the hood, all textures you render to have a RenderTarget created on their behalf.
* @memberof rendering
*/
export class RenderTarget
{
/** The default options for a render target */
public static defaultOptions: RenderTargetOptions = {
/** the width of the RenderTarget */
width: 0,
/** the height of the RenderTarget */
height: 0,
/** the resolution of the RenderTarget */
resolution: 1,
/** an array of textures, or a number indicating how many color textures there should be */
colorTextures: 1,
/** should this render target have a stencil buffer? */
stencil: false,
/** should this render target have a depth buffer? */
depth: false,
/** should this render target be antialiased? */
antialias: false, // save on perf by default!
/** is this a root element, true if this is gl context owners render target */
isRoot: false
};
/** unique id for this render target */
public readonly uid: number = uid('renderTarget');
/**
* An array of textures that can be written to by the GPU - mostly this has one texture in Pixi, but you could
* write to multiple if required! (eg deferred lighting)
*/
public colorTextures: TextureSource[] = [];
/** the stencil and depth buffer will right to this texture in WebGPU */
public depthStencilTexture: TextureSource;
/** if true, will ensure a stencil buffer is added. For WebGPU, this will automatically create a depthStencilTexture */
public stencil: boolean;
/** if true, will ensure a depth buffer is added. For WebGPU, this will automatically create a depthStencilTexture */
public depth: boolean;
public dirtyId = 0;
public isRoot = false;
private readonly _size = new Float32Array(2);
/** if true, then when the render target is destroyed, it will destroy all the textures that were created for it. */
private readonly _managedColorTextures: boolean = false;
/**
* @param [descriptor] - Options for creating a render target.
*/
constructor(descriptor: RenderTargetOptions = {})
{
descriptor = { ...RenderTarget.defaultOptions, ...descriptor };
this.stencil = descriptor.stencil;
this.depth = descriptor.depth;
this.isRoot = descriptor.isRoot;
if (typeof descriptor.colorTextures === 'number')
{
this._managedColorTextures = true;
for (let i = 0; i < descriptor.colorTextures; i++)
{
this.colorTextures.push(new TextureSource({
width: descriptor.width,
height: descriptor.height,
resolution: descriptor.resolution,
antialias: descriptor.antialias,
})
);
}
}
else
{
this.colorTextures = [...descriptor.colorTextures.map((texture) => texture.source)];
const colorSource = this.colorTexture.source;
this.resize(colorSource.width, colorSource.height, colorSource._resolution);
}
// the first color texture drives the size of all others..
this.colorTexture.source.on('resize', this.onSourceResize, this);
// TODO should listen for texture destroyed?
if (descriptor.depthStencilTexture || this.stencil)
{
// TODO add a test
if (descriptor.depthStencilTexture instanceof Texture
|| descriptor.depthStencilTexture instanceof TextureSource)
{
this.depthStencilTexture = descriptor.depthStencilTexture.source;
}
else
{
this.ensureDepthStencilTexture();
}
}
}
get size(): [number, number]
{
const _size = this._size;
_size[0] = this.pixelWidth;
_size[1] = this.pixelHeight;
return _size as any as [number, number];
}
get width(): number
{
return this.colorTexture.source.width;
}
get height(): number
{
return this.colorTexture.source.height;
}
get pixelWidth(): number
{
return this.colorTexture.source.pixelWidth;
}
get pixelHeight(): number
{
return this.colorTexture.source.pixelHeight;
}
get resolution(): number
{
return this.colorTexture.source._resolution;
}
get colorTexture(): TextureSource
{
return this.colorTextures[0];
}
protected onSourceResize(source: TextureSource)
{
this.resize(source.width, source.height, source._resolution, true);
}
/**
* This will ensure a depthStencil texture is created for this render target.
* Most likely called by the mask system to make sure we have stencil buffer added.
* @internal
* @ignore
*/
public ensureDepthStencilTexture()
{
if (!this.depthStencilTexture)
{
this.depthStencilTexture = new TextureSource({
width: this.width,
height: this.height,
resolution: this.resolution,
format: 'depth24plus-stencil8',
autoGenerateMipmaps: false,
antialias: false,
mipLevelCount: 1,
// sampleCount: handled by the render target system..
});
}
}
public resize(width: number, height: number, resolution = this.resolution, skipColorTexture = false)
{
this.dirtyId++;
this.colorTextures.forEach((colorTexture, i) =>
{
if (skipColorTexture && i === 0) return;
colorTexture.source.resize(width, height, resolution);
});
if (this.depthStencilTexture)
{
this.depthStencilTexture.source.resize(width, height, resolution);
}
}
public destroy()
{
this.colorTexture.source.off('resize', this.onSourceResize, this);
if (this._managedColorTextures)
{
this.colorTextures.forEach((texture) =>
{
texture.destroy();
});
}
if (this.depthStencilTexture)
{
this.depthStencilTexture.destroy();
delete this.depthStencilTexture;
}
}
}