UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

312 lines (311 loc) 12.3 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 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 { FramePass } from "./frame-pass.js"; class ColorAttachmentOps { constructor() { /** * A color used to clear the color attachment when the clear is enabled, specified in sRGB space. */ __publicField(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. */ __publicField(this, "clearValueLinear", new Color(0, 0, 0, 1)); /** * True if the attachment should be cleared before rendering, false to preserve * the existing content. */ __publicField(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. */ __publicField(this, "store", false); /** * True if the attachment needs to be resolved. */ __publicField(this, "resolve", true); /** * True if the attachment needs to have mipmaps generated. */ __publicField(this, "genMipmaps", false); } } class DepthStencilAttachmentOps { constructor() { /** * A depth value used to clear the depth attachment when the clear is enabled. */ __publicField(this, "clearDepthValue", 1); /** * A stencil value used to clear the stencil attachment when the clear is enabled. */ __publicField(this, "clearStencilValue", 0); /** * True if the depth attachment should be cleared before rendering, false to preserve * the existing content. */ __publicField(this, "clearDepth", false); /** * True if the stencil attachment should be cleared before rendering, false to preserve * the existing content. */ __publicField(this, "clearStencil", false); /** * True if the depth attachment needs to be stored after the render pass. False * if it can be discarded. */ __publicField(this, "storeDepth", false); /** * True if the depth attachment needs to be resolved. */ __publicField(this, "resolveDepth", false); /** * True if the stencil attachment needs to be stored after the render pass. False * if it can be discarded. */ __publicField(this, "storeStencil", false); } } class RenderPass extends FramePass { constructor() { super(...arguments); /** * The render target for this render pass: * * - `undefined`: render pass does not render to any render target * - `null`: render pass renders to the backbuffer * - Otherwise, renders to the provided RT. * * @type {RenderTarget|null|undefined} */ __publicField(this, "renderTarget"); /** * The options specified when the render target was initialized. */ __publicField(this, "_options"); /** * 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. */ __publicField(this, "samples", 0); /** * Array of color attachment operations. The first element corresponds to the color attachment * 0, and so on. * * @type {Array<ColorAttachmentOps>} */ __publicField(this, "colorArrayOps", []); /** @type {DepthStencilAttachmentOps} */ __publicField(this, "depthStencilOps"); /** * 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. */ __publicField(this, "requiresCubemaps", true); /** * True if the render pass uses the full viewport / scissor for rendering into the render target. */ __publicField(this, "fullSizeClearRect", true); } /** * Color attachment operations for the first color attachment. * * @type {ColorAttachmentOps} */ get colorOps() { return this.colorArrayOps[0]; } 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; if (value) { this.scaleX = this.scaleX ?? 1; this.scaleY = 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 = null, options) { this.options = options; this.renderTarget = renderTarget; this.samples = Math.max(this.renderTarget ? this.renderTarget.samples : this.device.samples, 1); this.allocateAttachments(); this.postInit(); } allocateAttachments() { const rt = this.renderTarget; this.depthStencilOps = new DepthStencilAttachmentOps(); if (rt?.depthBuffer) { this.depthStencilOps.storeDepth = true; } const numColorOps = rt ? rt._colorBuffers?.length ?? 0 : 1; this.colorArrayOps.length = 0; for (let i = 0; i < numColorOps; i++) { const colorOps = new ColorAttachmentOps(); this.colorArrayOps[i] = colorOps; if (this.samples === 1) { colorOps.store = true; colorOps.resolve = false; } const colorBuffer = this.renderTarget?._colorBuffers?.[i]; if (this.renderTarget?.mipmaps && colorBuffer?.mipmaps) { const intFormat = isIntegerPixelFormat(colorBuffer._format); colorOps.genMipmaps = !intFormat; } } } postInit() { } frameUpdate() { if (this._options && this.renderTarget) { const resizeSource = this._options.resizeSource ?? this.device.backBuffer; const width = Math.floor(resizeSource.width * this.scaleX); const height = Math.floor(resizeSource.height * this.scaleY); this.renderTarget.resize(width, height); } } /** * 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) { const count = this.colorArrayOps.length; for (let i = 0; i < count; i++) { const 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 !== void 0) { this.depthStencilOps.clearDepthValue = depthValue; } this.depthStencilOps.clearDepth = depthValue !== void 0; } /** * 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 !== void 0) { this.depthStencilOps.clearStencilValue = stencilValue; } this.depthStencilOps.clearStencil = stencilValue !== void 0; } /** * Render the render pass */ render() { if (this.enabled) { const device = this.device; const realPass = this.renderTarget !== void 0; 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 = 0) { if (Tracing.get(TRACEID_RENDER_PASS) || Tracing.get(TRACEID_RENDER_PASS_DETAIL)) { const rt = this.renderTarget ?? (this.renderTarget === null ? device.backBuffer : null); const isBackBuffer = !!rt?.impl.assignedColorTexture || rt?.impl.suppliedColorFramebuffer !== void 0; const numColor = rt?._colorBuffers?.length ?? (isBackBuffer ? 1 : 0); const hasDepth = rt?.depth; const hasStencil = rt?.stencil; const mipLevel = rt?.mipLevel; const 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}` : ""}`; const indexString = this._skipStart ? "++" : index.toString().padEnd(2, " "); Debug.trace( TRACEID_RENDER_PASS, `${indexString}: ${this.name.padEnd(20, " ")}${this.executeEnabled ? "" : " DISABLED "}${rtInfo.padEnd(30)}` ); for (let i = 0; i < numColor; i++) { const colorOps = this.colorArrayOps[i]; const colorFormat = pixelFormatInfo.get(isBackBuffer ? device.backBufferFormat : rt.getColorBuffer(i).format)?.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) { const depthFormat = `${rt.depthBuffer ? ` [format: ${pixelFormatInfo.get(rt.depthBuffer.format)?.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}]` : ""}` ); } } } } } export { RenderPass };