// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/common/gl_scaler.h"

#include <algorithm>
#include <array>
#include <sstream>
#include <string>

#include "base/logging.h"
#include "components/viz/common/gpu/context_provider.h"
#include "gpu/GLES2/gl2chromium.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "ui/gfx/color_transform.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace viz {

namespace {

// The code in GLScaler that computes the ScalerStages is greatly simplified by
// being able to access the X and Y components by index (instead of
// Vector2d::x() or Vector2d::y()). Thus, define a helper class to represent the
// relative size as a 2-element std::array and convert to/from Vector2d.
struct RelativeSize : public std::array<int, 2> {
  using std::array<int, 2>::operator[];

  RelativeSize(int width, int height) : std::array<int, 2>{{width, height}} {}
  explicit RelativeSize(const gfx::Vector2d& v)
      : std::array<int, 2>{{v.x(), v.y()}} {}

  gfx::Vector2d AsVector2d() const {
    return gfx::Vector2d((*this)[0], (*this)[1]);
  }
};

std::ostream& operator<<(std::ostream& out, const RelativeSize& size) {
  return (out << size[0] << 'x' << size[1]);
}

}  // namespace

GLScaler::GLScaler(scoped_refptr<ContextProvider> context_provider)
    : context_provider_(std::move(context_provider)) {
  if (context_provider_) {
    DCHECK(context_provider_->ContextGL());
    context_provider_->AddObserver(this);
  }
}

GLScaler::~GLScaler() {
  OnContextLost();  // Ensures destruction in dependency order.
}

bool GLScaler::SupportsPreciseColorManagement() const {
  if (!context_provider_) {
    return false;
  }
  if (!supports_half_floats_.has_value()) {
    supports_half_floats_ = AreAllGLExtensionsPresent(
        context_provider_->ContextGL(),
        {"GL_EXT_color_buffer_half_float", "GL_OES_texture_half_float_linear"});
  }
  return supports_half_floats_.value();
}

int GLScaler::GetMaxDrawBuffersSupported() const {
  if (!context_provider_) {
    return 0;
  }

  if (max_draw_buffers_ < 0) {
    // Query the GL context for the multiple draw buffers extension and, if
    // present, the actual platform-supported maximum.
    GLES2Interface* const gl = context_provider_->ContextGL();
    DCHECK(gl);
    if (AreAllGLExtensionsPresent(gl, {"GL_EXT_draw_buffers"})) {
      gl->GetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max_draw_buffers_);
    }

    if (max_draw_buffers_ < 1) {
      max_draw_buffers_ = 1;
    }
  }

  return max_draw_buffers_;
}

bool GLScaler::Configure(const Parameters& new_params) {
  chain_.reset();
  shader_programs_.clear();

  if (!context_provider_) {
    return false;
  }
  GLES2Interface* const gl = context_provider_->ContextGL();
  DCHECK(gl);

  params_ = new_params;

  // Ensure the client has provided valid scaling vectors.
  if (params_.scale_from.x() == 0 || params_.scale_from.y() == 0 ||
      params_.scale_to.x() == 0 || params_.scale_to.y() == 0) {
    // The caller computed invalid scale_from and/or scale_to values.
    DVLOG(1) << __func__ << ": Invalid scaling vectors: scale_from="
             << params_.scale_from.ToString()
             << ", scale_to=" << params_.scale_to.ToString();
    return false;
  }

  // Resolve the color spaces according to the rules described in the header
  // file.
  if (!params_.source_color_space.IsValid()) {
    params_.source_color_space = gfx::ColorSpace::CreateSRGB();
  }
  if (!params_.output_color_space.IsValid()) {
    params_.output_color_space = params_.source_color_space;
  }

  // Check that 16-bit half floats are supported if precise color management is
  // being requested.
  if (params_.enable_precise_color_management) {
    if (!SupportsPreciseColorManagement()) {
      DVLOG(1) << __func__
               << ": GL context does not support the half-floats "
                  "required for precise color management.";
      return false;
    }
  }

  // Check that MRT support is available if certain export formats were
  // specified in the Parameters.
  if (params_.export_format == Parameters::ExportFormat::NV61 ||
      params_.export_format ==
          Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE) {
    if (GetMaxDrawBuffersSupported() < 2) {
      DVLOG(1) << __func__ << ": GL context does not support 2+ draw buffers.";
      return false;
    }
  }

  // Color space transformation is meaningless when using the deinterleaver
  // because it only deals with two color channels. This also means precise
  // color management must be disabled.
  if (params_.export_format ==
          Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE &&
      (params_.source_color_space != params_.output_color_space ||
       params_.enable_precise_color_management)) {
    NOTIMPLEMENTED();
    return false;
  }

  // Check that one of the two implemented output swizzles has been specified.
  for (GLenum s : params_.swizzle) {
    if (s != GL_RGBA && s != GL_BGRA_EXT) {
      NOTIMPLEMENTED();
      return false;
    }
  }

  // Create the chain of ScalerStages. If the quality setting is FAST or there
  // is no scaling to be done, just create a single stage.
  std::unique_ptr<ScalerStage> chain;
  if (params_.quality == Parameters::Quality::FAST ||
      params_.scale_from == params_.scale_to) {
    chain = std::make_unique<ScalerStage>(gl, Shader::BILINEAR, HORIZONTAL,
                                          params_.scale_from, params_.scale_to);
  } else if (params_.quality == Parameters::Quality::GOOD) {
    chain = CreateAGoodScalingChain(gl, params_.scale_from, params_.scale_to);
  } else if (params_.quality == Parameters::Quality::BEST) {
    chain = CreateTheBestScalingChain(gl, params_.scale_from, params_.scale_to);
  } else {
    NOTREACHED();
  }
  chain = MaybeAppendExportStage(gl, std::move(chain), params_.export_format);

  // Determine the color space and the data type of the pixels in the
  // intermediate textures, depending on whether precise color management is
  // enabled. Note that nothing special need be done here if no scaling will be
  // performed.
  GLenum intermediate_texture_type;
  if (params_.enable_precise_color_management &&
      params_.scale_from != params_.scale_to) {
    // Ensure the scaling color space is using a linear transfer function.
    constexpr auto kLinearFunction = std::make_tuple(1, 0, 1, 0, 0, 0, 1);
    SkColorSpaceTransferFn fn;
    if (params_.source_color_space.GetTransferFunction(&fn) &&
        std::make_tuple(fn.fA, fn.fB, fn.fC, fn.fD, fn.fE, fn.fF, fn.fG) ==
            kLinearFunction) {
      scaling_color_space_ = params_.source_color_space;
    } else {
      // Use the source color space, but with a linear transfer function.
      skcms_Matrix3x3 to_XYZD50;
      params_.source_color_space.GetPrimaryMatrix(&to_XYZD50);
      std::tie(fn.fA, fn.fB, fn.fC, fn.fD, fn.fE, fn.fF, fn.fG) =
          kLinearFunction;
      scaling_color_space_ = gfx::ColorSpace::CreateCustom(to_XYZD50, fn);
    }
    intermediate_texture_type = GL_HALF_FLOAT_OES;
  } else {
    scaling_color_space_ = params_.source_color_space;
    intermediate_texture_type = GL_UNSIGNED_BYTE;
  }

  // Set the shader program on the final stage. Include color space
  // transformation and swizzling, if necessary.
  std::unique_ptr<gfx::ColorTransform> transform;
  if (scaling_color_space_ != params_.output_color_space) {
    transform = gfx::ColorTransform::NewColorTransform(
        scaling_color_space_, params_.output_color_space,
        gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
    if (!transform->CanGetShaderSource()) {
      NOTIMPLEMENTED() << "color transform from "
                       << scaling_color_space_.ToString() << " to "
                       << params_.output_color_space.ToString();
      return false;
    }
  }
  ScalerStage* const final_stage = chain.get();
  final_stage->set_shader_program(
      GetShaderProgram(final_stage->shader(), intermediate_texture_type,
                       transform.get(), params_.swizzle));

  // Set the shader program on all prior stages. These stages are all operating
  // in the same color space, |scaling_color_space_|.
  static const GLenum kNoSwizzle[2] = {GL_RGBA, GL_RGBA};
  ScalerStage* input_stage = final_stage;
  while (input_stage->input_stage()) {
    input_stage = input_stage->input_stage();
    input_stage->set_shader_program(GetShaderProgram(
        input_stage->shader(), intermediate_texture_type, nullptr, kNoSwizzle));
  }
  // From this point, |input_stage| points to the first ScalerStage (i.e., the
  // one that will be reading from the source).

  // If necessary, prepend an extra "import stage" that color-converts the input
  // before any scaling occurs. It's important not to merge color space
  // conversion of the source with any other steps because the texture sampler
  // must not linearly interpolate until after the colors have been mapped to a
  // linear color space.
  if (params_.source_color_space != scaling_color_space_) {
    input_stage->set_input_stage(std::make_unique<ScalerStage>(
        gl, Shader::BILINEAR, HORIZONTAL, input_stage->scale_from(),
        input_stage->scale_from()));
    input_stage = input_stage->input_stage();
    transform = gfx::ColorTransform::NewColorTransform(
        params_.source_color_space, scaling_color_space_,
        gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
    if (!transform->CanGetShaderSource()) {
      NOTIMPLEMENTED() << "color transform from "
                       << params_.source_color_space.ToString() << " to "
                       << scaling_color_space_.ToString();
      return false;
    }
    input_stage->set_shader_program(
        GetShaderProgram(input_stage->shader(), intermediate_texture_type,
                         transform.get(), kNoSwizzle));
  }

  // If the source content is Y-flipped, the input scaler stage will perform
  // math to account for this. It also will flip the content during scaling so
  // that all following stages may assume the content is not flipped. Then, the
  // final stage must ensure the final output is correctly flipped-back (or not)
  // based on what the first stage did PLUS what is being requested by the
  // client code.
  if (params_.is_flipped_source) {
    input_stage->set_is_flipped_source(true);
    input_stage->set_flip_output(true);
  }
  if (input_stage->flip_output() != params_.flip_output) {
    final_stage->set_flip_output(!final_stage->flip_output());
  }

  chain_ = std::move(chain);
  VLOG(2) << __func__ << " built this: " << *this;
  return true;
}

bool GLScaler::ScaleToMultipleOutputs(GLuint src_texture,
                                      const gfx::Size& src_texture_size,
                                      const gfx::Vector2d& src_offset,
                                      GLuint dest_texture_0,
                                      GLuint dest_texture_1,
                                      const gfx::Rect& output_rect) {
  if (!chain_) {
    return false;
  }

  // Bind the vertex attributes used to sweep the entire source area when
  // executing the shader programs.
  GLES2Interface* const gl = context_provider_->ContextGL();
  DCHECK(gl);
  if (vertex_attributes_buffer_) {
    gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
  } else {
    gl->GenBuffers(1, &vertex_attributes_buffer_);
    gl->BindBuffer(GL_ARRAY_BUFFER, vertex_attributes_buffer_);
    gl->BufferData(GL_ARRAY_BUFFER, sizeof(ShaderProgram::kVertexAttributes),
                   ShaderProgram::kVertexAttributes, GL_STATIC_DRAW);
  }

  // Disable GL clipping/blending features that interfere with assumptions made
  // by the implementation. Only those known to possibly be enabled elsewhere in
  // Chromium code are disabled here, while the remainder are sanity-DCHECK'ed.
  gl->Disable(GL_SCISSOR_TEST);
  gl->Disable(GL_STENCIL_TEST);
  gl->Disable(GL_BLEND);
  DCHECK_NE(gl->IsEnabled(GL_CULL_FACE), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_DEPTH_TEST), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_POLYGON_OFFSET_FILL), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_SAMPLE_COVERAGE), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_SCISSOR_TEST), GL_TRUE);
  DCHECK_NE(gl->IsEnabled(GL_STENCIL_TEST), GL_TRUE);

  chain_->ScaleToMultipleOutputs(src_texture, src_texture_size, src_offset,
                                 dest_texture_0, dest_texture_1, output_rect);

  gl->BindBuffer(GL_ARRAY_BUFFER, 0);
  return true;
}

