UNPKG

@pixi/core

Version:
299 lines (298 loc) 16.3 kB
"use strict"; var constants = require("@pixi/constants"), extensions = require("@pixi/extensions"), math = require("@pixi/math"), settings = require("@pixi/settings"), Framebuffer = require("./Framebuffer.js"), GLFramebuffer = require("./GLFramebuffer.js"); const tempRectangle = new math.Rectangle(); class FramebufferSystem { /** * @param renderer - The renderer this System works for. */ constructor(renderer) { this.renderer = renderer, this.managedFramebuffers = [], this.unknownFramebuffer = new Framebuffer.Framebuffer(10, 10), this.msaaSamples = null; } /** Sets up the renderer context and necessary buffers. */ contextChange() { this.disposeAll(!0); const gl = this.gl = this.renderer.gl; if (this.CONTEXT_UID = this.renderer.CONTEXT_UID, this.current = this.unknownFramebuffer, this.viewport = new math.Rectangle(), this.hasMRT = !0, this.writeDepthTexture = !0, this.renderer.context.webGLVersion === 1) { let nativeDrawBuffersExtension = this.renderer.context.extensions.drawBuffers, nativeDepthTextureExtension = this.renderer.context.extensions.depthTexture; settings.settings.PREFER_ENV === constants.ENV.WEBGL_LEGACY && (nativeDrawBuffersExtension = null, nativeDepthTextureExtension = null), nativeDrawBuffersExtension ? gl.drawBuffers = (activeTextures) => nativeDrawBuffersExtension.drawBuffersWEBGL(activeTextures) : (this.hasMRT = !1, gl.drawBuffers = () => { }), nativeDepthTextureExtension || (this.writeDepthTexture = !1); } else this.msaaSamples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES); } /** * Bind a framebuffer. * @param framebuffer * @param frame - frame, default is framebuffer size * @param mipLevel - optional mip level to set on the framebuffer - defaults to 0 */ bind(framebuffer, frame, mipLevel = 0) { const { gl } = this; if (framebuffer) { const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID] || this.initFramebuffer(framebuffer); this.current !== framebuffer && (this.current = framebuffer, gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.framebuffer)), fbo.mipLevel !== mipLevel && (framebuffer.dirtyId++, framebuffer.dirtyFormat++, fbo.mipLevel = mipLevel), fbo.dirtyId !== framebuffer.dirtyId && (fbo.dirtyId = framebuffer.dirtyId, fbo.dirtyFormat !== framebuffer.dirtyFormat ? (fbo.dirtyFormat = framebuffer.dirtyFormat, fbo.dirtySize = framebuffer.dirtySize, this.updateFramebuffer(framebuffer, mipLevel)) : fbo.dirtySize !== framebuffer.dirtySize && (fbo.dirtySize = framebuffer.dirtySize, this.resizeFramebuffer(framebuffer))); for (let i = 0; i < framebuffer.colorTextures.length; i++) { const tex = framebuffer.colorTextures[i]; this.renderer.texture.unbind(tex.parentTextureArray || tex); } if (framebuffer.depthTexture && this.renderer.texture.unbind(framebuffer.depthTexture), frame) { const mipWidth = frame.width >> mipLevel, mipHeight = frame.height >> mipLevel, scale = mipWidth / frame.width; this.setViewport( frame.x * scale, frame.y * scale, mipWidth, mipHeight ); } else { const mipWidth = framebuffer.width >> mipLevel, mipHeight = framebuffer.height >> mipLevel; this.setViewport(0, 0, mipWidth, mipHeight); } } else this.current && (this.current = null, gl.bindFramebuffer(gl.FRAMEBUFFER, null)), frame ? this.setViewport(frame.x, frame.y, frame.width, frame.height) : this.setViewport(0, 0, this.renderer.width, this.renderer.height); } /** * Set the WebGLRenderingContext's viewport. * @param x - X position of viewport * @param y - Y position of viewport * @param width - Width of viewport * @param height - Height of viewport */ setViewport(x, y, width, height) { const v = this.viewport; x = Math.round(x), y = Math.round(y), width = Math.round(width), height = Math.round(height), (v.width !== width || v.height !== height || v.x !== x || v.y !== y) && (v.x = x, v.y = y, v.width = width, v.height = height, this.gl.viewport(x, y, width, height)); } /** * Get the size of the current width and height. Returns object with `width` and `height` values. * @readonly */ get size() { return this.current ? { x: 0, y: 0, width: this.current.width, height: this.current.height } : { x: 0, y: 0, width: this.renderer.width, height: this.renderer.height }; } /** * Clear the color of the context * @param r - Red value from 0 to 1 * @param g - Green value from 0 to 1 * @param b - Blue value from 0 to 1 * @param a - Alpha value from 0 to 1 * @param {PIXI.BUFFER_BITS} [mask=BUFFER_BITS.COLOR | BUFFER_BITS.DEPTH] - Bitwise OR of masks * that indicate the buffers to be cleared, by default COLOR and DEPTH buffers. */ clear(r, g, b, a, mask = constants.BUFFER_BITS.COLOR | constants.BUFFER_BITS.DEPTH) { const { gl } = this; gl.clearColor(r, g, b, a), gl.clear(mask); } /** * Initialize framebuffer for this context * @protected * @param framebuffer * @returns - created GLFramebuffer */ initFramebuffer(framebuffer) { const { gl } = this, fbo = new GLFramebuffer.GLFramebuffer(gl.createFramebuffer()); return fbo.multisample = this.detectSamples(framebuffer.multisample), framebuffer.glFramebuffers[this.CONTEXT_UID] = fbo, this.managedFramebuffers.push(framebuffer), framebuffer.disposeRunner.add(this), fbo; } /** * Resize the framebuffer * @param framebuffer * @protected */ resizeFramebuffer(framebuffer) { const { gl } = this, fbo = framebuffer.glFramebuffers[this.CONTEXT_UID]; if (fbo.stencil) { gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil); let stencilFormat; this.renderer.context.webGLVersion === 1 ? stencilFormat = gl.DEPTH_STENCIL : framebuffer.depth && framebuffer.stencil ? stencilFormat = gl.DEPTH24_STENCIL8 : framebuffer.depth ? stencilFormat = gl.DEPTH_COMPONENT24 : stencilFormat = gl.STENCIL_INDEX8, fbo.msaaBuffer ? gl.renderbufferStorageMultisample( gl.RENDERBUFFER, fbo.multisample, stencilFormat, framebuffer.width, framebuffer.height ) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, framebuffer.width, framebuffer.height); } const colorTextures = framebuffer.colorTextures; let count = colorTextures.length; gl.drawBuffers || (count = Math.min(count, 1)); for (let i = 0; i < count; i++) { const texture = colorTextures[i], parentTexture = texture.parentTextureArray || texture; this.renderer.texture.bind(parentTexture, 0), i === 0 && fbo.msaaBuffer && (gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer), gl.renderbufferStorageMultisample( gl.RENDERBUFFER, fbo.multisample, parentTexture._glTextures[this.CONTEXT_UID].internalFormat, framebuffer.width, framebuffer.height )); } framebuffer.depthTexture && this.writeDepthTexture && this.renderer.texture.bind(framebuffer.depthTexture, 0); } /** * Update the framebuffer * @param framebuffer * @param mipLevel * @protected */ updateFramebuffer(framebuffer, mipLevel) { const { gl } = this, fbo = framebuffer.glFramebuffers[this.CONTEXT_UID], colorTextures = framebuffer.colorTextures; let count = colorTextures.length; gl.drawBuffers || (count = Math.min(count, 1)), fbo.multisample > 1 && this.canMultisampleFramebuffer(framebuffer) ? fbo.msaaBuffer = fbo.msaaBuffer || gl.createRenderbuffer() : fbo.msaaBuffer && (gl.deleteRenderbuffer(fbo.msaaBuffer), fbo.msaaBuffer = null, fbo.blitFramebuffer && (fbo.blitFramebuffer.dispose(), fbo.blitFramebuffer = null)); const activeTextures = []; for (let i = 0; i < count; i++) { const texture = colorTextures[i], parentTexture = texture.parentTextureArray || texture; this.renderer.texture.bind(parentTexture, 0), i === 0 && fbo.msaaBuffer ? (gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.msaaBuffer), gl.renderbufferStorageMultisample( gl.RENDERBUFFER, fbo.multisample, parentTexture._glTextures[this.CONTEXT_UID].internalFormat, framebuffer.width, framebuffer.height ), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, fbo.msaaBuffer)) : (gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, texture.target, parentTexture._glTextures[this.CONTEXT_UID].texture, mipLevel ), activeTextures.push(gl.COLOR_ATTACHMENT0 + i)); } if (activeTextures.length > 1 && gl.drawBuffers(activeTextures), framebuffer.depthTexture && this.writeDepthTexture) { const depthTexture = framebuffer.depthTexture; this.renderer.texture.bind(depthTexture, 0), gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture._glTextures[this.CONTEXT_UID].texture, mipLevel ); } if ((framebuffer.stencil || framebuffer.depth) && !(framebuffer.depthTexture && this.writeDepthTexture)) { fbo.stencil = fbo.stencil || gl.createRenderbuffer(); let stencilAttachment, stencilFormat; this.renderer.context.webGLVersion === 1 ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH_STENCIL) : framebuffer.depth && framebuffer.stencil ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH24_STENCIL8) : framebuffer.depth ? (stencilAttachment = gl.DEPTH_ATTACHMENT, stencilFormat = gl.DEPTH_COMPONENT24) : (stencilAttachment = gl.STENCIL_ATTACHMENT, stencilFormat = gl.STENCIL_INDEX8), gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.stencil), fbo.msaaBuffer ? gl.renderbufferStorageMultisample( gl.RENDERBUFFER, fbo.multisample, stencilFormat, framebuffer.width, framebuffer.height ) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, framebuffer.width, framebuffer.height), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, stencilAttachment, gl.RENDERBUFFER, fbo.stencil); } else fbo.stencil && (gl.deleteRenderbuffer(fbo.stencil), fbo.stencil = null); } /** * Returns true if the frame buffer can be multisampled. * @param framebuffer */ canMultisampleFramebuffer(framebuffer) { return this.renderer.context.webGLVersion !== 1 && framebuffer.colorTextures.length <= 1 && !framebuffer.depthTexture; } /** * Detects number of samples that is not more than a param but as close to it as possible * @param samples - number of samples * @returns - recommended number of samples */ detectSamples(samples) { const { msaaSamples } = this; let res = constants.MSAA_QUALITY.NONE; if (samples <= 1 || msaaSamples === null) return res; for (let i = 0; i < msaaSamples.length; i++) if (msaaSamples[i] <= samples) { res = msaaSamples[i]; break; } return res === 1 && (res = constants.MSAA_QUALITY.NONE), res; } /** * Only works with WebGL2 * * blits framebuffer to another of the same or bigger size * after that target framebuffer is bound * * Fails with WebGL warning if blits multisample framebuffer to different size * @param framebuffer - by default it blits "into itself", from renderBuffer to texture. * @param sourcePixels - source rectangle in pixels * @param destPixels - dest rectangle in pixels, assumed to be the same as sourcePixels */ blit(framebuffer, sourcePixels, destPixels) { const { current, renderer, gl, CONTEXT_UID } = this; if (renderer.context.webGLVersion !== 2 || !current) return; const fbo = current.glFramebuffers[CONTEXT_UID]; if (!fbo) return; if (!framebuffer) { if (!fbo.msaaBuffer) return; const colorTexture = current.colorTextures[0]; if (!colorTexture) return; fbo.blitFramebuffer || (fbo.blitFramebuffer = new Framebuffer.Framebuffer(current.width, current.height), fbo.blitFramebuffer.addColorTexture(0, colorTexture)), framebuffer = fbo.blitFramebuffer, framebuffer.colorTextures[0] !== colorTexture && (framebuffer.colorTextures[0] = colorTexture, framebuffer.dirtyId++, framebuffer.dirtyFormat++), (framebuffer.width !== current.width || framebuffer.height !== current.height) && (framebuffer.width = current.width, framebuffer.height = current.height, framebuffer.dirtyId++, framebuffer.dirtySize++); } sourcePixels || (sourcePixels = tempRectangle, sourcePixels.width = current.width, sourcePixels.height = current.height), destPixels || (destPixels = sourcePixels); const sameSize = sourcePixels.width === destPixels.width && sourcePixels.height === destPixels.height; this.bind(framebuffer), gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo.framebuffer), gl.blitFramebuffer( sourcePixels.left, sourcePixels.top, sourcePixels.right, sourcePixels.bottom, destPixels.left, destPixels.top, destPixels.right, destPixels.bottom, gl.COLOR_BUFFER_BIT, sameSize ? gl.NEAREST : gl.LINEAR ), gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer.glFramebuffers[this.CONTEXT_UID].framebuffer); } /** * Disposes framebuffer. * @param framebuffer - framebuffer that has to be disposed of * @param contextLost - If context was lost, we suppress all delete function calls */ disposeFramebuffer(framebuffer, contextLost) { const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID], gl = this.gl; if (!fbo) return; delete framebuffer.glFramebuffers[this.CONTEXT_UID]; const index = this.managedFramebuffers.indexOf(framebuffer); index >= 0 && this.managedFramebuffers.splice(index, 1), framebuffer.disposeRunner.remove(this), contextLost || (gl.deleteFramebuffer(fbo.framebuffer), fbo.msaaBuffer && gl.deleteRenderbuffer(fbo.msaaBuffer), fbo.stencil && gl.deleteRenderbuffer(fbo.stencil)), fbo.blitFramebuffer && this.disposeFramebuffer(fbo.blitFramebuffer, contextLost); } /** * Disposes all framebuffers, but not textures bound to them. * @param [contextLost=false] - If context was lost, we suppress all delete function calls */ disposeAll(contextLost) { const list = this.managedFramebuffers; this.managedFramebuffers = []; for (let i = 0; i < list.length; i++) this.disposeFramebuffer(list[i], contextLost); } /** * Forcing creation of stencil buffer for current framebuffer, if it wasn't done before. * Used by MaskSystem, when its time to use stencil mask for Graphics element. * * Its an alternative for public lazy `framebuffer.enableStencil`, in case we need stencil without rebind. * @private */ forceStencil() { const framebuffer = this.current; if (!framebuffer) return; const fbo = framebuffer.glFramebuffers[this.CONTEXT_UID]; if (!fbo || fbo.stencil && framebuffer.stencil) return; framebuffer.stencil = !0; const w = framebuffer.width, h = framebuffer.height, gl = this.gl, stencil = fbo.stencil = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, stencil); let stencilAttachment, stencilFormat; this.renderer.context.webGLVersion === 1 ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH_STENCIL) : framebuffer.depth ? (stencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT, stencilFormat = gl.DEPTH24_STENCIL8) : (stencilAttachment = gl.STENCIL_ATTACHMENT, stencilFormat = gl.STENCIL_INDEX8), fbo.msaaBuffer ? gl.renderbufferStorageMultisample(gl.RENDERBUFFER, fbo.multisample, stencilFormat, w, h) : gl.renderbufferStorage(gl.RENDERBUFFER, stencilFormat, w, h), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, stencilAttachment, gl.RENDERBUFFER, stencil); } /** Resets framebuffer stored state, binds screen framebuffer. Should be called before renderTexture reset(). */ reset() { this.current = this.unknownFramebuffer, this.viewport = new math.Rectangle(); } destroy() { this.renderer = null; } } FramebufferSystem.extension = { type: extensions.ExtensionType.RendererSystem, name: "framebuffer" }; extensions.extensions.add(FramebufferSystem); exports.FramebufferSystem = FramebufferSystem; //# sourceMappingURL=FramebufferSystem.js.map