UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

378 lines (375 loc) 16.7 kB
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 };