// static
bool GLScaler::ParametersHasSameScaleRatio(const GLScaler::Parameters& params,
                                           const gfx::Vector2d& from,
                                           const gfx::Vector2d& to) {
  // Returns true iff a_num/a_denom == b_num/b_denom.
  const auto AreRatiosEqual = [](int32_t a_num, int32_t a_denom, int32_t b_num,
                                 int32_t b_denom) -> bool {
    // The math (for each dimension):
    //   If: a_num/a_denom == b_num/b_denom
    //   Then: a_num*b_denom == b_num*a_denom
    //
    // ...and cast to int64_t to guarantee no overflow from the multiplications.
    return (static_cast<int64_t>(a_num) * b_denom) ==
           (static_cast<int64_t>(b_num) * a_denom);
  };

  return AreRatiosEqual(params.scale_from.x(), params.scale_to.x(), from.x(),
                        to.x()) &&
         AreRatiosEqual(params.scale_from.y(), params.scale_to.y(), from.y(),
                        to.y());
}

// static
bool GLScaler::ParametersAreEquivalent(const Parameters& a,
                                       const Parameters& b) {
  if (!ParametersHasSameScaleRatio(a, b.scale_from, b.scale_to) ||
      a.enable_precise_color_management != b.enable_precise_color_management ||
      a.quality != b.quality || a.is_flipped_source != b.is_flipped_source ||
      a.flip_output != b.flip_output || a.export_format != b.export_format ||
      a.swizzle[0] != b.swizzle[0] || a.swizzle[1] != b.swizzle[1]) {
    return false;
  }

  const gfx::ColorSpace source_color_space_a =
      a.source_color_space.IsValid() ? a.source_color_space
                                     : gfx::ColorSpace::CreateSRGB();
  const gfx::ColorSpace source_color_space_b =
      b.source_color_space.IsValid() ? b.source_color_space
                                     : gfx::ColorSpace::CreateSRGB();
  if (source_color_space_a != source_color_space_b) {
    return false;
  }

  const gfx::ColorSpace output_color_space_a = a.output_color_space.IsValid()
                                                   ? a.output_color_space
                                                   : source_color_space_a;
  const gfx::ColorSpace output_color_space_b = b.output_color_space.IsValid()
                                                   ? b.output_color_space
                                                   : source_color_space_b;
  return output_color_space_a == output_color_space_b;
}

void GLScaler::OnContextLost() {
  // The destruction order here is important due to data dependencies.
  chain_.reset();
  shader_programs_.clear();
  if (vertex_attributes_buffer_) {
    if (auto* gl = context_provider_->ContextGL()) {
      gl->DeleteBuffers(1, &vertex_attributes_buffer_);
    }
    vertex_attributes_buffer_ = 0;
  }
  if (context_provider_) {
    context_provider_->RemoveObserver(this);
    context_provider_ = nullptr;
  }
}

GLScaler::ShaderProgram* GLScaler::GetShaderProgram(
    Shader shader,
    GLenum texture_type,
    const gfx::ColorTransform* color_transform,
    const GLenum swizzle[2]) {
  const ShaderCacheKey key{
      shader,
      texture_type,
      color_transform ? color_transform->GetSrcColorSpace() : gfx::ColorSpace(),
      color_transform ? color_transform->GetDstColorSpace() : gfx::ColorSpace(),
      swizzle[0],
      swizzle[1]};
  auto it = shader_programs_.find(key);
  if (it == shader_programs_.end()) {
    GLES2Interface* const gl = context_provider_->ContextGL();
    DCHECK(gl);
    it = shader_programs_
             .emplace(std::piecewise_construct, std::forward_as_tuple(key),
                      std::forward_as_tuple(gl, shader, texture_type,
                                            color_transform, swizzle))
             .first;
  }
  return &it->second;
}

// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateAGoodScalingChain(
    gpu::gles2::GLES2Interface* gl,
    const gfx::Vector2d& scale_from,
    const gfx::Vector2d& scale_to) {
  DCHECK(scale_from.x() != 0 && scale_from.y() != 0)
      << "Bad scale_from: " << scale_from.ToString();
  DCHECK(scale_to.x() != 0 && scale_to.y() != 0)
      << "Bad scale_to: " << scale_to.ToString();
  DCHECK(scale_from != scale_to);

  // The GOOD quality chain performs one bilinear upscale followed by N bilinear
  // halvings, and does this is both directions. Exception: No upscale is needed
  // when |scale_from| is a power of two multiple of |scale_to|.
  //
  // Since all shaders use bilinear filtering, the heuristics below attempt to
  // greedily merge steps wherever possible to minimize GPU memory usage and
  // processing time. This also means that it will be extremely rare for the
  // stage doing the initial upscale to actually require a larger output texture
  // than the source texture (a downscale will be merged into the same stage).

  // Determine the initial upscaled-to size, as the minimum number of doublings
  // to make |scale_to| greater than |scale_from|.
  const RelativeSize from(scale_from);
  const RelativeSize to(scale_to);
  RelativeSize upscale_to = to;
  for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
    while (upscale_to[x_or_y] < from[x_or_y]) {
      upscale_to[x_or_y] *= 2;
    }
  }

  // Create the stages in order from first-to-last, taking the greediest path
  // each time. Something like an A* algorithm would be better for discovering
  // an optimal sequence of operations, and would allow using the BILINEAR3
  // shader as well, but the run-time performance to compute the stages would be
  // too prohibitive.
  std::unique_ptr<ScalerStage> chain;
  struct CandidateOp {
    Shader shader;
    Axis primary_axis;
    RelativeSize output_size;
  };
  std::vector<CandidateOp> candidates;
  for (RelativeSize cur = from; cur != to;
       cur = RelativeSize(chain->scale_to())) {
    candidates.clear();

    // Determine whether it's possible to do exactly 2 bilinear passes in both
    // directions.
    RelativeSize output_size_2x2 = {0, 0};
    for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
      if (cur[x_or_y] == from[x_or_y]) {
        // For the first stage, the 2 bilinear passes must be the initial
        // upscale followed by one downscale. If there is no initial upscale,
        // then the 2 passes must both be downscales.
        if (upscale_to[x_or_y] != from[x_or_y] &&
            upscale_to[x_or_y] / 2 >= to[x_or_y]) {
          output_size_2x2[x_or_y] = upscale_to[x_or_y] / 2;
        } else if (upscale_to[x_or_y] == from[x_or_y] &&
                   upscale_to[x_or_y] / 4 >= to[x_or_y]) {
          output_size_2x2[x_or_y] = cur[x_or_y] / 4;
        }
      } else {
        // For all later stages, the 2 bilinear passes must be 2 halvings.
        if (cur[x_or_y] / 4 >= to[x_or_y]) {
          output_size_2x2[x_or_y] = cur[x_or_y] / 4;
        }
      }
    }
    if (output_size_2x2[HORIZONTAL] != 0 && output_size_2x2[VERTICAL] != 0) {
      candidates.push_back(
          CandidateOp{Shader::BILINEAR2X2, HORIZONTAL, output_size_2x2});
    }

    // Determine the valid set of Ops that do 1 to 3 bilinear passes in one
    // direction and 0 or 1 pass in the other direction.
    for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
      // The first bilinear pass in x_or_y must be an upscale or a halving.
      Shader shader = Shader::BILINEAR;
      RelativeSize output_size = cur;
      if (cur[x_or_y] == from[x_or_y] && upscale_to[x_or_y] != from[x_or_y]) {
        output_size[x_or_y] = upscale_to[x_or_y];
      } else if (cur[x_or_y] / 2 >= to[x_or_y]) {
        output_size[x_or_y] /= 2;
      } else {
        DCHECK_EQ(cur[x_or_y], to[x_or_y]);
        continue;
      }

      // Determine whether 1 or 2 additional passes can be made in the same
      // direction.
      if (output_size[x_or_y] / 4 >= to[x_or_y]) {
        shader = Shader::BILINEAR4;  // 2 more passes == 3 total.
        output_size[x_or_y] /= 4;
      } else if (output_size[x_or_y] / 2 >= to[x_or_y]) {
        shader = Shader::BILINEAR2;  // 1 more pass == 2 total.
        output_size[x_or_y] /= 2;
      } else {
        DCHECK_EQ(output_size[x_or_y], to[x_or_y]);
      }

      // Determine whether 0 or 1 bilinear passes can be made in the other
      // direction at the same time.
      const Axis y_or_x = TheOtherAxis(x_or_y);
      if (cur[y_or_x] == from[y_or_x] && upscale_to[y_or_x] != from[y_or_x]) {
        output_size[y_or_x] = upscale_to[y_or_x];
      } else if (cur[y_or_x] / 2 >= to[y_or_x]) {
        output_size[y_or_x] /= 2;
      } else {
        DCHECK_EQ(cur[y_or_x], to[y_or_x]);
      }

      candidates.push_back(CandidateOp{shader, x_or_y, output_size});
    }

    // From the candidates, pick the one that produces the fewest number of
    // output pixels, and append a new ScalerStage. There are opportunities to
    // improve the "cost function" here (e.g., pixels in the Y direction
    // probably cost more to process than pixels in the X direction), but that
    // would require more research.
    const auto best_candidate = std::min_element(
        candidates.begin(), candidates.end(),
        [](const CandidateOp& a, const CandidateOp& b) {
          static_assert(sizeof(a.output_size[0]) <= sizeof(int32_t),
                        "Overflow issue in the math here.");
          const int64_t cost_of_a =
              int64_t{a.output_size[HORIZONTAL]} * a.output_size[VERTICAL];
          const int64_t cost_of_b =
              int64_t{b.output_size[HORIZONTAL]} * b.output_size[VERTICAL];
          return cost_of_a < cost_of_b;
        });
    DCHECK(best_candidate != candidates.end());
    DCHECK(cur != best_candidate->output_size)
        << "Best candidate's output size (" << best_candidate->output_size
        << ") should not equal the input size.";
    auto next_stage = std::make_unique<ScalerStage>(
        gl, best_candidate->shader, best_candidate->primary_axis,
        cur.AsVector2d(), best_candidate->output_size.AsVector2d());
    next_stage->set_input_stage(std::move(chain));
    chain = std::move(next_stage);
  }

  return chain;
}

// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::CreateTheBestScalingChain(
    gpu::gles2::GLES2Interface* gl,
    const gfx::Vector2d& scale_from,
    const gfx::Vector2d& scale_to) {
  // The BEST quality chain performs one bicubic upscale followed by N bicubic
  // halvings, and does this is both directions. Exception: No upscale is needed
  // when |scale_from| is a power of two multiple of |scale_to|.

  // Determine the initial upscaled-to size, as the minimum number of doublings
  // to make |scale_to| greater than |scale_from|.
  const RelativeSize from(scale_from);
  const RelativeSize to(scale_to);
  RelativeSize upscale_to = to;
  for (Axis x_or_y : std::array<Axis, 2>{HORIZONTAL, VERTICAL}) {
    while (upscale_to[x_or_y] < from[x_or_y]) {
      upscale_to[x_or_y] *= 2;
    }
  }

  // Create the stages in order from first-to-last.
  RelativeSize cur = from;
  std::unique_ptr<ScalerStage> chain;
  for (Axis x_or_y : std::array<Axis, 2>{VERTICAL, HORIZONTAL}) {
    if (upscale_to[x_or_y] != from[x_or_y]) {
      RelativeSize next = cur;
      next[x_or_y] = upscale_to[x_or_y];
      auto upscale_stage =
          std::make_unique<ScalerStage>(gl, Shader::BICUBIC_UPSCALE, x_or_y,
                                        cur.AsVector2d(), next.AsVector2d());
      upscale_stage->set_input_stage(std::move(chain));
      chain = std::move(upscale_stage);
      cur = next;
    }
    while (cur[x_or_y] > to[x_or_y]) {
      RelativeSize next = cur;
      next[x_or_y] /= 2;
      auto next_stage =
          std::make_unique<ScalerStage>(gl, Shader::BICUBIC_HALF_1D, x_or_y,
                                        cur.AsVector2d(), next.AsVector2d());
      next_stage->set_input_stage(std::move(chain));
      chain = std::move(next_stage);
      cur = next;
    }
  }
  DCHECK_EQ(cur, to);

  return chain;
}

