/**
 * @module ol/webgl/PostProcessingPass
 */
import { getUid } from '../util.js';
var DEFAULT_VERTEX_SHADER = "\n  precision mediump float;\n  \n  attribute vec2 a_position;\n  varying vec2 v_texCoord;\n  varying vec2 v_screenCoord;\n  \n  uniform vec2 u_screenSize;\n   \n  void main() {\n    v_texCoord = a_position * 0.5 + 0.5;\n    v_screenCoord = v_texCoord * u_screenSize;\n    gl_Position = vec4(a_position, 0.0, 1.0);\n  }\n";
var DEFAULT_FRAGMENT_SHADER = "\n  precision mediump float;\n   \n  uniform sampler2D u_image;\n  uniform float u_opacity;\n   \n  varying vec2 v_texCoord;\n   \n  void main() {\n    gl_FragColor = texture2D(u_image, v_texCoord) * u_opacity;\n  }\n";
/**
 * @typedef {Object} Options
 * @property {WebGLRenderingContext} webGlContext WebGL context; mandatory.
 * @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
 * the main canvas that will then be sampled up (useful for saving resource on blur steps).
 * @property {string} [vertexShader] Vertex shader source
 * @property {string} [fragmentShader] Fragment shader source
 * @property {Object<string,import("./Helper").UniformValue>} [uniforms] Uniform definitions for the post process step
 */
/**
 * @typedef {Object} UniformInternalDescription
 * @property {import("./Helper").UniformValue} value Value
 * @property {number} location Location
 * @property {WebGLTexture} [texture] Texture
 * @private
 */
/**
 * @classdesc
 * This class is used to define Post Processing passes with custom shaders and uniforms.
 * This is used internally by {@link module:ol/webgl/Helper~WebGLHelper}.
 *
 * Please note that the final output on the DOM canvas is expected to have premultiplied alpha, which means that
 * a pixel which is 100% red with an opacity of 50% must have a color of (r=0.5, g=0, b=0, a=0.5).
 * Failing to provide pixel colors with premultiplied alpha will result in render anomalies.
 *
 * The default post-processing pass does *not* multiply color values with alpha value, it expects color values to be
 * premultiplied.
 *
 * Default shaders are shown hereafter:
 *
 * * Vertex shader:
 *
 *   ```
 *   precision mediump float;
 *
 *   attribute vec2 a_position;
 *   varying vec2 v_texCoord;
 *   varying vec2 v_screenCoord;
 *
 *   uniform vec2 u_screenSize;
 *
 *   void main() {
 *     v_texCoord = a_position * 0.5 + 0.5;
 *     v_screenCoord = v_texCoord * u_screenSize;
 *     gl_Position = vec4(a_position, 0.0, 1.0);
 *   }
 *   ```
 *
 * * Fragment shader:
 *
 *   ```
 *   precision mediump float;
 *
 *   uniform sampler2D u_image;
 *   uniform float u_opacity;
 *
 *   varying vec2 v_texCoord;
 *
 *   void main() {
 *     gl_FragColor = texture2D(u_image, v_texCoord) * u_opacity;
 *   }
 *   ```
 *
 * @api
 */
