/** * A render target is a rectangular rendering surface that can be rendered into, instead of the * screen. It wraps one or more color buffer {@link Texture}s and an optional depth (and stencil) * buffer. Once a camera or a render pass has rendered into it, the color texture holds the result * and can be used anywhere a normal texture can - applied to a material to display it in the * scene, or fed into further processing. This underpins effects such as in-world screens, mirrors * and portals, reflections, picking and custom multi-pass pipelines. * * ## Usage * Create a texture to render into, wrap it in a render target and assign it to a camera. The * texture must use a renderable, uncompressed format: * * ```javascript * const texture = new Texture(device, { * width: 512, * height: 512, * format: PIXELFORMAT_SRGBA8, * mipmaps: false, * minFilter: FILTER_LINEAR, * magFilter: FILTER_LINEAR * }); * * const renderTarget = new RenderTarget({ * colorBuffer: texture, * depth: true, * origin: RENDERTARGET_ORIGIN_TOP * }); * * // the camera renders into the texture instead of the screen * cameraEntity.camera.renderTarget = renderTarget; * * // and the texture can be used as any other, for example by a material * material.emissiveMap = texture; * ``` * * When the result is sampled as a regular texture like this, specify the `origin` option as * {@link RENDERTARGET_ORIGIN_TOP}, which stores the image in the same orientation on all graphics * APIs. Multiple color buffers can be attached using the `colorBuffers` option, to render into * all of them simultaneously from a single pass (MRT). * * A live example: {@link https://playcanvas.github.io/#/graphics/render-to-texture} * * ## Multisampling (MSAA) * Set the `samples` option to a value greater than 1 to render with hardware anti-aliasing. The * render target internally allocates a multisampled buffer to render into, and automatically * resolves it into the single-sampled `colorBuffer` at the end of a render pass - the color * texture is used the same way as in the single-sampled case. * * ```javascript * const renderTarget = new RenderTarget({ colorBuffer: texture, depth: true, samples: 4 }); * ``` * * ## Explicit multisampled color buffers and custom resolves (WebGPU) * A multisampled texture (a {@link Texture} created with `samples` greater than 1, WebGPU only) * can be used as the color buffer directly. The render target then renders into its samples, and * the sample count is inferred from the texture. Provide a `resolveBuffer` to get the standard * hardware resolve, or omit it to keep the individual samples: these are then read in a shader * using `textureLoad` on a `texture_multisampled_2d`, typically by a follow-up pass implementing * a custom resolve - an operation the hardware resolve cannot express, such as a tonemapped or * min/max resolve. This is also the only way to use multisampling with formats the hardware * cannot resolve, such as integer formats. * * ```javascript * // a multisampled texture, rendered into directly * const msColor = new Texture(device, { * width: 512, * height: 512, * format: PIXELFORMAT_RGBA16F, * samples: 4 * }); * * // renders into the samples of msColor, which are stored (no resolve buffer), * // to be read by a custom resolve pass using textureLoad * const renderTarget = new RenderTarget({ colorBuffer: msColor, depth: true }); * ``` * * A live example: {@link https://playcanvas.github.io/#/graphics-advanced/custom-msaa-resolve} * * @category Graphics */ export class RenderTarget { /** * Creates a new RenderTarget instance. A color buffer or a depth buffer must be set. * * @param {object} [options] - Object for passing optional arguments. * @param {boolean} [options.autoResolve] - If samples > 1, enables or disables automatic MSAA * resolve after rendering to this RT (see {@link resolve}). Applies to the implicit * multisampled path only - resolves of explicit multisampled attachments (a multisampled * `colorBuffer` with a `resolveBuffer`, or a multisampled `depthBuffer` with a * `depthResolveBuffer`) are controlled by the per-pass resolve flags instead. Defaults to * true. * @param {string} [options.depthResolveMode] - How the samples of the multisampled depth * buffer are resolved into a single depth value, whenever the depth of this render target is * resolved by a shader-based resolve (WebGPU only) - the depth grab pass (`sceneDepthMap`), a * depth {@link copy}, or the automatic resolve into a provided `depthBuffer`. Can be: * * - {@link DEPTHRESOLVE_MIN}: the minimum sample value - with a standard depth buffer this * selects the nearest surface, a conservative and stable choice for depth-consuming effects. * - {@link DEPTHRESOLVE_MAX}: the maximum sample value - the farthest surface. * - {@link DEPTHRESOLVE_SAMPLE0}: the value of the sample at index 0. * * Defaults to {@link DEPTHRESOLVE_MIN}. Ignored on WebGL2, where the sample selection of the * depth resolve is defined by the implementation. Can also be changed at any time using the * {@link RenderTarget#depthResolveMode} property. * @param {Texture} [options.colorBuffer] - The texture that this render target will treat as a * rendering surface. This can be a multisampled texture (a texture created with `samples` > 1, * WebGPU only), in which case the render target renders directly into its samples, the sample * count is inferred from the texture, and an optional `resolveBuffer` receives the hardware * resolve. * @param {Texture[]} [options.colorBuffers] - The textures that this render target will treat * as a rendering surfaces. If this option is set, the colorBuffer option is ignored. All * textures must have the same sample count. * @param {Texture|null} [options.resolveBuffer] - A single-sampled texture that the * multisampled color buffer is hardware-resolved into at the end of a render pass. Only valid * when `colorBuffer` is a multisampled texture, and must match its format and dimensions. When * not provided, the multisampled samples are stored instead, to be read in a shader using * `textureLoad` (a custom resolve). Note that integer formats and {@link PIXELFORMAT_R32F} * cannot be hardware-resolved. * @param {(Texture|null)[]} [options.resolveBuffers] - Per-attachment resolve textures * matching `colorBuffers` by index; use null for attachments that should not be * hardware-resolved. If this option is set, the resolveBuffer option must not be used. * @param {boolean} [options.depth] - If set to true, depth buffer will be created. Defaults to * true. Ignored if depthBuffer is defined. * @param {Texture} [options.depthBuffer] - The texture that this render target will treat as a * depth/stencil surface. If set, the 'depth' and 'stencil' properties are ignored. The texture * must use {@link PIXELFORMAT_DEPTH}, {@link PIXELFORMAT_DEPTH16} or * {@link PIXELFORMAT_DEPTHSTENCIL} format. On WebGPU this can be a multisampled texture (a * texture created with `samples` > 1), in which case the render target renders directly into * its depth samples, which can later be read in a shader using `textureLoad` on a * `texture_depth_multisampled_2d`, or resolved into an optional `depthResolveBuffer`. * @param {Texture} [options.depthResolveBuffer] - A single-sampled {@link PIXELFORMAT_R32F} * texture that the multisampled depth buffer is resolved into at the end of a render pass, * using a shader-based resolve controlled by {@link RenderTarget#depthResolveMode} (WebGPU * only - no hardware depth resolve exists). Only valid when `depthBuffer` is a multisampled * texture, and must match its dimensions. * @param {number} [options.mipLevel] - If set to a number greater than 0, the render target * will render to the specified mip level of the color buffer. Defaults to 0. * @param {number} [options.face] - If the colorBuffer parameter is a cubemap, use this option * to specify the face of the cubemap to render to. Can be: * * - {@link CUBEFACE_POSX} * - {@link CUBEFACE_NEGX} * - {@link CUBEFACE_POSY} * - {@link CUBEFACE_NEGY} * - {@link CUBEFACE_POSZ} * - {@link CUBEFACE_NEGZ} * * Defaults to {@link CUBEFACE_POSX}. * @param {string} [options.name] - The name of the render target. * @param {string} [options.origin] - Controls the vertical orientation of the image stored * in the render target. Choose based on how the texture is sampled. Can be: * * - {@link RENDERTARGET_ORIGIN_TOP}: row 0 of the stored image is the top row of the * rendered image, on all graphics APIs - the same layout image textures use. Use for * anything treated as a picture: sampling with mesh UVs, cube map faces, or pixel readback * saved as an image. Recommended for all new content - write the sampling code as if the * texture was a loaded image. Internally the image is rendered upside-down on WebGL2. * - {@link RENDERTARGET_ORIGIN_BOTTOM}: row 0 of the stored image is the bottom row of the * rendered image, on all graphics APIs - replicating WebGL2's native layout. Use to keep * consuming code written against WebGL conventions working unchanged on all APIs: shaders * deriving UVs from projected (NDC) coordinates or a projection scale-bias matrix, and * texture atlases addressing cells by viewport rectangles (on WebGPU this also switches * viewport / scissor rectangles to raw texel-row addressing). If a render target that worked * on WebGL2 appears upside-down on WebGPU, this is the drop-in fix; migrating the sampling * code to image orientation and {@link RENDERTARGET_ORIGIN_TOP} is the better long-term * choice. Internally the image is rendered upside-down on WebGPU. * - {@link RENDERTARGET_ORIGIN_NATIVE}: the image is stored in the native orientation of the * graphics API and the row order differs between WebGL2 (bottom-up) and WebGPU (top-down). * No flipping takes place. Only appropriate for orientation-agnostic consumers: UVs derived * from gl_FragCoord, sampling via the same matrix the target was rendered with (shadow * maps), or integer texel fetch. * * Takes precedence over the deprecated `flipY` option. Defaults to * {@link RENDERTARGET_ORIGIN_NATIVE}. * @param {number} [options.samples] - Number of hardware anti-aliasing samples. Default is 1. * @param {boolean} [options.stencil] - If set to true, depth buffer will include stencil. * Defaults to false. Ignored if depthBuffer is defined or depth is false. * @param {boolean} [options.transientColor] - If set to true, the multi-sampled (MSAA) color * attachment is allocated as a transient ("memoryless") attachment, allowing tile-based GPUs to * keep its contents in on-chip memory and avoid VRAM allocation. WebGPU only, and only effective * when samples > 1 - it has no effect on single-sampled color (which is always stored). Ignored * on devices without transient attachment support. The attachment must be cleared on load and * discarded on store, so it is incompatible with a scene color grab pass (`sceneColorMap`). * Defaults to false. * @param {boolean} [options.transientDepth] - If set to true, the (engine-allocated) depth * attachment is allocated as a transient ("memoryless") attachment (see `transientColor`). * Applies to both single- and multi-sampled depth. WebGPU only; ignored on devices without * transient attachment support, and ignored (with a warning) when an explicit `depthBuffer` is * provided. Incompatible with a scene depth grab pass (`sceneDepthMap`), a depth prepass, or any * depth resolve, as the depth cannot be sampled or copied out. Defaults to false. * @example * // Create a 512x512x24-bit render target with a depth buffer * const colorBuffer = new Texture(graphicsDevice, { * width: 512, * height: 512, * format: PIXELFORMAT_RGB8 * }); * const renderTarget = new RenderTarget({ * colorBuffer: colorBuffer, * depth: true * }); * * // Set the render target on a camera component * camera.renderTarget = renderTarget; * * // Destroy render target at a later stage. Note that the color buffer needs * // to be destroyed separately. * renderTarget.colorBuffer.destroy(); * renderTarget.destroy(); * camera.renderTarget = null; */ constructor(options?: { autoResolve?: boolean; depthResolveMode?: string; colorBuffer?: Texture; colorBuffers?: Texture[]; resolveBuffer?: Texture | null; resolveBuffers?: (Texture | null)[]; depth?: boolean; depthBuffer?: Texture; depthResolveBuffer?: Texture; mipLevel?: number; face?: number; name?: string; origin?: string; samples?: number; stencil?: boolean; transientColor?: boolean; transientDepth?: boolean; }); /** * The name of the render target. * * @type {string} */ name: string; /** * @type {GraphicsDevice} * @private */ private _device; /** * @type {Texture} * @private */ private _colorBuffer; /** * @type {Texture[]} * @private */ private _colorBuffers; /** * @type {Texture} * @private */ private _depthBuffer; /** * Per-attachment resolve targets for explicit multisampled color attachments. * * @type {(Texture|null)[]|null} * @private */ private _resolveBuffers; /** * Single-sampled resolve target for an explicit multisampled depth buffer. * * @type {Texture|null} * @private */ private _depthResolveBuffer; /** * @type {boolean} * @private */ private _depth; /** * @type {boolean} * @private */ private _stencil; /** * @type {number} * @private */ private _samples; /** * @type {boolean} * @private */ private _transientColor; /** * @type {boolean} * @private */ private _transientDepth; /** @type {boolean} */ autoResolve: boolean; /** * @type {string} * @private */ private _depthResolveMode; /** * @type {number} * @private */ private _face; /** * @type {number} * @private */ private _mipLevel; /** * True if the mipmaps should be automatically generated for the color buffer(s) if it contains * a mip chain. * * @type {boolean} * @private */ private _mipmaps; /** * @type {number | undefined} * @private */ private _width; /** * @type {number | undefined} * @private */ private _height; /** * @type {boolean} * @private */ private _flipY; /** * @type {string} * @private */ private _origin; id: number; /** * Sets how the samples of the multisampled depth buffer are resolved into a single depth * value (WebGPU only). Can be changed at any time - the mode is used at the time the depth is * resolved. See the `depthResolveMode` constructor option. * * @type {string} */ set depthResolveMode(value: string); /** * Gets how the samples of the multisampled depth buffer are resolved into a single depth * value. * * @type {string} */ get depthResolveMode(): string; impl: any; /** * Frees resources associated with this render target. */ destroy(): void; /** * Free device resources associated with this render target. * * @ignore */ destroyFrameBuffers(): void; /** * Free textures associated with this render target. * * @ignore */ destroyTextureBuffers(): void; /** * Resizes the render target to the specified width and height. Internally this resizes all the * assigned texture color and depth buffers. * * @param {number} width - The width of the render target in pixels. * @param {number} height - The height of the render target in pixels. */ resize(width: number, height: number): void; validateMrt(): void; /** * Evaluates and stores the width and height of the render target based on the color/depth * buffers and mip level. * * @private */ private evaluateDimensions; /** * Initializes the resources associated with this render target. * * @ignore */ init(): void; /** @ignore */ get initialized(): any; /** @ignore */ get device(): GraphicsDevice; /** * Called when the device context was lost. It releases all context related resources. * * @ignore */ loseContext(): void; /** * If samples > 1, resolves the anti-aliased render target (WebGL2 only). When you're rendering * to an anti-aliased render target, pixels aren't written directly to the readable texture. * Instead, they're first written to an MSAA buffer, where each sample for each pixel is stored * independently. In order to read the results, you first need to 'resolve' the buffer - to * average all samples and create a simple texture with one color per pixel. This function * performs this averaging and updates the colorBuffer and the depthBuffer. If autoResolve is * set to true, the resolve will happen after every rendering to this render target, otherwise * you can do it manually, during the app update or similar. * * @param {boolean} [color] - Resolve color buffer. Defaults to true. * @param {boolean} [depth] - Resolve depth buffer. Defaults to true if the render target has a * depth buffer. */ resolve(color?: boolean, depth?: boolean): void; /** * Copies color and/or depth contents of source render target to this one. Formats, sizes and * anti-aliasing samples must match. * * A depth copy is supported in these cases: * * - On WebGL 2.0, between render targets with matching sample counts. * - On WebGPU, between single-sampled render targets. * - On WebGPU, from a multisampled source into a multisampled `depthBuffer` of this render * target with an equal sample count and matching format - a full depth snapshot, including * the individual samples. * - On WebGPU, from a multisampled source into a single-sampled {@link PIXELFORMAT_R32F} * color buffer of this render target - a shader-based resolve controlled by the source's * {@link RenderTarget#depthResolveMode}. * * @param {RenderTarget} source - Source render target to copy from. * @param {boolean} [color] - If true, will copy the color buffer. Defaults to false. * @param {boolean} [depth] - If true, will copy the depth buffer. Defaults to false. * @returns {boolean} True if the copy was successful, false otherwise. */ copy(source: RenderTarget, color?: boolean, depth?: boolean): boolean; /** * @deprecated Use the "origin" option of the RenderTarget constructor instead. Typical * migration: flipY: !device.isWebGPU -> origin: RENDERTARGET_ORIGIN_TOP, flipY: device.isWebGPU * -> origin: RENDERTARGET_ORIGIN_BOTTOM. * @ignore */ set flipY(value: boolean); /** * Gets whether the rendered image is flipped in Y. * * @type {boolean} * @ignore */ get flipY(): boolean; /** * Gets the vertical orientation of the image stored in this render target, as resolved at * construction from the `origin` option, or derived from the deprecated flipY option or * property. Can be {@link RENDERTARGET_ORIGIN_TOP}, {@link RENDERTARGET_ORIGIN_BOTTOM} or * {@link RENDERTARGET_ORIGIN_NATIVE}. See the `origin` option of the constructor for * details. * * @type {string} */ get origin(): string; /** * Number of antialiasing samples the render target uses. * * @type {number} */ get samples(): number; /** * True if the multi-sampled color attachment is allocated as a transient ("memoryless") * attachment (WebGPU only). See the `transientColor` constructor option. * * @type {boolean} */ get transientColor(): boolean; /** * True if the depth attachment is allocated as a transient ("memoryless") attachment (WebGPU * only). See the `transientDepth` constructor option. * * @type {boolean} */ get transientDepth(): boolean; /** * True if the render target contains the depth attachment. * * @type {boolean} */ get depth(): boolean; /** * True if the render target contains the stencil attachment. * * @type {boolean} */ get stencil(): boolean; /** * Color buffer set up on the render target. * * @type {Texture} */ get colorBuffer(): Texture; /** * The number of color buffers (attachments) set up on the render target. * * @type {number} */ get colorBufferCount(): number; /** * Accessor for multiple render target color buffers. * * @param {number} index - Index of the color buffer to get. * @returns {Texture} - Color buffer at the specified index. */ getColorBuffer(index: number): Texture; /** * The resolve texture of the first color attachment, when the render target uses explicit * multisampled color buffers and a resolve buffer was provided. See the `resolveBuffer` * constructor option. Null otherwise. * * @type {Texture|null} */ get resolveBuffer(): Texture | null; /** * Accessor for the per-attachment resolve textures. See the `resolveBuffers` constructor * option. * * @param {number} [index] - Index of the color attachment. Defaults to 0. * @returns {Texture|null} - The resolve texture at the specified index, or null when the * attachment has none. */ getResolveBuffer(index?: number): Texture | null; /** * Depth buffer set up on the render target. Only available, if depthBuffer was set in * constructor. Not available if depth property was used instead. * * @type {Texture} */ get depthBuffer(): Texture; /** * The single-sampled texture the multisampled depth buffer is resolved into at the end of a * render pass. See the `depthResolveBuffer` constructor option. Null when not provided. * * @type {Texture|null} */ get depthResolveBuffer(): Texture | null; /** * If the render target is bound to a cubemap, this property specifies which face of the * cubemap is rendered to. Can be: * * - {@link CUBEFACE_POSX} * - {@link CUBEFACE_NEGX} * - {@link CUBEFACE_POSY} * - {@link CUBEFACE_NEGY} * - {@link CUBEFACE_POSZ} * - {@link CUBEFACE_NEGZ} * * @type {number} */ get face(): number; /** * Mip level of the render target. * * @type {number} */ get mipLevel(): number; /** * True if the mipmaps are automatically generated for the color buffer(s) if it contains * a mip chain. * * @type {boolean} */ get mipmaps(): boolean; /** * Width of the render target in pixels. * * @type {number} */ get width(): number; /** * Height of the render target in pixels. * * @type {number} */ get height(): number; set _glFrameBuffer(value: any); get _glFrameBuffer(): any; /** * Gets whether the format of the specified color buffer is sRGB. * * @param {number} index - The index of the color buffer. * @returns {boolean} True if the color buffer is sRGB, false otherwise. * @ignore */ isColorBufferSrgb(index?: number): boolean; } import { GraphicsDevice } from './graphics-device.js'; import type { Texture } from './texture.js';