// static
std::unique_ptr<GLScaler::ScalerStage> GLScaler::MaybeAppendExportStage(
    gpu::gles2::GLES2Interface* gl,
    std::unique_ptr<GLScaler::ScalerStage> chain,
    GLScaler::Parameters::ExportFormat export_format) {
  DCHECK(chain);

  if (export_format == Parameters::ExportFormat::INTERLEAVED_QUADS) {
    return chain;  // No format change.
  }

  // If the final stage uses the BILINEAR shader that is not upscaling, the
  // export stage can replace it with no change in the results. Otherwise, a
  // separate export stage will be appended.
  gfx::Vector2d scale_from = chain->scale_from();
  const gfx::Vector2d scale_to = chain->scale_to();
  if (chain->shader() == Shader::BILINEAR && scale_from.x() >= scale_to.x() &&
      scale_from.y() >= scale_to.y()) {
    chain = chain->take_input_stage();
  } else {
    scale_from = scale_to;
  }

  Shader shader = Shader::BILINEAR;
  scale_from.set_x(scale_from.x() * 4);
  switch (export_format) {
    case Parameters::ExportFormat::INTERLEAVED_QUADS:
      NOTREACHED();
      break;
    case Parameters::ExportFormat::CHANNEL_0:
      shader = Shader::PLANAR_CHANNEL_0;
      break;
    case Parameters::ExportFormat::CHANNEL_1:
      shader = Shader::PLANAR_CHANNEL_1;
      break;
    case Parameters::ExportFormat::CHANNEL_2:
      shader = Shader::PLANAR_CHANNEL_2;
      break;
    case Parameters::ExportFormat::CHANNEL_3:
      shader = Shader::PLANAR_CHANNEL_3;
      break;
    case Parameters::ExportFormat::NV61:
      shader = Shader::I422_NV61_MRT;
      break;
    case Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE:
      shader = Shader::DEINTERLEAVE_PAIRWISE_MRT;
      // Horizontal scale is only 0.5X, not 0.25X like all the others.
      scale_from.set_x(scale_from.x() / 2);
      break;
  }

  auto export_stage = std::make_unique<ScalerStage>(gl, shader, HORIZONTAL,
                                                    scale_from, scale_to);
  export_stage->set_input_stage(std::move(chain));
  return export_stage;
}

// static
GLScaler::Axis GLScaler::TheOtherAxis(GLScaler::Axis x_or_y) {
  return x_or_y == HORIZONTAL ? VERTICAL : HORIZONTAL;
}

// static
const char* GLScaler::GetShaderName(GLScaler::Shader shader) {
  switch (shader) {
#define CASE_RETURN_SHADER_STR(x) \
  case Shader::x:                 \
    return #x
    CASE_RETURN_SHADER_STR(BILINEAR);
    CASE_RETURN_SHADER_STR(BILINEAR2);
    CASE_RETURN_SHADER_STR(BILINEAR3);
    CASE_RETURN_SHADER_STR(BILINEAR4);
    CASE_RETURN_SHADER_STR(BILINEAR2X2);
    CASE_RETURN_SHADER_STR(BICUBIC_UPSCALE);
    CASE_RETURN_SHADER_STR(BICUBIC_HALF_1D);
    CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_0);
    CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_1);
    CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_2);
    CASE_RETURN_SHADER_STR(PLANAR_CHANNEL_3);
    CASE_RETURN_SHADER_STR(I422_NV61_MRT);
    CASE_RETURN_SHADER_STR(DEINTERLEAVE_PAIRWISE_MRT);
#undef CASE_RETURN_SHADER_STR
  }
}

// static
bool GLScaler::AreAllGLExtensionsPresent(
    gpu::gles2::GLES2Interface* gl,
    const std::vector<std::string>& names) {
  DCHECK(gl);
  if (const auto* extensions = gl->GetString(GL_EXTENSIONS)) {
    const std::string extensions_string =
        " " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
    for (const std::string& name : names) {
      if (extensions_string.find(" " + name + " ") == std::string::npos) {
        return false;
      }
    }
    return true;
  }
  return false;
}

GLScaler::Parameters::Parameters() = default;
GLScaler::Parameters::Parameters(const Parameters& other) = default;
GLScaler::Parameters::~Parameters() = default;

// static
const GLfloat GLScaler::ShaderProgram::kVertexAttributes[16] = {
    -1.0f, -1.0f, 0.0f, 0.0f,  // vertex 0
    1.0f,  -1.0f, 1.0f, 0.0f,  // vertex 1
    -1.0f, 1.0f,  0.0f, 1.0f,  // vertex 2
    1.0f,  1.0f,  1.0f, 1.0f,  // vertex 3
};

