text: import Cartesian2 from "../Core/Cartesian2.js";
  import Color from "../Core/Color.js";
  import defined from "../Core/defined.js";
  import destroyObject from "../Core/destroyObject.js";
  import ClearCommand from "../Renderer/ClearCommand.js";
  import FramebufferManager from "../Renderer/FramebufferManager.js";
  import PixelDatatype from "../Renderer/PixelDatatype.js";
  
  /**
   * A post process stage that will get the luminance value at each pixel and
   * uses parallel reduction to compute the average luminance in a 1x1 texture.
   * This texture can be used as input for tone mapping.
   *
   * @constructor
   * @private
   */
  function AutoExposure() {
    this._uniformMap = undefined;
    this._command = undefined;
  
    this._colorTexture = undefined;
    this._depthTexture = undefined;
  
    this._ready = false;
  
    this._name = "czm_autoexposure";
  
    this._logDepthChanged = undefined;
    this._useLogDepth = undefined;
  
    this._framebuffers = undefined;
    this._previousLuminance = new FramebufferManager();
  
    this._commands = undefined;
    this._clearCommand = undefined;
  
    this._minMaxLuminance = new Cartesian2();
  
    /**
     * Whether or not to execute this post-process stage when ready.
     *
     * @type {Boolean}
     */
    this.enabled = true;
    this._enabled = true;
  
    /**
     * The minimum value used to clamp the luminance.
     *
     * @type {Number}
     * @default 0.1
     */
    this.minimumLuminance = 0.1;
  
    /**
     * The maximum value used to clamp the luminance.
     *
     * @type {Number}
     * @default 10.0
     */
    this.maximumLuminance = 10.0;
  }
  
  Object.defineProperties(AutoExposure.prototype, {
    /**
     * Determines if this post-process stage is ready to be executed. A stage is only executed when both <code>ready</code>
     * and {@link AutoExposure#enabled} are <code>true</code>. A stage will not be ready while it is waiting on textures
     * to load.
     *
     * @memberof AutoExposure.prototype
     * @type {Boolean}
     * @readonly
     */
    ready: {
      get: function () {
        return this._ready;
      },
    },
    /**
     * The unique name of this post-process stage for reference by other stages.
     *
     * @memberof AutoExposure.prototype
     * @type {String}
     * @readonly
     */
    name: {
      get: function () {
        return this._name;
      },
    },
  
    /**
     * A reference to the texture written to when executing this post process stage.
     *
     * @memberof AutoExposure.prototype
     * @type {Texture}
     * @readonly
     * @private
     */
    outputTexture: {
      get: function () {
        const framebuffers = this._framebuffers;
        if (!defined(framebuffers)) {
          return undefined;
        }
        return framebuffers[framebuffers.length - 1].getColorTexture(0);
      },
    },
  });
  
  function destroyFramebuffers(autoexposure) {
    const framebuffers = autoexposure._framebuffers;
    if (!defined(framebuffers)) {
      return;
    }
  
    const length = framebuffers.length;
    for (let i = 0; i < length; ++i) {
      framebuffers[i].destroy();
    }
    autoexposure._framebuffers = undefined;
  
    autoexposure._previousLuminance.destroy();
    autoexposure._previousLuminance = undefined;
  }
  
  function createFramebuffers(autoexposure, context) {
    destroyFramebuffers(autoexposure);
  
    let width = autoexposure._width;
    let height = autoexposure._height;
  
    const pixelDatatype = context.halfFloatingPointTexture
      ? PixelDatatype.HALF_FLOAT
      : PixelDatatype.FLOAT;
  
    const length = Math.ceil(Math.log(Math.max(width, height)) / Math.log(3.0));
    const framebuffers = new Array(length);
    for (let i = 0; i < length; ++i) {
      width = Math.max(Math.ceil(width / 3.0), 1.0);
      height = Math.max(Math.ceil(height / 3.0), 1.0);
      framebuffers[i] = new FramebufferManager();
      framebuffers[i].update(context, width, height, 1, pixelDatatype);
    }
  
    const lastTexture = framebuffers[length - 1].getColorTexture(0);
    autoexposure._previousLuminance.update(
      context,
      lastTexture.width,
      lastTexture.height,
      1,
      pixelDatatype
    );
    autoexposure._framebuffers = framebuffers;
  }
  
  function destroyCommands(autoexposure) {
    const commands = autoexposure._commands;
    if (!defined(commands)) {
      return;
    }
  
    const length = commands.length;
    for (let i = 0; i < length; ++i) {
      commands[i].shaderProgram.destroy();
    }
    autoexposure._commands = undefined;
  }
  
  function createUniformMap(autoexposure, index) {
    let uniforms;
    if (index === 0) {
      uniforms = {
        colorTexture: function () {
          return autoexposure._colorTexture;
        },
        colorTextureDimensions: function () {
          return autoexposure._colorTexture.dimensions;
        },
      };
    } else {
      const texture = autoexposure._framebuffers[index - 1].getColorTexture(0);
      uniforms = {
        colorTexture: function () {
          return texture;
        },
        colorTextureDimensions: function () {
          return texture.dimensions;
        },
      };
    }
  
    uniforms.minMaxLuminance = function () {
      return autoexposure._minMaxLuminance;
    };
    uniforms.previousLuminance = function () {
      return autoexposure._previousLuminance.getColorTexture(0);
    };
  
    return uniforms;
  }
  
  function getShaderSource(index, length) {
    let source =
      "uniform sampler2D colorTexture; \n" +
      "varying vec2 v_textureCoordinates; \n" +
      "float sampleTexture(vec2 offset) { \n";
  
    if (index === 0) {
      source +=
        "    vec4 color = texture2D(colorTexture, v_textureCoordinates + offset); \n" +
        "    return czm_luminance(color.rgb); \n";
    } else {
      source +=
        "    return texture2D(colorTexture, v_textureCoordinates + offset).r; \n";
    }
  
    source += "}\n\n";
  
    source +=
      "uniform vec2 colorTextureDimensions; \n" +
      "uniform vec2 minMaxLuminance; \n" +
      "uniform sampler2D previousLuminance; \n" +
      "void main() { \n" +
      "    float color = 0.0; \n" +
      "    float xStep = 1.0 / colorTextureDimensions.x; \n" +
      "    float yStep = 1.0 / colorTextureDimensions.y; \n" +
      "    int count = 0; \n" +
      "    for (int i = 0; i < 3; ++i) { \n" +
      "        for (int j = 0; j < 3; ++j) { \n" +
      "            vec2 offset; \n" +
      "            offset.x = -xStep + float(i) * xStep; \n" +
      "            offset.y = -yStep + float(j) * yStep; \n" +
      "            if (offset.x < 0.0 || offset.x > 1.0 || offset.y < 0.0 || offset.y > 1.0) { \n" +
      "                continue; \n" +
      "            } \n" +
      "            color += sampleTexture(offset); \n" +
      "            ++count; \n" +
      "        } \n" +
      "    } \n" +
      "    if (count > 0) { \n" +
      "        color /= float(count); \n" +
      "    } \n";
  
    if (index === length - 1) {
      source +=
        "    float previous = texture2D(previousLuminance, vec2(0.5)).r; \n" +
        "    color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n" +
        "    color = previous + (color - previous) / (60.0 * 1.5); \n" +
        "    color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n";
    }
  
    source += "    gl_FragColor = vec4(color); \n" + "} \n";
    return source;
  }
  
  function createCommands(autoexposure, context) {
    destroyCommands(autoexposure);
    const framebuffers = autoexposure._framebuffers;
    const length = framebuffers.length;
  
    const commands = new Array(length);
  
    for (let i = 0; i < length; ++i) {
      commands[i] = context.createViewportQuadCommand(
        getShaderSource(i, length),
        {
          framebuffer: framebuffers[i].framebuffer,
          uniformMap: createUniformMap(autoexposure, i),
        }
      );
    }
    autoexposure._commands = commands;
  }
  
  /**
   * A function that will be called before execute. Used to clear any textures attached to framebuffers.
   * @param {Context} context The context.
   * @private
   */
  AutoExposure.prototype.clear = function (context) {
    const framebuffers = this._framebuffers;
    if (!defined(framebuffers)) {
      return;
    }
  
    let clearCommand = this._clearCommand;
    if (!defined(clearCommand)) {
      clearCommand = this._clearCommand = new ClearCommand({
        color: new Color(0.0, 0.0, 0.0, 0.0),
        framebuffer: undefined,
      });
    }
  
    const length = framebuffers.length;
    for (let i = 0; i < length; ++i) {
      framebuffers[i].clear(context, clearCommand);
    }
  };
  
  /**
   * A function that will be called before execute. Used to create WebGL resources and load any textures.
   * @param {Context} context The context.
   * @private
   */
  AutoExposure.prototype.update = function (context) {
    const width = context.drawingBufferWidth;
    const height = context.drawingBufferHeight;
  
    if (width !== this._width || height !== this._height) {
      this._width = width;
      this._height = height;
  
      createFramebuffers(this, context);
      createCommands(this, context);
  
      if (!this._ready) {
        this._ready = true;
      }
    }
  
    this._minMaxLuminance.x = this.minimumLuminance;
    this._minMaxLuminance.y = this.maximumLuminance;
  
    const framebuffers = this._framebuffers;
    const temp = framebuffers[framebuffers.length - 1];
    framebuffers[framebuffers.length - 1] = this._previousLuminance;
    this._commands[
      this._commands.length - 1
    ].framebuffer = this._previousLuminance.framebuffer;
    this._previousLuminance = temp;
  };
  
  /**
   * Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage.
   * @param {Context} context The context.
   * @param {Texture} colorTexture The input color texture.
   * @private
   */
  AutoExposure.prototype.execute = function (context, colorTexture) {
    this._colorTexture = colorTexture;
  
    const commands = this._commands;
    if (!defined(commands)) {
      return;
    }
  
    const length = commands.length;
    for (let i = 0; i < length; ++i) {
      commands[i].execute(context);
    }
  };
  
  /**
   * Returns true if this object was destroyed; otherwise, false.
   * <p>
   * If this object was destroyed, it should not be used; calling any function other than
   * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
   * </p>
   *
   * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
   *
   * @see AutoExposure#destroy
   */
  AutoExposure.prototype.isDestroyed = function () {
    return false;
  };
  
  /**
   * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
   * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
   * <p>
   * Once an object is destroyed, it should not be used; calling any function other than
   * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
   * assign the return value (<code>undefined</code>) to the object as done in the example.
   * </p>
   *
   * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
   *
   * @see AutoExposure#isDestroyed
   */
  AutoExposure.prototype.destroy = function () {
    destroyFramebuffers(this);
    destroyCommands(this);
    return destroyObject(this);
  };
  export default AutoExposure;
  