playcanvas
Version:
PlayCanvas WebGL game engine
378 lines (375 loc) • 16.7 kB
JavaScript
import { Debug } from '../../core/debug.js';
import { Tracing } from '../../core/tracing.js';
import { Color } from '../../core/math/color.js';
import { TRACEID_RENDER_PASS, TRACEID_RENDER_PASS_DETAIL } from '../../core/constants.js';
import { isIntegerPixelFormat, pixelFormatInfo } from './constants.js';
/**
* @import { GraphicsDevice } from '../graphics/graphics-device.js'
* @import { RenderTarget } from '../graphics/render-target.js'
* @import { Texture } from './texture.js'
*/ class ColorAttachmentOps {
constructor(){
/**
* A color used to clear the color attachment when the clear is enabled, specified in sRGB space.
*/ this.clearValue = new Color(0, 0, 0, 1);
/**
* A color used to clear the color attachment when the clear is enabled, specified in linear
* space.
*/ this.clearValueLinear = new Color(0, 0, 0, 1);
/**
* True if the attachment should be cleared before rendering, false to preserve
* the existing content.
*/ this.clear = false;
/**
* True if the attachment needs to be stored after the render pass. False if it can be
* discarded. Note: This relates to the surface that is getting rendered to, and can be either
* single or multi-sampled. Further, if a multi-sampled surface is used, the resolve flag
* further specifies if this gets resolved to a single-sampled surface. This behavior matches
* the WebGPU specification.
*
* @type {boolean}
*/ this.store = false;
/**
* True if the attachment needs to be resolved.
*
* @type {boolean}
*/ this.resolve = true;
/**
* True if the attachment needs to have mipmaps generated.
*
* @type {boolean}
*/ this.genMipmaps = false;
}
}
class DepthStencilAttachmentOps {
constructor(){
/**
* A depth value used to clear the depth attachment when the clear is enabled.
*/ this.clearDepthValue = 1;
/**
* A stencil value used to clear the stencil attachment when the clear is enabled.
*/ this.clearStencilValue = 0;
/**
* True if the depth attachment should be cleared before rendering, false to preserve
* the existing content.
*/ this.clearDepth = false;
/**
* True if the stencil attachment should be cleared before rendering, false to preserve
* the existing content.
*/ this.clearStencil = false;
/**
* True if the depth attachment needs to be stored after the render pass. False
* if it can be discarded.
*
* @type {boolean}
*/ this.storeDepth = false;
/**
* True if the depth attachment needs to be resolved.
*
* @type {boolean}
*/ this.resolveDepth = false;
/**
* True if the stencil attachment needs to be stored after the render pass. False
* if it can be discarded.
*
* @type {boolean}
*/ this.storeStencil = false;
}
}
/**
* A render pass represents a node in the frame graph, and encapsulates a system which
* renders to a render target using an execution callback.
*
* @ignore
*/ class RenderPass {
/**
* Color attachment operations for the first color attachment.
*
* @type {ColorAttachmentOps}
*/ get colorOps() {
return this.colorArrayOps[0];
}
set name(value) {
this._name = value;
}
get name() {
if (!this._name) {
this._name = this.constructor.name;
}
return this._name;
}
set scaleX(value) {
Debug.assert(this._options, 'The render pass needs to be initialized first.');
this._options.scaleX = value;
}
get scaleX() {
return this._options.scaleX;
}
set scaleY(value) {
Debug.assert(this._options, 'The render pass needs to be initialized first.');
this._options.scaleY = value;
}
get scaleY() {
return this._options.scaleY;
}
set options(value) {
this._options = value;
// sanitize options
if (value) {
var _this_scaleX;
this.scaleX = (_this_scaleX = this.scaleX) != null ? _this_scaleX : 1;
var _this_scaleY;
this.scaleY = (_this_scaleY = this.scaleY) != null ? _this_scaleY : 1;
}
}
get options() {
return this._options;
}
/**
* @param {RenderTarget|null} [renderTarget] - The render target to render into (output). This
* function should be called only for render passes which use render target, or passes which
* render directly into the default framebuffer, in which case a null or undefined render
* target is expected.
* @param {object} [options] - Object for passing optional arguments.
* @param {Texture} [options.resizeSource] - A texture to use as a source for the automatic
* render target resize operation. If not provided, no automatic resizing takes place.
* @param {number} [options.scaleX] - The scale factor for the render target width. Defaults to 1.
* @param {number} [options.scaleY] - The scale factor for the render target height. Defaults to 1.
*/ init(renderTarget, options) {
if (renderTarget === void 0) renderTarget = null;
this.options = options;
// null represents the default framebuffer
this.renderTarget = renderTarget;
// defaults depend on multisampling
this.samples = Math.max(this.renderTarget ? this.renderTarget.samples : this.device.samples, 1);
// allocate ops only when render target is used (when this function was called)
this.allocateAttachments();
// allow for post-init setup
this.postInit();
}
allocateAttachments() {
var _rt__colorBuffers;
var rt = this.renderTarget;
// depth
this.depthStencilOps = new DepthStencilAttachmentOps();
// if a RT is used (so not a backbuffer) that was created with a user supplied depth buffer,
// assume the user wants to use its content, and so store it by default
if (rt == null ? void 0 : rt.depthBuffer) {
this.depthStencilOps.storeDepth = true;
}
var _rt__colorBuffers_length;
// color
var numColorOps = rt ? (_rt__colorBuffers_length = (_rt__colorBuffers = rt._colorBuffers) == null ? void 0 : _rt__colorBuffers.length) != null ? _rt__colorBuffers_length : 0 : 1;
this.colorArrayOps.length = 0;
for(var i = 0; i < numColorOps; i++){
var _this_renderTarget__colorBuffers, _this_renderTarget, _this_renderTarget1;
var colorOps = new ColorAttachmentOps();
this.colorArrayOps[i] = colorOps;
// if rendering to single-sampled buffer, this buffer needs to be stored
if (this.samples === 1) {
colorOps.store = true;
colorOps.resolve = false;
}
// if render target needs mipmaps
var colorBuffer = (_this_renderTarget = this.renderTarget) == null ? void 0 : (_this_renderTarget__colorBuffers = _this_renderTarget._colorBuffers) == null ? void 0 : _this_renderTarget__colorBuffers[i];
if (((_this_renderTarget1 = this.renderTarget) == null ? void 0 : _this_renderTarget1.mipmaps) && (colorBuffer == null ? void 0 : colorBuffer.mipmaps)) {
var intFormat = isIntegerPixelFormat(colorBuffer._format);
colorOps.genMipmaps = !intFormat; // no automatic mipmap generation for integer formats
}
}
}
destroy() {}
postInit() {}
frameUpdate() {
// resize the render target if needed
if (this._options && this.renderTarget) {
var _this__options_resizeSource;
var resizeSource = (_this__options_resizeSource = this._options.resizeSource) != null ? _this__options_resizeSource : this.device.backBuffer;
var width = Math.floor(resizeSource.width * this.scaleX);
var height = Math.floor(resizeSource.height * this.scaleY);
this.renderTarget.resize(width, height);
}
}
before() {}
execute() {}
after() {}
onEnable() {}
onDisable() {}
set enabled(value) {
if (this._enabled !== value) {
this._enabled = value;
if (value) {
this.onEnable();
} else {
this.onDisable();
}
}
}
get enabled() {
return this._enabled;
}
/**
* Mark render pass as clearing the full color buffer.
*
* @param {Color|undefined} color - The color to clear to, or undefined to preserve the existing
* content.
*/ setClearColor(color) {
// in case of MRT, we clear all color buffers.
// TODO: expose per color buffer clear parameters on the camera, and copy them here.
var count = this.colorArrayOps.length;
for(var i = 0; i < count; i++){
var colorOps = this.colorArrayOps[i];
if (color) {
colorOps.clearValue.copy(color);
colorOps.clearValueLinear.linear(color);
}
colorOps.clear = !!color;
}
}
/**
* Mark render pass as clearing the full depth buffer.
*
* @param {number|undefined} depthValue - The depth value to clear to, or undefined to preserve
* the existing content.
*/ setClearDepth(depthValue) {
if (depthValue) {
this.depthStencilOps.clearDepthValue = depthValue;
}
this.depthStencilOps.clearDepth = depthValue !== undefined;
}
/**
* Mark render pass as clearing the full stencil buffer.
*
* @param {number|undefined} stencilValue - The stencil value to clear to, or undefined to
* preserve the existing content.
*/ setClearStencil(stencilValue) {
if (stencilValue) {
this.depthStencilOps.clearStencilValue = stencilValue;
}
this.depthStencilOps.clearStencil = stencilValue !== undefined;
}
/**
* Render the render pass
*/ render() {
if (this.enabled) {
var device = this.device;
var realPass = this.renderTarget !== undefined;
Debug.call(()=>{
this.log(device, device.renderPassIndex);
});
this.before();
if (this.executeEnabled) {
if (realPass && !this._skipStart) {
device.startRenderPass(this);
}
this.execute();
if (realPass && !this._skipEnd) {
device.endRenderPass(this);
}
}
this.after();
device.renderPassIndex++;
}
}
log(device, index) {
if (index === void 0) index = 0;
if (Tracing.get(TRACEID_RENDER_PASS) || Tracing.get(TRACEID_RENDER_PASS_DETAIL)) {
var _rt__colorBuffers;
var _this_renderTarget;
var rt = (_this_renderTarget = this.renderTarget) != null ? _this_renderTarget : this.renderTarget === null ? device.backBuffer : null;
var isBackBuffer = !!(rt == null ? void 0 : rt.impl.assignedColorTexture) || (rt == null ? void 0 : rt.impl.suppliedColorFramebuffer) !== undefined;
var _rt__colorBuffers_length;
var numColor = (_rt__colorBuffers_length = rt == null ? void 0 : (_rt__colorBuffers = rt._colorBuffers) == null ? void 0 : _rt__colorBuffers.length) != null ? _rt__colorBuffers_length : isBackBuffer ? 1 : 0;
var hasDepth = rt == null ? void 0 : rt.depth;
var hasStencil = rt == null ? void 0 : rt.stencil;
var mipLevel = rt == null ? void 0 : rt.mipLevel;
var rtInfo = !rt ? '' : " RT: " + (rt ? rt.name : 'NULL') + " " + ("" + (numColor > 0 ? "[Color" + (numColor > 1 ? " x " + numColor : '') + "]" : '')) + ("" + (hasDepth ? '[Depth]' : '')) + ("" + (hasStencil ? '[Stencil]' : '')) + (" " + rt.width + " x " + rt.height) + ("" + (this.samples > 0 ? " samples: " + this.samples : '')) + ("" + (mipLevel > 0 ? " mipLevel: " + mipLevel : ''));
var indexString = this._skipStart ? '++' : index.toString().padEnd(2, ' ');
Debug.trace(TRACEID_RENDER_PASS, indexString + ": " + this.name.padEnd(20, ' ') + ("" + (this.executeEnabled ? '' : ' DISABLED ') + rtInfo.padEnd(30)));
for(var i = 0; i < numColor; i++){
var _pixelFormatInfo_get;
var colorOps = this.colorArrayOps[i];
var colorFormat = (_pixelFormatInfo_get = pixelFormatInfo.get(isBackBuffer ? device.backBufferFormat : rt.getColorBuffer(i).format)) == null ? void 0 : _pixelFormatInfo_get.name;
Debug.trace(TRACEID_RENDER_PASS_DETAIL, " color[" + i + "]: " + ("" + (colorOps.clear ? 'clear' : 'load') + "->") + ("" + (colorOps.store ? 'store' : 'discard') + " ") + ("" + (colorOps.resolve ? 'resolve ' : '')) + ("" + (colorOps.genMipmaps ? 'mipmaps ' : '')) + (" [format: " + colorFormat + "]") + (" " + (colorOps.clear ? "[clear: " + colorOps.clearValue.toString(true, true) + "]" : '')));
}
if (this.depthStencilOps) {
var _pixelFormatInfo_get1;
var depthFormat = "" + (rt.depthBuffer ? " [format: " + ((_pixelFormatInfo_get1 = pixelFormatInfo.get(rt.depthBuffer.format)) == null ? void 0 : _pixelFormatInfo_get1.name) + "]" : '');
if (hasDepth) {
Debug.trace(TRACEID_RENDER_PASS_DETAIL, ' depthOps: ' + ("" + (this.depthStencilOps.clearDepth ? 'clear' : 'load') + "->") + ("" + (this.depthStencilOps.storeDepth ? 'store' : 'discard')) + ("" + (this.depthStencilOps.resolveDepth ? ' resolve' : '')) + ("" + depthFormat) + ("" + (this.depthStencilOps.clearDepth ? " [clear: " + this.depthStencilOps.clearDepthValue + "]" : '')));
}
if (hasStencil) {
Debug.trace(TRACEID_RENDER_PASS_DETAIL, ' stencOps: ' + ("" + (this.depthStencilOps.clearStencil ? 'clear' : 'load') + "->") + ("" + (this.depthStencilOps.storeStencil ? 'store' : 'discard')) + ("" + depthFormat) + ("" + (this.depthStencilOps.clearStencil ? " [clear: " + this.depthStencilOps.clearStencilValue + "]" : '')));
}
}
}
}
/**
* Creates an instance of the RenderPass.
*
* @param {GraphicsDevice} graphicsDevice - The
* graphics device.
*/ constructor(graphicsDevice){
/**
* True if the render pass is enabled.
*
* @type {boolean}
* @private
*/ this._enabled = true;
/**
* True if the render pass start is skipped. This means the render pass is merged into the
* previous one.
*
* @type {boolean}
* @private
*/ this._skipStart = false;
/**
* True if the render pass end is skipped. This means the following render pass is merged into
* this one.
*
* @type {boolean}
* @private
*/ this._skipEnd = false;
/**
* True if the render pass is enabled and execute function will be called. Note that before and
* after functions are called regardless of this flag.
*/ this.executeEnabled = true;
/**
* Number of samples. 0 if no render target, otherwise number of samples from the render target,
* or the main framebuffer if render target is null.
*
* @type {number}
*/ this.samples = 0;
/**
* Array of color attachment operations. The first element corresponds to the color attachment
* 0, and so on.
*
* @type {Array<ColorAttachmentOps>}
*/ this.colorArrayOps = [];
/**
* If true, this pass might use dynamically rendered cubemaps. Use for a case where rendering to cubemap
* faces is interleaved with rendering to shadows, to avoid generating cubemap mipmaps. This will likely
* be retired when render target dependency tracking gets implemented.
*
* @type {boolean}
*/ this.requiresCubemaps = true;
/**
* True if the render pass uses the full viewport / scissor for rendering into the render target.
*
* @type {boolean}
*/ this.fullSizeClearRect = true;
/**
* Render passes which need to be executed before this pass.
*
* @type {RenderPass[]}
*/ this.beforePasses = [];
/**
* Render passes which need to be executed after this pass.
*
* @type {RenderPass[]}
*/ this.afterPasses = [];
Debug.assert(graphicsDevice);
this.device = graphicsDevice;
}
}
export { RenderPass };