GLScaler::ShaderProgram::ShaderProgram(
    gpu::gles2::GLES2Interface* gl,
    GLScaler::Shader shader,
    GLenum texture_type,
    const gfx::ColorTransform* color_transform,
    const GLenum swizzle[2])
    : gl_(gl),
      shader_(shader),
      texture_type_(texture_type),
      program_(gl_->CreateProgram()) {
  DCHECK(program_);

  std::basic_ostringstream<GLchar> vertex_header;
  std::basic_ostringstream<GLchar> fragment_directives;
  std::basic_ostringstream<GLchar> fragment_header;
  std::basic_ostringstream<GLchar> shared_variables;
  std::basic_ostringstream<GLchar> vertex_main;
  std::basic_ostringstream<GLchar> fragment_main;

  vertex_header
      << ("precision highp float;\n"
          "attribute vec2 a_position;\n"
          "attribute vec2 a_texcoord;\n"
          "uniform vec4 src_rect;\n");

  fragment_header << "precision mediump float;\n";
  switch (texture_type_) {
    case GL_FLOAT:
      fragment_header << "precision highp sampler2D;\n";
      break;
    case GL_HALF_FLOAT_OES:
      fragment_header << "precision mediump sampler2D;\n";
      break;
    default:
      fragment_header << "precision lowp sampler2D;\n";
      break;
  }
  fragment_header << "uniform sampler2D s_texture;\n";

  if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
    const std::string& source = color_transform->GetShaderSource();
    // Assumption: gfx::ColorTransform::GetShaderSource() should provide a
    // function named DoColorConversion() that takes a vec3 argument and returns
    // a vec3.
    DCHECK_NE(source.find("DoColorConversion"), std::string::npos);
    fragment_header << source;
  }

  vertex_main
      << ("  gl_Position = vec4(a_position, 0.0, 1.0);\n"
          "  vec2 texcoord = src_rect.xy + a_texcoord * src_rect.zw;\n");

  switch (shader_) {
    case Shader::BILINEAR:
      shared_variables << "varying highp vec2 v_texcoord;\n";
      vertex_main << "  v_texcoord = texcoord;\n";
      fragment_main << "  vec4 sample = texture2D(s_texture, v_texcoord);\n";
      if (color_transform) {
        fragment_main << "  sample.rgb = DoColorConversion(sample.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  sample.rb = sample.br;\n";
      }
      fragment_main << "  gl_FragColor = sample;\n";
      break;

    case Shader::BILINEAR2:
      // This is equivialent to two passes of the BILINEAR shader above. It can
      // be used to scale an image down 1.0x-2.0x in either dimension, or
      // exactly 4x.
      shared_variables << "varying highp vec4 v_texcoords;\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 4.0;\n"
              "  v_texcoords.xy = texcoord + step;\n"
              "  v_texcoords.zw = texcoord - step;\n");
      fragment_main
          << ("  vec4 blended = (texture2D(s_texture, v_texcoords.xy) +\n"
              "                  texture2D(s_texture, v_texcoords.zw)) /\n"
              "                 2.0;\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::BILINEAR3:
      // This is kind of like doing 1.5 passes of the BILINEAR shader. It can be
      // used to scale an image down 1.5x-3.0x, or exactly 6x.
      shared_variables
          << ("varying highp vec4 v_texcoords0;\n"
              "varying highp vec2 v_texcoords1;\n");
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 3.0;\n"
              "  v_texcoords0.xy = texcoord + step;\n"
              "  v_texcoords0.zw = texcoord;\n"
              "  v_texcoords1 = texcoord - step;\n");
      fragment_main
          << ("  vec4 blended = (texture2D(s_texture, v_texcoords0.xy) +\n"
              "                  texture2D(s_texture, v_texcoords0.zw) +\n"
              "                  texture2D(s_texture, v_texcoords1)) / 3.0;\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::BILINEAR4:
      // This is equivialent to three passes of the BILINEAR shader above. It
      // can be used to scale an image down 2.0x-4.0x or exactly 8x.
      shared_variables << "varying highp vec4 v_texcoords[2];\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 8.0;\n"
              "  v_texcoords[0].xy = texcoord - step * 3.0;\n"
              "  v_texcoords[0].zw = texcoord - step;\n"
              "  v_texcoords[1].xy = texcoord + step;\n"
              "  v_texcoords[1].zw = texcoord + step * 3.0;\n");
      fragment_main
          << ("  vec4 blended = (\n"
              "      texture2D(s_texture, v_texcoords[0].xy) +\n"
              "      texture2D(s_texture, v_texcoords[0].zw) +\n"
              "      texture2D(s_texture, v_texcoords[1].xy) +\n"
              "      texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::BILINEAR2X2:
      // This is equivialent to four passes of the BILINEAR shader above, two in
      // each dimension. It can be used to scale an image down 1.0x-2.0x in both
      // X and Y directions. Or, it could be used to scale an image down by
      // exactly 4x in both dimensions.
      shared_variables << "varying highp vec4 v_texcoords[2];\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 4.0;\n"
              "  v_texcoords[0].xy = texcoord + vec2(step.x, step.y);\n"
              "  v_texcoords[0].zw = texcoord + vec2(step.x, -step.y);\n"
              "  v_texcoords[1].xy = texcoord + vec2(-step.x, step.y);\n"
              "  v_texcoords[1].zw = texcoord + vec2(-step.x, -step.y);\n");
      fragment_main
          << ("  vec4 blended = (\n"
              "      texture2D(s_texture, v_texcoords[0].xy) +\n"
              "      texture2D(s_texture, v_texcoords[0].zw) +\n"
              "      texture2D(s_texture, v_texcoords[1].xy) +\n"
              "      texture2D(s_texture, v_texcoords[1].zw)) / 4.0;\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::BICUBIC_UPSCALE:
      // When scaling up, 4 texture reads are necessary. However, some
      // instructions can be saved because the parameter passed to the bicubic
      // function will be in a known range. Also, when sampling the bicubic
      // function like this, the sum is always exactly one, so normalization can
      // be skipped as well.
      shared_variables << "varying highp vec2 v_texcoord;\n";
      vertex_main << "  v_texcoord = texcoord;\n";
      fragment_header
          << ("uniform highp vec2 src_pixelsize;\n"
              "uniform highp vec2 scaling_vector;\n"
              "const float a = -0.5;\n"
              // This function is equivialent to calling the bicubic
              // function with x-1, x, 1-x and 2-x (assuming
              // 0 <= x < 1). The following is the Catmull-Rom spline.
              // See: http://wikipedia.org/wiki/Cubic_Hermite_spline
              "vec4 filt4(float x) {\n"
              "  return vec4(x * x * x, x * x, x, 1) *\n"
              "         mat4(       a,      -2.0 * a,   a, 0.0,\n"
              "               a + 2.0,      -a - 3.0, 0.0, 1.0,\n"
              "              -a - 2.0, 3.0 + 2.0 * a,  -a, 0.0,\n"
              "                    -a,             a, 0.0, 0.0);\n"
              "}\n"
              "mat4 pixels_x(highp vec2 pos, highp vec2 step) {\n"
              "  return mat4(texture2D(s_texture, pos - step),\n"
              "              texture2D(s_texture, pos),\n"
              "              texture2D(s_texture, pos + step),\n"
              "              texture2D(s_texture, pos + step * 2.0));\n"
              "}\n");
      fragment_main
          << ("  highp vec2 pixel_pos = v_texcoord * src_pixelsize - \n"
              "      scaling_vector / 2.0;\n"
              "  highp float frac = fract(dot(pixel_pos, scaling_vector));\n"
              "  highp vec2 base = \n"
              "      (floor(pixel_pos) + vec2(0.5)) / src_pixelsize;\n"
              "  highp vec2 step = scaling_vector / src_pixelsize;\n"
              "  vec4 blended = pixels_x(base, step) * filt4(frac);\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::BICUBIC_HALF_1D:
      // This scales down an image by exactly half in one dimension. The
      // bilinear lookup reduces the number of texture reads from 8 to 4.
      shared_variables << "varying highp vec4 v_texcoords[2];\n";
      vertex_header
          << ("uniform vec2 scaling_vector;\n"
              "const float CenterDist = 99.0 / 140.0;\n"
              "const float LobeDist = 11.0 / 4.0;\n");
      vertex_main
          << ("  vec2 step = scaling_vector / 2.0;\n"
              "  v_texcoords[0].xy = texcoord - LobeDist * step;\n"
              "  v_texcoords[0].zw = texcoord - CenterDist * step;\n"
              "  v_texcoords[1].xy = texcoord + CenterDist * step;\n"
              "  v_texcoords[1].zw = texcoord + LobeDist * step;\n");
      fragment_header
          << ("const float CenterWeight = 35.0 / 64.0;\n"
              "const float LobeWeight = -3.0 / 64.0;\n");
      fragment_main
          << ("  vec4 blended = \n"
              // Lobe pixels
              "      (texture2D(s_texture, v_texcoords[0].xy) +\n"
              "       texture2D(s_texture, v_texcoords[1].zw)) *\n"
              "          LobeWeight +\n"
              // Center pixels
              "      (texture2D(s_texture, v_texcoords[0].zw) +\n"
              "       texture2D(s_texture, v_texcoords[1].xy)) *\n"
              "          CenterWeight;\n");
      if (color_transform) {
        fragment_main << "  blended.rgb = DoColorConversion(blended.rgb);\n";
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  blended.rb = blended.br;\n";
      }
      fragment_main << "  gl_FragColor = blended;\n";
      break;

    case Shader::PLANAR_CHANNEL_0:
    case Shader::PLANAR_CHANNEL_1:
    case Shader::PLANAR_CHANNEL_2:
    case Shader::PLANAR_CHANNEL_3: {
      // Select one color channel, and pack 4 pixels into one output quad. See
      // header file for diagram.
      shared_variables << "varying highp vec4 v_texcoords[2];\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 4.0;\n"
              "  v_texcoords[0].xy = texcoord - step * 1.5;\n"
              "  v_texcoords[0].zw = texcoord - step * 0.5;\n"
              "  v_texcoords[1].xy = texcoord + step * 0.5;\n"
              "  v_texcoords[1].zw = texcoord + step * 1.5;\n");
      std::basic_string<GLchar> convert_open;
      std::basic_string<GLchar> convert_close;
      if (color_transform && shader_ != Shader::PLANAR_CHANNEL_3) {
        convert_open = "DoColorConversion(";
        convert_close = ".rgb)";
      }
      const char selector = "rgba"[static_cast<int>(shader_) -
                                   static_cast<int>(Shader::PLANAR_CHANNEL_0)];
      fragment_main << "  vec4 packed_quad = vec4(\n"
                    << "      " << convert_open
                    << "texture2D(s_texture, v_texcoords[0].xy)"
                    << convert_close << '.' << selector << ",\n"
                    << "      " << convert_open
                    << "texture2D(s_texture, v_texcoords[0].zw)"
                    << convert_close << '.' << selector << ",\n"
                    << "      " << convert_open
                    << "texture2D(s_texture, v_texcoords[1].xy)"
                    << convert_close << '.' << selector << ",\n"
                    << "      " << convert_open
                    << "texture2D(s_texture, v_texcoords[1].zw)"
                    << convert_close << '.' << selector << ");\n";
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  packed_quad.rb = packed_quad.br;\n";
      }
      fragment_main << "  gl_FragColor = packed_quad;\n";
      break;
    }

    case Shader::I422_NV61_MRT:
      // I422 sampling, delivered via two output textures (NV61 format). See
      // header file for diagram.
      shared_variables << "varying highp vec4 v_texcoords[2];\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 4.0;\n"
              "  v_texcoords[0].xy = texcoord - step * 1.5;\n"
              "  v_texcoords[0].zw = texcoord - step * 0.5;\n"
              "  v_texcoords[1].xy = texcoord + step * 0.5;\n"
              "  v_texcoords[1].zw = texcoord + step * 1.5;\n");
      fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
      fragment_main
          << ("  vec3 pixel0 = texture2D(s_texture, v_texcoords[0].xy).rgb;\n"
              "  vec3 pixel1 = texture2D(s_texture, v_texcoords[0].zw).rgb;\n"
              "  vec3 pixel01 = (pixel0 + pixel1) / 2.0;\n"
              "  vec3 pixel2 = texture2D(s_texture, v_texcoords[1].xy).rgb;\n"
              "  vec3 pixel3 = texture2D(s_texture, v_texcoords[1].zw).rgb;\n"
              "  vec3 pixel23 = (pixel2 + pixel3) / 2.0;\n");
      if (color_transform) {
        fragment_main
            << ("  pixel0 = DoColorConversion(pixel0);\n"
                "  pixel1 = DoColorConversion(pixel1);\n"
                "  pixel01 = DoColorConversion(pixel01);\n"
                "  pixel2 = DoColorConversion(pixel2);\n"
                "  pixel3 = DoColorConversion(pixel3);\n"
                "  pixel23 = DoColorConversion(pixel23);\n");
      }
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main
            << ("  gl_FragData[0] = \n"
                "      vec4(pixel2.r, pixel1.r, pixel0.r, pixel3.r);\n");
      } else {
        fragment_main
            << ("  gl_FragData[0] = \n"
                "      vec4(pixel0.r, pixel1.r, pixel2.r, pixel3.r);\n");
      }
      if (swizzle[1] == GL_BGRA_EXT) {
        fragment_main
            << ("  gl_FragData[1] = \n"
                "      vec4(pixel23.g, pixel01.b, pixel01.g, pixel23.b);\n");
      } else {
        fragment_main
            << ("  gl_FragData[1] = \n"
                "      vec4(pixel01.g, pixel01.b, pixel23.g, pixel23.b);\n");
      }
      break;

    case Shader::DEINTERLEAVE_PAIRWISE_MRT:
      // Sample two pixels and unswizzle them. There's no need to do vertical
      // scaling with math, since the bilinear interpolation in the sampler
      // takes care of that.
      shared_variables << "varying highp vec4 v_texcoords;\n";
      vertex_header << "uniform vec2 scaling_vector;\n";
      vertex_main
          << ("  vec2 step = scaling_vector / 2.0;\n"
              "  v_texcoords.xy = texcoord - step * 0.5;\n"
              "  v_texcoords.zw = texcoord + step * 0.5;\n");
      fragment_directives << "#extension GL_EXT_draw_buffers : enable\n";
      DCHECK(!color_transform);
      fragment_main
          << ("  vec4 lo_uvuv = texture2D(s_texture, v_texcoords.xy);\n"
              "  vec4 hi_uvuv = texture2D(s_texture, v_texcoords.zw);\n"
              "  vec4 uuuu = vec4(lo_uvuv.rb, hi_uvuv.rb);\n"
              "  vec4 vvvv = vec4(lo_uvuv.ga, hi_uvuv.ga);\n");
      if (swizzle[0] == GL_BGRA_EXT) {
        fragment_main << "  uuuu.rb = uuuu.br;\n";
      }
      fragment_main << "  gl_FragData[0] = uuuu;\n";
      if (swizzle[1] == GL_BGRA_EXT) {
        fragment_main << "  vvvv.rb = vvvv.br;\n";
      }
      fragment_main << "  gl_FragData[1] = vvvv;\n";
      break;
  }

  // Helper function to compile the shader source and log the GLSL compiler's
  // results.
  const auto CompileShaderFromSource =
      [](GLES2Interface* gl, const std::basic_string<GLchar>& source,
         GLenum type) -> GLuint {
    VLOG(2) << __func__ << ": Compiling shader " << type
            << " with source:" << std::endl
            << source;
    const GLuint shader = gl->CreateShader(type);
    const GLchar* source_data = source.data();
    const GLint length = base::checked_cast<GLint>(source.size());
    gl->ShaderSource(shader, 1, &source_data, &length);
    gl->CompileShader(shader);
    GLint compile_status = GL_FALSE;
    gl->GetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);

    // Fetch logs and forward them to the system logger. If compilation failed,
    // clean-up and return 0 for error.
    if (compile_status != GL_TRUE || VLOG_IS_ON(2)) {
      GLint log_length = 0;
      gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
      std::string log;
      if (log_length > 0) {
        std::unique_ptr<GLchar[]> tmp(new GLchar[log_length]);
        GLsizei returned_log_length = 0;
        gl->GetShaderInfoLog(shader, log_length, &returned_log_length,
                             tmp.get());
        log.assign(tmp.get(), returned_log_length);
      }
      if (log.empty()) {
        log = "<<NO LOG>>";
      }
      if (compile_status != GL_TRUE) {
        LOG(ERROR) << __func__ << ": Compilation of shader " << type
                   << " failed:" << std::endl
                   << log;
        gl->DeleteShader(shader);
        return 0;
      }
      VLOG(2) << __func__ << ": Compilation of shader " << type
              << " succeeded:" << std::endl
              << log;
    }
    return shader;
  };

  // Compile the vertex shader and attach it to the program.
  const std::string shared_variables_str = shared_variables.str();
  const GLuint vertex_shader =
      CompileShaderFromSource(gl_,
                              vertex_header.str() + shared_variables_str +
                                  "void main() {\n" + vertex_main.str() + "}\n",
                              GL_VERTEX_SHADER);
  if (vertex_shader == 0) {
    return;
  }
  gl_->AttachShader(program_, vertex_shader);
  gl_->DeleteShader(vertex_shader);

  // Compile the fragment shader and attach to |program_|.
  const GLuint fragment_shader = CompileShaderFromSource(
      gl_,
      fragment_directives.str() + fragment_header.str() + shared_variables_str +
          "void main() {\n" + fragment_main.str() + "}\n",
      GL_FRAGMENT_SHADER);
  if (fragment_shader == 0) {
    return;
  }
  gl_->AttachShader(program_, fragment_shader);
  gl_->DeleteShader(fragment_shader);

  // Link the program.
  gl_->LinkProgram(program_);
  GLint link_status = GL_FALSE;
  gl_->GetProgramiv(program_, GL_LINK_STATUS, &link_status);
  if (link_status != GL_TRUE) {
    LOG(ERROR) << "Failed to link shader program.";
    return;
  }

