Source: render/DoubleBufferedFbo.js

/*
 * Copyright 2003-2006, 2009, 2017, 2018, 2019, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @exports DoubleBufferedFbo
 */
define([
    '../error/ArgumentError',
    '../util/Logger',
],
    function (
        ArgumentError,
        Logger) {
        'use strict';

        /**
         * Constructs a DoubleBufferedFbo.
         * @alias DoubleBufferedFbo
         * @constructor
         
         * @classdesc A DoubleBufferedFbo is typically used in offscreen rendering scenarios when the data from the previous frame is needed in the current frame.
         * Using two framebuffers backed by two textures avoids syncronization between the CPU and GPU.
         * Applications typically do not interact with this class.
         
         * @param {DrawContext} dc The current draw context.
         * @param {WebGLTexture} texture1 The texture for the first framebuffer.
         * @param {WebGLTexture} texture2 The texture for the second framebuffer.
         * @param {Number} width The width of the framebuffers, in pixels.
         * @param {Number} height The height of the framebuffers, in pixels.
         
         * @throws {ArgumentError} If the specified DrawContext or textures are missing, or if the width or height is less
         * than zero.
         */
        var DoubleBufferedFbo = function (dc, texture1, texture2, width, height) {
            if (!dc) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "DoubleBufferedFbo", "constructor",
                    "missing DrawContext"));
            }

            if (!texture1 || !texture2) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "DoubleBufferedFbo", "constructor",
                    "missing texture"));
            }

            if (width < 0 || height < 0) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "DoubleBufferedFbo", "constructor",
                    "The framebuffer width or height is less than zero."));
            }

            /**
             * The width of the framebuffers, in pixels.
             * @type {Number}
             * @readonly
             */
            this.width = width;

            /**
             * The height of the framebuffers, in pixels.
             * @type {Number}
             * @readonly
             */
            this.height = height;

            /**
             * Indicates the size of the framebuffers, in bytes.
             * @type {Number}
             * @readonly
             */
            this.size = width * height * 4 * 2;

            /**
             * @type {WebGLFramebuffer}
             */
            this.fbo1 = dc.currentGlContext.createFramebuffer();

            /**
             * @type {WebGLFramebuffer}
             */
            this.fbo2 = dc.currentGlContext.createFramebuffer();

            /**
             * @type {WebGLTexture}
             * @readonly
             */
            this.texture1 = texture1;

            /**
             * @type {WebGLTexture}
             * @readonly
             */
            this.texture2 = texture2;

            /**
             * Indicates if texture1 has been attached to the fbo1.
             * @type {Boolean}
             * @readonly
             */
            this.fbo1Complete = false;

            /**
             * Indicates if texture2 has been attached to the fbo2.
             * @type {Boolean}
             * @readonly
             */
            this.fbo2Complete = false;

            /**
             * Indicates which of the two framebuffers and textures is the primary one.
             * @type {Number}
             * @readonly
             */
            this.primary = 1;

            /**
             * Indicates if both framebuffers have been cleared using the clearFbo method.
             * Calling code must set this flag to false after rendering in one the the framebuffers.
             * @type {Boolean}
             */
            this.isCleared = true;
        };

        /**
         * Binds the primary fbo in the current WebGL graphics context.
         * @param {DrawContext} dc The current draw context.
         */
        DoubleBufferedFbo.prototype.bindFbo = function (dc) {
            var gl = dc.currentGlContext;
            var fbo = this.getPrimaryFbo();

            gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
            this.ensureFboComplete(dc);
        };

        /**
         * Binds the primary texture in the current WebGL graphics context.
         * @param {DrawContext} dc The current draw context.
         */
        DoubleBufferedFbo.prototype.bind = function (dc) {
            var gl = dc.currentGlContext;
            var texture = this.getPrimaryTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            dc.frameStatistics.incrementTextureLoadCount(1);
            return true;
        };

        /**
         * Disposes of the WebGL textures and framebuffers.
         * @param {WebGLRenderingContext} gl
         */
        DoubleBufferedFbo.prototype.dispose = function (gl) {
            gl.deleteTexture(this.texture1);
            gl.deleteTexture(this.texture2);
            gl.deleteFramebuffer(this.fbo1);
            gl.deleteFramebuffer(this.fbo2);
            this.texture1 = undefined;
            this.texture2 = undefined;
            this.fbo1 = undefined;
            this.fbo2 = undefined;
        };

        /**
         * Gets the primary framebuffer.
         * @return {WebGLFramebuffer}
         */
        DoubleBufferedFbo.prototype.getPrimaryFbo = function () {
            if (this.primary === 1) {
                return this.fbo1;
            }
            return this.fbo2;
        };

        /**
         * Gets the secondary framebuffer.
         * @return {WebGLFramebuffer}
         */
        DoubleBufferedFbo.prototype.getSecondaryFbo = function () {
            if (this.primary === 1) {
                return this.fbo2;
            }
            return this.fbo1;
        };

        /**
         * Gets the primary texture.
         * @return {WebGLTexture}
         */
        DoubleBufferedFbo.prototype.getPrimaryTexture = function () {
            if (this.primary === 1) {
                return this.texture1;
            }
            return this.texture2;
        };

        /**
         * Gets the secondary texture.
         * @return {WebGLTexture}
         */
        DoubleBufferedFbo.prototype.getSecondaryTexture = function () {
            if (this.primary === 1) {
                return this.texture2;
            }
            return this.texture1;
        };

        /**
         * Clears both frambuffers using the gl.clear command.
         * Calling code must make sure to rebind to the previously used framebuffer and set the propper viewport.
         * @param {DrawContext} dc The current draw context.
         */
        DoubleBufferedFbo.prototype.clearFbo = function (dc) {
            if (this.isCleared) {
                return;
            }

            var gl = dc.currentGlContext;
            var glAllBuffers = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT;

            this.bindFbo(dc);
            gl.clear(glAllBuffers);
            gl.viewport(0, 0, this.width, this.height);

            this.swap();

            this.bindFbo(dc);
            gl.clear(glAllBuffers);

            this.swap();

            this.isCleared = true;
        };

        /**
         * Swaps the primary and secondary framebuffer and texture.
         */
        DoubleBufferedFbo.prototype.swap = function () {
            if (this.primary === 1) {
                this.primary = 2;
            }
            else {
                this.primary = 1;
            }
        };

        /**
         * Ensures that the framebuffer has a texture attached.
         * @param {DrawContext} dc The current draw context.
         */
        DoubleBufferedFbo.prototype.ensureFboComplete = function (dc) {
            var gl = dc.currentGlContext;

            if (this.primary === 1 && !this.fbo1Complete) {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture1, 0);
                this.fbo1Complete = true;
            }
            else if (this.primary === 2 && !this.fbo2Complete) {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture2, 0);
                this.fbo2Complete = true;
            }
        };

        return DoubleBufferedFbo;
    });