var WebGLPostProcessingPass = /** @class */ (function () {
    /**
     * @param {Options} options Options.
     */
    function WebGLPostProcessingPass(options) {
        this.gl_ = options.webGlContext;
        var gl = this.gl_;
        this.scaleRatio_ = options.scaleRatio || 1;
        this.renderTargetTexture_ = gl.createTexture();
        this.renderTargetTextureSize_ = null;
        this.frameBuffer_ = gl.createFramebuffer();
        // compile the program for the frame buffer
        // TODO: make compilation errors show up
        var vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, options.vertexShader || DEFAULT_VERTEX_SHADER);
        gl.compileShader(vertexShader);
        var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, options.fragmentShader || DEFAULT_FRAGMENT_SHADER);
        gl.compileShader(fragmentShader);
        this.renderTargetProgram_ = gl.createProgram();
        gl.attachShader(this.renderTargetProgram_, vertexShader);
        gl.attachShader(this.renderTargetProgram_, fragmentShader);
        gl.linkProgram(this.renderTargetProgram_);
        // bind the vertices buffer for the frame buffer
        this.renderTargetVerticesBuffer_ = gl.createBuffer();
        var verticesArray = [-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1];
        gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesArray), gl.STATIC_DRAW);
        this.renderTargetAttribLocation_ = gl.getAttribLocation(this.renderTargetProgram_, 'a_position');
        this.renderTargetUniformLocation_ = gl.getUniformLocation(this.renderTargetProgram_, 'u_screenSize');
        this.renderTargetOpacityLocation_ = gl.getUniformLocation(this.renderTargetProgram_, 'u_opacity');
        this.renderTargetTextureLocation_ = gl.getUniformLocation(this.renderTargetProgram_, 'u_image');
        /**
         * Holds info about custom uniforms used in the post processing pass
         * @type {Array<UniformInternalDescription>}
         * @private
         */
        this.uniforms_ = [];
        options.uniforms &&
            Object.keys(options.uniforms).forEach(function (name) {
                this.uniforms_.push({
                    value: options.uniforms[name],
                    location: gl.getUniformLocation(this.renderTargetProgram_, name),
                });
            }.bind(this));
    }
    /**
     * Get the WebGL rendering context
     * @return {WebGLRenderingContext} The rendering context.
     * @api
     */
    WebGLPostProcessingPass.prototype.getGL = function () {
        return this.gl_;
    };
    /**
     * Initialize the render target texture of the post process, make sure it is at the
     * right size and bind it as a render target for the next draw calls.
     * The last step to be initialized will be the one where the primitives are rendered.
     * @param {import("../PluggableMap.js").FrameState} frameState current frame state
     * @api
     */
    WebGLPostProcessingPass.prototype.init = function (frameState) {
        var gl = this.getGL();
        var textureSize = [
            gl.drawingBufferWidth * this.scaleRatio_,
            gl.drawingBufferHeight * this.scaleRatio_,
        ];
        // rendering goes to my buffer
        gl.bindFramebuffer(gl.FRAMEBUFFER, this.getFrameBuffer());
        gl.viewport(0, 0, textureSize[0], textureSize[1]);
        // if size has changed: adjust canvas & render target texture
        if (!this.renderTargetTextureSize_ ||
            this.renderTargetTextureSize_[0] !== textureSize[0] ||
            this.renderTargetTextureSize_[1] !== textureSize[1]) {
            this.renderTargetTextureSize_ = textureSize;
            // create a new texture
            var level = 0;
            var internalFormat = gl.RGBA;
            var border = 0;
            var format = gl.RGBA;
            var type = gl.UNSIGNED_BYTE;
            var data = null;
            gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
            gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, textureSize[0], textureSize[1], border, format, type, data);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            // bind the texture to the framebuffer
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.renderTargetTexture_, 0);
        }
    };
    /**
     * Render to the next postprocessing pass (or to the canvas if final pass).
     * @param {import("../PluggableMap.js").FrameState} frameState current frame state
     * @param {WebGLPostProcessingPass} [nextPass] Next pass, optional
     * @param {function(WebGLRenderingContext, import("../PluggableMap.js").FrameState):void} [preCompose] Called before composing.
     * @param {function(WebGLRenderingContext, import("../PluggableMap.js").FrameState):void} [postCompose] Called before composing.
     * @api
     */
    WebGLPostProcessingPass.prototype.apply = function (frameState, nextPass, preCompose, postCompose) {
        var gl = this.getGL();
        var size = frameState.size;
        gl.bindFramebuffer(gl.FRAMEBUFFER, nextPass ? nextPass.getFrameBuffer() : null);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
        if (!nextPass) {
            // clear the canvas if we are the first to render to it
            // and preserveDrawingBuffer is true
            var canvasId = getUid(gl.canvas);
            if (!frameState.renderTargets[canvasId]) {
                var attributes = gl.getContextAttributes();
                if (attributes && attributes.preserveDrawingBuffer) {
                    gl.clearColor(0.0, 0.0, 0.0, 0.0);
                    gl.clear(gl.COLOR_BUFFER_BIT);
                }
                frameState.renderTargets[canvasId] = true;
            }
        }
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);
        gl.useProgram(this.renderTargetProgram_);
        gl.enableVertexAttribArray(this.renderTargetAttribLocation_);
        gl.vertexAttribPointer(this.renderTargetAttribLocation_, 2, gl.FLOAT, false, 0, 0);
        gl.uniform2f(this.renderTargetUniformLocation_, size[0], size[1]);
        gl.uniform1i(this.renderTargetTextureLocation_, 0);
        var opacity = frameState.layerStatesArray[frameState.layerIndex].opacity;
        gl.uniform1f(this.renderTargetOpacityLocation_, opacity);
        this.applyUniforms(frameState);
        if (preCompose) {
            preCompose(gl, frameState);
        }
        gl.drawArrays(gl.TRIANGLES, 0, 6);
        if (postCompose) {
            postCompose(gl, frameState);
        }
    };
    /**
     * @return {WebGLFramebuffer} Frame buffer
     * @api
     */
    WebGLPostProcessingPass.prototype.getFrameBuffer = function () {
        return this.frameBuffer_;
    };
    /**
     * Sets the custom uniforms based on what was given in the constructor.
     * @param {import("../PluggableMap.js").FrameState} frameState Frame state.
     * @private
     */
    WebGLPostProcessingPass.prototype.applyUniforms = function (frameState) {
        var gl = this.getGL();
        var value;
        var textureSlot = 1;
        this.uniforms_.forEach(function (uniform) {
            value =
                typeof uniform.value === 'function'
                    ? uniform.value(frameState)
                    : uniform.value;
            // apply value based on type
            if (value instanceof HTMLCanvasElement || value instanceof ImageData) {
                // create a texture & put data
                if (!uniform.texture) {
                    uniform.texture = gl.createTexture();
                }
                gl.activeTexture(gl["TEXTURE".concat(textureSlot)]);
                gl.bindTexture(gl.TEXTURE_2D, uniform.texture);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                if (value instanceof ImageData) {
                    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, value.width, value.height, 0, gl.UNSIGNED_BYTE, new Uint8Array(value.data));
                }
                else {
                    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
                }
                // fill texture slots
                gl.uniform1i(uniform.location, textureSlot++);
            }
            else if (Array.isArray(value)) {
                switch (value.length) {
                    case 2:
                        gl.uniform2f(uniform.location, value[0], value[1]);
                        return;
                    case 3:
                        gl.uniform3f(uniform.location, value[0], value[1], value[2]);
                        return;
                    case 4:
                        gl.uniform4f(uniform.location, value[0], value[1], value[2], value[3]);
                        return;
                    default:
                        return;
                }
            }
            else if (typeof value === 'number') {
                gl.uniform1f(uniform.location, value);
            }
        });
    };
    return WebGLPostProcessingPass;
}());
export default WebGLPostProcessingPass;
//# sourceMappingURL=PostProcessingPass.js.map