#define DCHECK_RESOLVED_LOCATION(member)                                  \
  DCHECK(member != -1 || gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) \
      << "Failed to get " #member " in program, or GPU process crashed."

  // Resolve the locations of the global variables.
  position_location_ = gl_->GetAttribLocation(program_, "a_position");
  DCHECK_RESOLVED_LOCATION(position_location_);
  texcoord_location_ = gl_->GetAttribLocation(program_, "a_texcoord");
  DCHECK_RESOLVED_LOCATION(texcoord_location_);
  texture_location_ = gl_->GetUniformLocation(program_, "s_texture");
  DCHECK_RESOLVED_LOCATION(texture_location_);
  src_rect_location_ = gl_->GetUniformLocation(program_, "src_rect");
  DCHECK_RESOLVED_LOCATION(src_rect_location_);
  switch (shader_) {
    case Shader::BILINEAR:
      break;

    case Shader::BILINEAR2:
    case Shader::BILINEAR3:
    case Shader::BILINEAR4:
    case Shader::BILINEAR2X2:
    case Shader::BICUBIC_HALF_1D:
    case Shader::PLANAR_CHANNEL_0:
    case Shader::PLANAR_CHANNEL_1:
    case Shader::PLANAR_CHANNEL_2:
    case Shader::PLANAR_CHANNEL_3:
    case Shader::I422_NV61_MRT:
    case Shader::DEINTERLEAVE_PAIRWISE_MRT:
      scaling_vector_location_ =
          gl_->GetUniformLocation(program_, "scaling_vector");
      DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
      break;

    case Shader::BICUBIC_UPSCALE:
      src_pixelsize_location_ =
          gl_->GetUniformLocation(program_, "src_pixelsize");
      DCHECK_RESOLVED_LOCATION(src_pixelsize_location_);
      scaling_vector_location_ =
          gl_->GetUniformLocation(program_, "scaling_vector");
      DCHECK_RESOLVED_LOCATION(scaling_vector_location_);
      break;
  }

