UNPKG

playcanvas

Version:

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

386 lines (385 loc) 15.7 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 { platform } from "../../../core/platform.js"; import { PIXELFORMAT_RGBA8 } from "../constants.js"; import { DebugGraphics } from "../debug-graphics.js"; import { DeviceCache } from "../device-cache.js"; import { getMultisampledTextureCache } from "../multi-sampled-texture-cache.js"; const _validatedFboConfigs = new DeviceCache(); class FramebufferPair { /** * @param {WebGLFramebuffer} msaaFB - Multi-sampled rendering framebuffer. * @param {WebGLFramebuffer} resolveFB - Single-sampled resolve framebuffer. */ constructor(msaaFB, resolveFB) { /** * Multi-sampled rendering framebuffer. * * @type {WebGLFramebuffer|null} */ __publicField(this, "msaaFB"); /** * Single-sampled resolve framebuffer. * * @type {WebGLFramebuffer|null} */ __publicField(this, "resolveFB"); this.msaaFB = msaaFB; this.resolveFB = resolveFB; } /** * @param {WebGLRenderingContext} gl - The WebGL rendering context. */ destroy(gl) { if (this.msaaFB) { gl.deleteRenderbuffer(this.msaaFB); this.msaaFB = null; } if (this.resolveFB) { gl.deleteRenderbuffer(this.resolveFB); this.resolveFB = null; } } } class WebglRenderTarget { constructor() { __publicField(this, "_glFrameBuffer", null); __publicField(this, "_glDepthBuffer", null); __publicField(this, "_glResolveFrameBuffer", null); /** * A list of framebuffers created When MSAA and MRT are used together, one for each color buffer. * This allows color buffers to be resolved separately. * * @type {FramebufferPair[]} */ __publicField(this, "colorMrtFramebuffers", null); __publicField(this, "_glMsaaColorBuffers", []); __publicField(this, "_glMsaaDepthBuffer", null); /** * Key used to store _glMsaaDepthBuffer in the cache. */ __publicField(this, "msaaDepthBufferKey"); /** * The supplied single-sampled framebuffer for rendering. Undefined represents no supplied * framebuffer. Null represents the default framebuffer. A value represents a user-supplied * framebuffer. */ __publicField(this, "suppliedColorFramebuffer"); __publicField(this, "_isInitialized", false); } destroy(device) { const gl = device.gl; this._isInitialized = false; if (this._glFrameBuffer) { if (this._glFrameBuffer !== this.suppliedColorFramebuffer) { gl.deleteFramebuffer(this._glFrameBuffer); } this._glFrameBuffer = null; } if (this._glDepthBuffer) { gl.deleteRenderbuffer(this._glDepthBuffer); this._glDepthBuffer = null; } if (this._glResolveFrameBuffer) { if (this._glResolveFrameBuffer !== this.suppliedColorFramebuffer) { gl.deleteFramebuffer(this._glResolveFrameBuffer); } this._glResolveFrameBuffer = null; } this._glMsaaColorBuffers.forEach((buffer) => { gl.deleteRenderbuffer(buffer); }); this._glMsaaColorBuffers.length = 0; this.colorMrtFramebuffers?.forEach((framebuffer) => { framebuffer.destroy(gl); }); this.colorMrtFramebuffers = null; if (this._glMsaaDepthBuffer) { this._glMsaaDepthBuffer = null; if (this.msaaDepthBufferKey) { getMultisampledTextureCache(device).release(this.msaaDepthBufferKey); } } this.suppliedColorFramebuffer = void 0; } get initialized() { return this._isInitialized; } init(device, target) { const gl = device.gl; Debug.assert(!this._isInitialized, "Render target already initialized."); this._isInitialized = true; const buffers = []; if (this.suppliedColorFramebuffer !== void 0) { this._glFrameBuffer = this.suppliedColorFramebuffer; } else { Debug.call(() => { if (target.width <= 0 || target.height <= 0) { Debug.warnOnce(`Invalid render target size: ${target.width} x ${target.height}`, target); } }); this._glFrameBuffer = gl.createFramebuffer(); device.setFramebuffer(this._glFrameBuffer); const colorBufferCount = target._colorBuffers?.length ?? 0; const attachmentBaseConstant = gl.COLOR_ATTACHMENT0; for (let i = 0; i < colorBufferCount; ++i) { const colorBuffer = target.getColorBuffer(i); if (colorBuffer) { if (!colorBuffer.impl._glTexture) { colorBuffer._width = Math.min(colorBuffer.width, device.maxRenderBufferSize); colorBuffer._height = Math.min(colorBuffer.height, device.maxRenderBufferSize); device.setTexture(colorBuffer, 0); } gl.framebufferTexture2D( gl.FRAMEBUFFER, attachmentBaseConstant + i, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, target.mipLevel ); buffers.push(attachmentBaseConstant + i); } } gl.drawBuffers(buffers); const depthBuffer = target._depthBuffer; if (depthBuffer || target._depth) { const attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; if (depthBuffer) { if (!depthBuffer.impl._glTexture) { depthBuffer._width = Math.min(depthBuffer.width, device.maxRenderBufferSize); depthBuffer._height = Math.min(depthBuffer.height, device.maxRenderBufferSize); device.setTexture(depthBuffer, 0); } gl.framebufferTexture2D( gl.FRAMEBUFFER, attachmentPoint, depthBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, target._depthBuffer.impl._glTexture, target.mipLevel ); } else { const willRenderMsaa = target._samples > 1; if (!willRenderMsaa) { if (!this._glDepthBuffer) { this._glDepthBuffer = gl.createRenderbuffer(); } const internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F; gl.bindRenderbuffer(gl.RENDERBUFFER, this._glDepthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, target.width, target.height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glDepthBuffer); gl.bindRenderbuffer(gl.RENDERBUFFER, null); } } } Debug.call(() => this._checkFbo(device, target)); } if (target._samples > 1) { this._glResolveFrameBuffer = this._glFrameBuffer; this._glFrameBuffer = gl.createFramebuffer(); device.setFramebuffer(this._glFrameBuffer); const colorBufferCount = target._colorBuffers?.length ?? 0; if (this.suppliedColorFramebuffer !== void 0) { const buffer = gl.createRenderbuffer(); this._glMsaaColorBuffers.push(buffer); const internalFormat = device.backBufferFormat === PIXELFORMAT_RGBA8 ? gl.RGBA8 : gl.RGB8; gl.bindRenderbuffer(gl.RENDERBUFFER, buffer); gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer); } else { for (let i = 0; i < colorBufferCount; ++i) { const colorBuffer = target.getColorBuffer(i); if (colorBuffer) { const buffer = gl.createRenderbuffer(); this._glMsaaColorBuffers.push(buffer); gl.bindRenderbuffer(gl.RENDERBUFFER, buffer); gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, buffer); } } } if (target._depth) { Debug.assert(!this._glMsaaDepthBuffer); const internalFormat = target._stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT32F; const attachmentPoint = target._stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; let key; const depthBuffer = target._depthBuffer; if (depthBuffer) { key = `${depthBuffer.id}:${target.width}:${target.height}:${target._samples}:${internalFormat}:${attachmentPoint}`; this._glMsaaDepthBuffer = getMultisampledTextureCache(device).get(key); } if (!this._glMsaaDepthBuffer) { this._glMsaaDepthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, this._glMsaaDepthBuffer); gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, internalFormat, target.width, target.height); this._glMsaaDepthBuffer.destroy = function() { gl.deleteRenderbuffer(this); }; if (depthBuffer) { getMultisampledTextureCache(device).set(key, this._glMsaaDepthBuffer); } } this.msaaDepthBufferKey = key; gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._glMsaaDepthBuffer); } Debug.call(() => this._checkFbo(device, target, "MSAA")); if (colorBufferCount > 1) { this._createMsaaMrtFramebuffers(device, target, colorBufferCount); device.setFramebuffer(this._glFrameBuffer); gl.drawBuffers(buffers); } } } _createMsaaMrtFramebuffers(device, target, colorBufferCount) { const gl = device.gl; this.colorMrtFramebuffers = []; for (let i = 0; i < colorBufferCount; ++i) { const colorBuffer = target.getColorBuffer(i); const srcFramebuffer = gl.createFramebuffer(); device.setFramebuffer(srcFramebuffer); const buffer = this._glMsaaColorBuffers[i]; gl.bindRenderbuffer(gl.RENDERBUFFER, buffer); gl.renderbufferStorageMultisample(gl.RENDERBUFFER, target._samples, colorBuffer.impl._glInternalFormat, target.width, target.height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, buffer); gl.drawBuffers([gl.COLOR_ATTACHMENT0]); Debug.call(() => this._checkFbo(device, target, `MSAA-MRT-src${i}`)); const dstFramebuffer = gl.createFramebuffer(); device.setFramebuffer(dstFramebuffer); gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorBuffer._cubemap ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + target._face : gl.TEXTURE_2D, colorBuffer.impl._glTexture, 0 ); this.colorMrtFramebuffers[i] = new FramebufferPair(srcFramebuffer, dstFramebuffer); Debug.call(() => this._checkFbo(device, target, `MSAA-MRT-dst${i}`)); } } /** * Checks the completeness status of the currently bound WebGLFramebuffer object. * * @param {WebglGraphicsDevice} device - The graphics device. * @param {RenderTarget} target - The render target. * @param {string} [type] - An optional type string to append to the error message. * @private */ _checkFbo(device, target, type = "") { const colorFormats = target._colorBuffers?.map((b) => b?.format ?? -1).join(",") ?? ""; const depthInfo = target._depth ? target._depthBuffer ? `dt${target._depthBuffer.format}` : target._stencil ? "ds" : "d" : ""; const key = `${type}:${colorFormats}:${depthInfo}:${target._samples}`; const validated = _validatedFboConfigs.get(device, () => { const set = /* @__PURE__ */ new Set(); set.loseContext = () => set.clear(); return set; }); if (validated.has(key)) { return; } const gl = device.gl; const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); let errorCode; switch (status) { case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT: errorCode = "FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; break; case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: errorCode = "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; break; case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS: errorCode = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; break; case gl.FRAMEBUFFER_UNSUPPORTED: errorCode = "FRAMEBUFFER_UNSUPPORTED"; break; } if (status === gl.FRAMEBUFFER_COMPLETE) { validated.add(key); } Debug.assert(!errorCode, `Framebuffer creation failed with error code ${errorCode}, render target: ${target.name} ${type}`, target); } loseContext() { this._glFrameBuffer = null; this._glDepthBuffer = null; this._glResolveFrameBuffer = null; this._glMsaaColorBuffers.length = 0; this._glMsaaDepthBuffer = null; this.msaaDepthBufferKey = void 0; this.colorMrtFramebuffers = null; this.suppliedColorFramebuffer = void 0; this._isInitialized = false; } internalResolve(device, src, dst, target, mask) { Debug.assert(src !== dst, "Source and destination framebuffers must be different when blitting."); device.setScissor(0, 0, target.width, target.height); const gl = device.gl; gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src); gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dst); gl.blitFramebuffer( 0, 0, target.width, target.height, 0, 0, target.width, target.height, mask, gl.NEAREST ); } /** * @param {WebglGraphicsDevice} device - The graphics device. * @param {RenderTarget} target - The render target. * @param {boolean} color - Whether to resolve the color buffer. * @param {boolean} depth - Whether to resolve the depth buffer. */ resolve(device, target, color, depth) { const gl = device.gl; if (this.colorMrtFramebuffers) { if (color) { for (let i = 0; i < this.colorMrtFramebuffers.length; i++) { const fbPair = this.colorMrtFramebuffers[i]; DebugGraphics.pushGpuMarker(device, `RESOLVE-MRT${i}`); this.internalResolve(device, fbPair.msaaFB, fbPair.resolveFB, target, gl.COLOR_BUFFER_BIT); DebugGraphics.popGpuMarker(device); } } if (depth) { DebugGraphics.pushGpuMarker(device, "RESOLVE-MRT-DEPTH"); this.internalResolve(device, this._glFrameBuffer, this._glResolveFrameBuffer, target, gl.DEPTH_BUFFER_BIT); DebugGraphics.popGpuMarker(device); } } else { const isXrFramebuffer = !!device.defaultFramebuffer && this._glResolveFrameBuffer === device.defaultFramebuffer; const xrColorQuad = color && isXrFramebuffer && platform.visionos; if (xrColorQuad) { DebugGraphics.pushGpuMarker(device, "RESOLVE-XR-COLOR-QUAD"); device.resolveMsaaColorToXrFramebufferViaQuads( this._glFrameBuffer, this._glResolveFrameBuffer, target.width, target.height ); DebugGraphics.popGpuMarker(device); color = false; } if (color || depth) { DebugGraphics.pushGpuMarker(device, "RESOLVE"); this.internalResolve( device, this._glFrameBuffer, this._glResolveFrameBuffer, target, (color ? gl.COLOR_BUFFER_BIT : 0) | (depth ? gl.DEPTH_BUFFER_BIT : 0) ); DebugGraphics.popGpuMarker(device); } } gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer); } } export { WebglRenderTarget };