#undef DCHECK_RESOLVED_LOCATION
}

GLScaler::ShaderProgram::~ShaderProgram() {
  gl_->DeleteProgram(program_);
}

void GLScaler::ShaderProgram::UseProgram(const gfx::Size& src_texture_size,
                                         const gfx::RectF& src_rect,
                                         const gfx::Size& dst_size,
                                         GLScaler::Axis primary_axis,
                                         bool flip_y) {
  gl_->UseProgram(program_);

  // OpenGL defines the last parameter to VertexAttribPointer as type
  // "const GLvoid*" even though it is actually an offset into the buffer
  // object's data store and not a pointer to the client's address space.
  const void* offsets[2] = {nullptr,
                            reinterpret_cast<const void*>(2 * sizeof(GLfloat))};

  gl_->VertexAttribPointer(position_location_, 2, GL_FLOAT, GL_FALSE,
                           4 * sizeof(GLfloat), offsets[0]);
  gl_->EnableVertexAttribArray(position_location_);

  gl_->VertexAttribPointer(texcoord_location_, 2, GL_FLOAT, GL_FALSE,
                           4 * sizeof(GLfloat), offsets[1]);
  gl_->EnableVertexAttribArray(texcoord_location_);

  // Always sample from the first texture unit.
  gl_->Uniform1i(texture_location_, 0);

  // Convert |src_rect| from pixel coordinates to texture coordinates. The
  // source texture coordinates are in the range [0.0,1.0] for each dimension,
  // but the sampling rect may slightly "spill" outside that range (e.g., for
  // scaler overscan).
  GLfloat src_rect_texcoord[4] = {
      src_rect.x() / src_texture_size.width(),
      src_rect.y() / src_texture_size.height(),
      src_rect.width() / src_texture_size.width(),
      src_rect.height() / src_texture_size.height(),
  };
  if (flip_y) {
    src_rect_texcoord[1] += src_rect_texcoord[3];
    src_rect_texcoord[3] *= -1.0f;
  }
  gl_->Uniform4fv(src_rect_location_, 1, src_rect_texcoord);

  // Set shader-specific uniform inputs. The |scaling_vector| is the ratio of
  // the number of source pixels sampled per dest pixels output. It is used by
  // the shader programs to locate distinct texels from the source texture, and
  // sample them at the appropriate offset to produce each output texel.
  switch (shader_) {
    case Shader::BILINEAR:
      break;

    case Shader::BILINEAR2:
    case Shader::BILINEAR3:
    case Shader::BILINEAR4:
    case Shader::BICUBIC_HALF_1D:
    case Shader::PLANAR_CHANNEL_0:
    case Shader::PLANAR_CHANNEL_1:
    case Shader::PLANAR_CHANNEL_2:
    case Shader::PLANAR_CHANNEL_3:
    case Shader::I422_NV61_MRT:
    case Shader::DEINTERLEAVE_PAIRWISE_MRT:
      switch (primary_axis) {
        case HORIZONTAL:
          gl_->Uniform2f(scaling_vector_location_,
                         src_rect_texcoord[2] / dst_size.width(), 0.0);
          break;
        case VERTICAL:
          gl_->Uniform2f(scaling_vector_location_, 0.0,
                         src_rect_texcoord[3] / dst_size.height());
          break;
      }
      break;

    case Shader::BILINEAR2X2:
      gl_->Uniform2f(scaling_vector_location_,
                     src_rect_texcoord[2] / dst_size.width(),
                     src_rect_texcoord[3] / dst_size.height());
      break;

    case Shader::BICUBIC_UPSCALE:
      gl_->Uniform2f(src_pixelsize_location_, src_texture_size.width(),
                     src_texture_size.height());
      // For this shader program, the |scaling_vector| has an alternate meaning:
      // It is only being used to select whether bicubic sampling is stepped in
      // the X or the Y direction.
      gl_->Uniform2f(scaling_vector_location_,
                     primary_axis == HORIZONTAL ? 1.0 : 0.0,
                     primary_axis == VERTICAL ? 1.0 : 0.0);
      break;
  }
}

GLScaler::ScalerStage::ScalerStage(gpu::gles2::GLES2Interface* gl,
                                   GLScaler::Shader shader,
                                   GLScaler::Axis primary_axis,
                                   const gfx::Vector2d& scale_from,
                                   const gfx::Vector2d& scale_to)
    : gl_(gl),
      shader_(shader),
      primary_axis_(primary_axis),
      scale_from_(scale_from),
      scale_to_(scale_to) {
  DCHECK(gl_);
}

GLScaler::ScalerStage::~ScalerStage() {
  if (dest_framebuffer_) {
    gl_->DeleteFramebuffers(1, &dest_framebuffer_);
  }
  if (intermediate_texture_) {
    gl_->DeleteTextures(1, &intermediate_texture_);
  }
}

void GLScaler::ScalerStage::ScaleToMultipleOutputs(
    GLuint src_texture,
    gfx::Size src_texture_size,
    const gfx::Vector2d& src_offset,
    GLuint dest_texture_0,
    GLuint dest_texture_1,
    const gfx::Rect& output_rect) {
  if (output_rect.IsEmpty())
    return;  // No work to do.

  // Make a recursive call to the "input" ScalerStage to produce an intermediate
  // texture for this stage to source from. Adjust src_* variables to use the
  // intermediate texture as input.
  //
  // If there is no input stage, simply modify |src_rect| to account for the
  // overall |src_offset| and Y-flip.
  gfx::RectF src_rect = ToSourceRect(output_rect);
  if (input_stage_) {
    const gfx::Rect input_rect = ToInputRect(src_rect);
    EnsureIntermediateTextureDefined(input_rect.size());
    input_stage_->ScaleToMultipleOutputs(src_texture, src_texture_size,
                                         src_offset, intermediate_texture_, 0,
                                         input_rect);
    src_texture = intermediate_texture_;
    src_texture_size = intermediate_texture_size_;
    DCHECK(!is_flipped_source_);
    src_rect -= input_rect.OffsetFromOrigin();
  } else {
    if (is_flipped_source_) {
      src_rect.set_x(src_rect.x() + src_offset.x());
      src_rect.set_y(src_texture_size.height() - src_rect.bottom() -
                     src_offset.y());
    } else {
      src_rect += src_offset;
    }
  }

  // Attach the output texture(s) to the framebuffer.
  if (!dest_framebuffer_) {
    gl_->GenFramebuffers(1, &dest_framebuffer_);
  }
  gl_->BindFramebuffer(GL_FRAMEBUFFER, dest_framebuffer_);
  gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                            dest_texture_0, 0);
  if (dest_texture_1 > 0) {
    gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + 1,
                              GL_TEXTURE_2D, dest_texture_1, 0);
  }

  // Bind to the source texture and set the texture sampler to use bilinear
  // filtering and clamp-to-edge, as required by all shader programs.
  //
  // It would be better to stash the existing parameter values, and restore them
  // back later. However, glGetTexParameteriv() currently requires a blocking
  // call to the GPU service, which is extremely costly performance-wise.
  gl_->BindTexture(GL_TEXTURE_2D, src_texture);
  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  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);

  // Prepare the shader program for drawing.
  DCHECK(program_);
  program_->UseProgram(src_texture_size, src_rect, output_rect.size(),
                       primary_axis_, flip_output_);

  // Execute the draw.
  gl_->Viewport(0, 0, output_rect.width(), output_rect.height());
  const GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT0 + 1};
  if (dest_texture_1 > 0) {
    gl_->DrawBuffersEXT(2, buffers);
  }
  gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  if (dest_texture_1 > 0) {
    // Set the draw buffers back, to not disrupt external operations.
    gl_->DrawBuffersEXT(1, buffers);
  }

  gl_->BindTexture(GL_TEXTURE_2D, 0);
  gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);
}

gfx::RectF GLScaler::ScalerStage::ToSourceRect(
    const gfx::Rect& output_rect) const {
  return gfx::ScaleRect(gfx::RectF(output_rect),
                        float(scale_from_.x()) / scale_to_.x(),
                        float(scale_from_.y()) / scale_to_.y());
}

gfx::Rect GLScaler::ScalerStage::ToInputRect(gfx::RectF source_rect) const {
  int overscan_x = 0;
  int overscan_y = 0;
  switch (shader_) {
    case Shader::BILINEAR:
    case Shader::BILINEAR2:
    case Shader::BILINEAR3:
    case Shader::BILINEAR4: {
      // These shaders sample 1 or more points along the primary axis, and only
      // 1 point in the other direction, in order to produce each output pixel.
      // The amount of overscan is always 0 or 1 pixel along the primary axis,
      // and this can be determined by looking at the upper-left-most source
      // texture sampling point: If this point is to the left of the middle of
      // the upper-left-most source pixel, the texture sampler will also read
      // the pixel to the left of that (for linear interpolation). Similar
      // behavior can occur towards the right, upwards, and downwards at the
      // source boundaries.
      int threshold;
      switch (shader_) {
        default:
          threshold = 1;
          break;
        case Shader::BILINEAR2:
          threshold = 2;
          break;
        case Shader::BILINEAR3:
          threshold = 3;
          break;
        case Shader::BILINEAR4:
          threshold = 4;
          break;
      }
      switch (primary_axis_) {
        case HORIZONTAL:
          if (scale_from_.x() < threshold * scale_to_.x()) {
            overscan_x = 1;
          }
          if (scale_from_.y() < scale_to_.y()) {
            overscan_y = 1;
          }
          break;
        case VERTICAL:
          if (scale_from_.x() < scale_to_.x()) {
            overscan_x = 1;
          }
          if (scale_from_.y() < threshold * scale_to_.y()) {
            overscan_y = 1;
          }
          break;
      }
      break;
    }

    case Shader::BILINEAR2X2:
      // This shader samples 2 points along both axes, and the overscan is 0 or
      // 1 pixel in both directions (same explanation as for the other BILINEAR
      // shaders).
      if (scale_from_.x() < 2 * scale_to_.x()) {
        overscan_x = 1;
      }
      if (scale_from_.y() < 2 * scale_to_.y()) {
        overscan_y = 1;
      }
      break;

    case Shader::BICUBIC_UPSCALE:
      // For each output pixel, this shader always reads 2 pixels about the
      // source position in one dimension, and has no overscan in the other
      // dimension.
      if (scale_from_.x() < scale_to_.x()) {
        DCHECK_EQ(HORIZONTAL, primary_axis_);
        overscan_x = 2;
      } else if (scale_from_.y() < scale_to_.y()) {
        DCHECK_EQ(VERTICAL, primary_axis_);
        overscan_y = 2;
      } else if (scale_from_ == scale_to_) {
        // Special case: When not scaling, the math in the shader will resolve
        // to just outputting the value for a single source pixel. The shader
        // will sample surrounding pixels, but then apply a zero weight to them
        // during convolution. Thus, there is effectively no overscan.
        NOTREACHED();  // This is a crazy-expensive way to do a 1:1 copy!
      } else {
        NOTREACHED();  // Downscaling is meaningless.
      }
      break;

    case Shader::BICUBIC_HALF_1D: {
      // For each output pixel, this shader always reads 4 pixels about the
      // source position in one dimension, and has no overscan in the other
      // dimension. However, since the source position always has a distance
      // >= 1 inside the "logical" bounds, there can never be more than 3 pixels
      // of overscan.
      if (scale_from_.x() == 2 * scale_to_.x()) {
        DCHECK_EQ(HORIZONTAL, primary_axis_);
        overscan_x = 3;
      } else if (scale_from_.y() == 2 * scale_to_.y()) {
        DCHECK_EQ(VERTICAL, primary_axis_);
        overscan_y = 3;
      } else {
        // Anything but a half-downscale in one dimension is meaningless.
        NOTREACHED();
      }
      break;
    }

    case Shader::PLANAR_CHANNEL_0:
    case Shader::PLANAR_CHANNEL_1:
    case Shader::PLANAR_CHANNEL_2:
    case Shader::PLANAR_CHANNEL_3:
    case Shader::I422_NV61_MRT:
      // All of these sample 4x1 source pixels to produce each output "pixel."
      // There is no overscan. They can also be combined with a bilinear
      // downscale, but not an upscale.
      DCHECK_GE(scale_from_.x(), 4 * scale_to_.x());
      DCHECK_EQ(HORIZONTAL, primary_axis_);
      break;

    case Shader::DEINTERLEAVE_PAIRWISE_MRT:
      // This shader samples 2x1 source pixels to produce each output "pixel."
      // There is no overscan. It can also be combined with a bilinear
      // downscale, but not an upscale.
      DCHECK_GE(scale_from_.x(), 2 * scale_to_.x());
      DCHECK_EQ(HORIZONTAL, primary_axis_);
      break;
  }

  source_rect.Inset(-overscan_x, -overscan_y);
  return gfx::ToEnclosingRect(source_rect);
}

void GLScaler::ScalerStage::EnsureIntermediateTextureDefined(
    const gfx::Size& size) {
  // Reallocate a new texture, if needed.
  if (!intermediate_texture_) {
    gl_->GenTextures(1, &intermediate_texture_);
  }
  if (intermediate_texture_size_ != size) {
    gl_->BindTexture(GL_TEXTURE_2D, intermediate_texture_);
    // Note: Not setting the filter or wrap parameters on the texture here
    // because that will be done in ScaleToMultipleOutputs() anyway.
    gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
                    GL_RGBA, program_->texture_type(), nullptr);
    intermediate_texture_size_ = size;
  }
}

std::ostream& operator<<(std::ostream& out, const GLScaler& scaler) {
  if (!scaler.chain_) {
    return (out << "[GLScaler NOT configured]");
  }

  out << "Output";
  const GLScaler::ScalerStage* const final_stage = scaler.chain_.get();
  for (auto* stage = final_stage; stage; stage = stage->input_stage()) {
    out << u8" ← {" << GLScaler::GetShaderName(stage->shader());
    if (stage->shader_program()) {
      switch (stage->shader_program()->texture_type()) {
        case GL_FLOAT:
          out << "/highp";
          break;
        case GL_HALF_FLOAT_OES:
          out << "/mediump";
          break;
        default:
          out << "/lowp";
          break;
      }
    }
    if (stage->flip_output()) {
      out << "+flip_y";
    }
    if (stage->scale_from() == stage->scale_to()) {
      out << " copy";
    } else {
      out << ' ' << stage->scale_from().ToString() << " to "
          << stage->scale_to().ToString();
    }
    if (!stage->input_stage() &&
        scaler.params_.source_color_space != scaler.scaling_color_space_) {
      out << ", with color x-form "
          << scaler.params_.source_color_space.ToString() << " to "
          << scaler.scaling_color_space_.ToString();
    }
    if (stage == final_stage) {
      if (scaler.params_.output_color_space != scaler.scaling_color_space_) {
        out << ", with color x-form to "
            << scaler.params_.output_color_space.ToString();
      }
      for (int i = 0; i < 2; ++i) {
        if (scaler.params_.swizzle[i] != GL_RGBA) {
          out << ", with swizzle(" << i << ')';
        }
      }
    }
    out << '}';
  }
  out << u8" ← Source";
  return out;
}

}  // namespace viz
