910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/client/gl_helper.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <utility>

#include "base/atomicops.h"
#include "base/bits.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/containers/queue.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/lazy_instance.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_span.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/gl_helper_scaling.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"

namespace gpu {

using gles2::GLES2Interface;

namespace {

class ScopedFlush {
 public:
  explicit ScopedFlush(gles2::GLES2Interface* gl) : gl_(gl) {}

  ScopedFlush(const ScopedFlush&) = delete;
  ScopedFlush& operator=(const ScopedFlush&) = delete;

  ~ScopedFlush() { gl_->Flush(); }

 private:
  raw_ptr<gles2::GLES2Interface> gl_;
};

// Helper class for allocating and holding an RGBA texture of a given
// size.
class TextureHolder {
 public:
  TextureHolder(GLES2Interface* gl, gfx::Size size)
      : texture_(gl), size_(size) {
    ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(gl, texture_);
    gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
                   GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  }

  TextureHolder(const TextureHolder&) = delete;
  TextureHolder& operator=(const TextureHolder&) = delete;

  GLuint texture() const { return texture_.id(); }
  gfx::Size size() const { return size_; }

 private:
  ScopedTexture texture_;
  gfx::Size size_;
};

class I420ConverterImpl : public I420Converter {
 public:
  I420ConverterImpl(GLES2Interface* gl,
                    GLHelperScaling* scaler_impl,
                    bool flipped_source,
                    bool flip_output,
                    bool swizzle,
                    bool use_mrt);

  I420ConverterImpl(const I420ConverterImpl&) = delete;
  I420ConverterImpl& operator=(const I420ConverterImpl&) = delete;

  ~I420ConverterImpl() override;

  void Convert(GLuint src_texture,
               const gfx::Size& src_texture_size,
               const gfx::Vector2dF& src_offset,
               GLHelper::ScalerInterface* optional_scaler,
               const gfx::Rect& output_rect,
               GLuint y_plane_texture,
               GLuint u_plane_texture,
               GLuint v_plane_texture) override;

  bool IsSamplingFlippedSource() const override;
  bool IsFlippingOutput() const override;
  GLenum GetReadbackFormat() const override;

 protected:
  // Returns true if the planerizer should use the faster, two-pass shaders
  // to generate the YUV planar outputs. If false, the source will be
  // scanned three times, once for each Y/U/V plane.
  bool use_mrt() const { return !v_planerizer_; }

  // Reallocates the intermediate and plane textures, if needed.
  void EnsureTexturesSizedFor(const gfx::Size& scaler_output_size,
                              const gfx::Size& y_texture_size,
                              const gfx::Size& chroma_texture_size,
                              GLuint y_plane_texture,
                              GLuint u_plane_texture,
                              GLuint v_plane_texture);

  const raw_ptr<GLES2Interface> gl_;

 private:
  // These generate the Y/U/V planes. If MRT is being used, |y_planerizer_|
  // generates the Y and interim UV plane, |u_planerizer_| generates the
  // final U and V planes, and |v_planerizer_| is unused. If MRT is not
  // being used, each of these generates only one of the Y/U/V planes.
  const std::unique_ptr<GLHelper::ScalerInterface> y_planerizer_;
  const std::unique_ptr<GLHelper::ScalerInterface> u_planerizer_;
  const std::unique_ptr<GLHelper::ScalerInterface> v_planerizer_;

  // Intermediate texture, holding the scaler's output.
  std::optional<TextureHolder> intermediate_;

  // Intermediate texture, holding the UV interim output (if the MRT shader
  // is being used).
  std::optional<ScopedTexture> uv_;
};

}  // namespace

// Implements texture consumption/readback and encapsulates
// the data needed for it.
class GLHelper::CopyTextureToImpl final {
 public:
  CopyTextureToImpl(GLES2Interface* gl,
                    ContextSupport* context_support,
                    GLHelper* helper)
      : gl_(gl),
        context_support_(context_support),
        helper_(helper),
        flush_(gl) {}
  ~CopyTextureToImpl() { CancelRequests(); }

  void ReadbackTextureAsync(GLuint texture,
                            GLenum texture_target,
                            const gfx::Point& src_starting_point,
                            const gfx::Size& dst_size,
                            base::span<uint8_t> out,
                            size_t row_stride_bytes,
                            bool flip_y,
                            GLenum format,
                            base::OnceCallback<void(bool)> callback);

  // Reads back bytes from the currently bound frame buffer.
  // Note that dst_size is specified in bytes, not pixels.
  void ReadbackAsync(const gfx::Point& src_starting_point,
                     const gfx::Size& dst_size,
                     size_t bytes_per_row,     // generally dst_size.width() * 4
                     size_t row_stride_bytes,  // generally dst_size.width() * 4
                     base::span<uint8_t> out,
                     GLenum format,
                     GLenum type,
                     size_t bytes_per_pixel,
                     bool flip_y,
                     base::OnceCallback<void(bool)> callback);

  void ReadbackPlane(const gfx::Size& texture_size,
                     int row_stride_bytes,
                     base::span<uint8_t> data,
                     int size_shift,
                     const gfx::Rect& paste_rect,
                     ReadbackSwizzle swizzle,
                     base::OnceCallback<void(bool)> callback);

  std::unique_ptr<ReadbackYUVInterface> CreateReadbackPipelineYUV(
      bool flip_vertically,
      bool use_mrt);

 private:
  // Represents the state of a single readback request.
  // The main thread can cancel the request, before it's handled by the helper
  // thread, by resetting the texture and pixels fields. Alternatively, the
  // thread marks that it handles the request by resetting the pixels field
  // (meaning it guarantees that the callback with be called).
  // In either case, the callback must be called exactly once, and the texture
  // must be deleted by the main thread gl.
  struct Request {
    Request(const gfx::Size& size_,
            size_t bytes_per_pixel_,
            size_t bytes_per_row_,
            size_t row_stride_bytes_,
            base::span<uint8_t> pixels_,
            bool flip_y_,
            base::OnceCallback<void(bool)> callback_)
        : size(size_),
          bytes_per_pixel(bytes_per_pixel_),
          bytes_per_row(bytes_per_row_),
          row_stride_bytes(row_stride_bytes_),
          flip_y(flip_y_),
          callback(std::move(callback_)),
          pixels(pixels_) {}

    bool done = false;
    bool result = false;
    gfx::Size size;
    size_t bytes_per_pixel;
    size_t bytes_per_row;
    size_t row_stride_bytes;
    bool flip_y;
    base::OnceCallback<void(bool)> callback;
    base::raw_span<uint8_t> pixels;
    GLuint buffer = 0;
    GLuint query = 0;
  };

  // We must take care to call the callbacks last, as they may
  // end up destroying the gl_helper and make *this invalid.
  // We stick the finished requests in a stack object that calls
  // the callbacks when it goes out of scope.
  class FinishRequestHelper {
   public:
    FinishRequestHelper() {}

    FinishRequestHelper(const FinishRequestHelper&) = delete;
    FinishRequestHelper& operator=(const FinishRequestHelper&) = delete;

    ~FinishRequestHelper() {
      while (!requests_.empty()) {
        Request* request = requests_.front();
        requests_.pop();
        std::move(request->callback).Run(request->result);
        delete request;
      }
    }
    void Add(Request* r) { requests_.push(r); }

   private:
    base::queue<raw_ptr<Request, CtnExperimental>> requests_;
  };

  // A readback pipeline that also converts the data to YUV before
  // reading it back.
  class ReadbackYUVImpl : public I420ConverterImpl,
                          public ReadbackYUVInterface {
   public:
    ReadbackYUVImpl(GLES2Interface* gl,
                    CopyTextureToImpl* copy_impl,
                    GLHelperScaling* scaler_impl,
                    bool flip_vertically,
                    ReadbackSwizzle swizzle,
                    bool use_mrt);

    ReadbackYUVImpl(const ReadbackYUVImpl&) = delete;
    ReadbackYUVImpl& operator=(const ReadbackYUVImpl&) = delete;

    ~ReadbackYUVImpl() override;

    void SetScaler(std::unique_ptr<GLHelper::ScalerInterface> scaler) override;

    GLHelper::ScalerInterface* scaler() const override;

    bool IsFlippingOutput() const override;

    void ReadbackYUV(GLuint texture,
                     const gfx::Size& src_texture_size,
                     const gfx::Rect& output_rect,
                     int y_plane_row_stride_bytes,
                     base::span<uint8_t> y_plane_data,
                     int u_plane_row_stride_bytes,
                     base::span<uint8_t> u_plane_data,
                     int v_plane_row_stride_bytes,
                     base::span<uint8_t> v_plane_data,
                     const gfx::Point& paste_location,
                     base::OnceCallback<void(bool)> callback) override;

   private:
    raw_ptr<GLES2Interface> gl_;
    raw_ptr<CopyTextureToImpl> copy_impl_;
    ReadbackSwizzle swizzle_;

    // May be null if no scaling is required. This can be changed between
    // calls to ReadbackYUV().
    std::unique_ptr<GLHelper::ScalerInterface> scaler_;

    // These are the output textures for each Y/U/V plane.
    ScopedTexture y_;
    ScopedTexture u_;
    ScopedTexture v_;

    // Framebuffers used by ReadbackPlane(). They are cached here so as to not
    // be re-allocated for every frame of video.
    ScopedFramebuffer y_readback_framebuffer_;
    ScopedFramebuffer u_readback_framebuffer_;
    ScopedFramebuffer v_readback_framebuffer_;
  };

  void ReadbackDone(Request* request);
  void FinishRequest(Request* request,
                     bool result,
                     FinishRequestHelper* helper);
  void CancelRequests();

  bool IsBGRAReadbackSupported();

  raw_ptr<GLES2Interface> gl_;
  raw_ptr<ContextSupport> context_support_;
  raw_ptr<GLHelper> helper_;

  // A scoped flush that will ensure all resource deletions are flushed when
  // this object is destroyed. Must be declared before other Scoped* fields.
  ScopedFlush flush_;

  base::queue<raw_ptr<Request, CtnExperimental>> request_queue_;

  // Lazily set by IsBGRAReadbackSupported().
  enum {
    BGRA_SUPPORT_UNKNOWN,
    BGRA_SUPPORTED,
    BGRA_NOT_SUPPORTED
  } bgra_support_ = BGRA_SUPPORT_UNKNOWN;

  // A run-once test is lazy executed in CreateReadbackPipelineYUV(), to
  // determine whether the GL_BGRA_EXT format is preferred for readback.
  enum {
    BGRA_PREFERENCE_UNKNOWN,
    BGRA_PREFERRED,
    BGRA_NOT_PREFERRED
  } bgra_preference_ = BGRA_PREFERENCE_UNKNOWN;

  base::WeakPtrFactory<CopyTextureToImpl> weak_ptr_factory_{this};
};

std::unique_ptr<GLHelper::ScalerInterface> GLHelper::CreateScaler(
    ScalerQuality quality,
    const gfx::Vector2d& scale_from,
    const gfx::Vector2d& scale_to,
    bool flipped_source,
    bool flip_output,
    bool swizzle) {
  InitScalerImpl();
  return scaler_impl_->CreateScaler(quality, scale_from, scale_to,
                                    flipped_source, flip_output, swizzle);
}

void GLHelper::CopyTextureToImpl::ReadbackAsync(
    const gfx::Point& src_starting_point,
    const gfx::Size& dst_size,
    size_t bytes_per_row,
    size_t row_stride_bytes,
    base::span<uint8_t> out,
    GLenum format,
    GLenum type,
    size_t bytes_per_pixel,
    bool flip_y,
    base::OnceCallback<void(bool)> callback) {
  TRACE_EVENT0("gpu.capture", "GLHelper::CopyTextureToImpl::ReadbackAsync");
  Request* request =
      new Request(dst_size, bytes_per_pixel, bytes_per_row, row_stride_bytes,
                  out, flip_y, std::move(callback));
  request_queue_.push(request);
  request->buffer = 0u;

  gl_->GenBuffers(1, &request->buffer);
  gl_->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, request->buffer);
  gl_->BufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
                  bytes_per_pixel * dst_size.GetArea(), nullptr,
                  GL_STREAM_READ);

  request->query = 0u;
  gl_->GenQueriesEXT(1, &request->query);
  gl_->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, request->query);
  gl_->ReadPixels(src_starting_point.x(), src_starting_point.y(),
                  dst_size.width(), dst_size.height(), format, type, nullptr);
  gl_->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
  gl_->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
  context_support_->SignalQuery(
      request->query, base::BindOnce(&CopyTextureToImpl::ReadbackDone,
                                     weak_ptr_factory_.GetWeakPtr(), request));
}

void GLHelper::CopyTextureToImpl::ReadbackTextureAsync(
    GLuint texture,
    GLenum texture_target,
    const gfx::Point& src_starting_point,
    const gfx::Size& dst_size,
    base::span<uint8_t> out,
    size_t row_stride_bytes,
    bool flip_y,
    GLenum format,
    base::OnceCallback<void(bool)> callback) {
  constexpr size_t kBytesPerPixel = 4;
  const size_t kBytesPerRow = dst_size.width() * kBytesPerPixel;

  // Note: It's possible the GL implementation supports other readback
  // types. However, as of this writing, no caller of this method will
  // request a different |color_type| (i.e., requiring using some other GL
  // format).
  if (format != GL_RGBA &&
      (format != GL_BGRA_EXT || !IsBGRAReadbackSupported())) {
    std::move(callback).Run(false);
    return;
  }

  ScopedFramebuffer dst_framebuffer(gl_);
  ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(gl_,
                                                             dst_framebuffer);
  gl_->BindTexture(texture_target, texture);
  gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                            texture_target, texture, 0);
  ReadbackAsync(src_starting_point, dst_size, kBytesPerRow, row_stride_bytes,
                out, format, GL_UNSIGNED_BYTE, kBytesPerPixel, flip_y,
                std::move(callback));
  gl_->BindTexture(texture_target, 0);
}

void GLHelper::CopyTextureToImpl::ReadbackDone(Request* finished_request) {
  TRACE_EVENT0("gpu.capture",
               "GLHelper::CopyTextureToImpl::CheckReadbackFramebufferComplete");
  finished_request->done = true;

  FinishRequestHelper finish_request_helper;

  // We process transfer requests in the order they were received, regardless
  // of the order we get the callbacks in.
  while (!request_queue_.empty()) {
    Request* request = request_queue_.front();
    if (!request->done) {
      break;
    }

    bool result = false;
    if (request->buffer != 0) {
      gl_->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, request->buffer);
      void* mapped_ptr = gl_->MapBufferCHROMIUM(
          GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, GL_READ_ONLY);
      if (mapped_ptr) {
        result = true;
        int dst_stride = base::saturated_cast<int>(request->row_stride_bytes);
        const size_t src_stride =
            request->bytes_per_pixel * request->size.width();
        const size_t src_buffer_size = src_stride * request->size.height();
        base::span<const uint8_t> UNSAFE_TODO(
            src_span(static_cast<const uint8_t*>(mapped_ptr), src_buffer_size));
        base::span<uint8_t> dst_span = request->pixels;

        size_t src_offset = 0;
        size_t dst_offset = 0;
        size_t bytes_to_copy =
            std::min(request->row_stride_bytes, request->bytes_per_row);
        if (request->flip_y && request->size.height() > 1) {
          dst_offset = dst_stride * (request->size.height() - 1);
          dst_stride = -dst_stride;
        }
        // We need to use `RelaxedAtomicWriteMemcpy` because we might be writing
        // into memory observed by JS at the same time.
        for (int y = 0; y < request->size.height(); ++y) {
          auto src_row = src_span.subspan(src_offset, bytes_to_copy);
          auto dst_row = dst_span.subspan(dst_offset, bytes_to_copy);
          base::subtle::RelaxedAtomicWriteMemcpy(dst_row, src_row);
          src_offset += src_stride;
          dst_offset += dst_stride;
        }
        gl_->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
      }
      gl_->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }
    FinishRequest(request, result, &finish_request_helper);
  }
}

void GLHelper::CopyTextureToImpl::FinishRequest(
    Request* request,
    bool result,
    FinishRequestHelper* finish_request_helper) {
  TRACE_EVENT0("gpu.capture", "GLHelper::CopyTextureToImpl::FinishRequest");
  DCHECK(request_queue_.front() == request);
  request_queue_.pop();
  request->result = result;
  ScopedFlush flush(gl_);
  if (request->query != 0) {
    gl_->DeleteQueriesEXT(1, &request->query);
    request->query = 0;
  }
  if (request->buffer != 0) {
    gl_->DeleteBuffers(1, &request->buffer);
    request->buffer = 0;
  }
  finish_request_helper->Add(request);
}

void GLHelper::CopyTextureToImpl::CancelRequests() {
  FinishRequestHelper finish_request_helper;
  while (!request_queue_.empty()) {
    Request* request = request_queue_.front();
    FinishRequest(request, false, &finish_request_helper);
  }
}

bool GLHelper::CopyTextureToImpl::IsBGRAReadbackSupported() {
  if (bgra_support_ == BGRA_SUPPORT_UNKNOWN) {
    bgra_support_ = BGRA_NOT_SUPPORTED;
    if (auto* extensions = gl_->GetString(GL_EXTENSIONS)) {
      const std::string extensions_string =
          " " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
      if (base::Contains(extensions_string, " GL_EXT_read_format_bgra ")) {
        bgra_support_ = BGRA_SUPPORTED;
      }
    }
  }

  return bgra_support_ == BGRA_SUPPORTED;
}

GLHelper::GLHelper(GLES2Interface* gl, ContextSupport* context_support)
    : gl_(gl), context_support_(context_support) {}

GLHelper::~GLHelper() {}

void GLHelper::ReadbackTextureAsync(GLuint texture,
                                    GLenum texture_target,
                                    const gfx::Point& src_starting_point,
                                    const gfx::Size& dst_size,
                                    base::span<uint8_t> out,
                                    size_t row_stride_bytes,
                                    bool flip_y,
                                    GLenum format,
                                    base::OnceCallback<void(bool)> callback) {
  InitCopyTextToImpl();
  copy_texture_to_impl_->ReadbackTextureAsync(
      texture, texture_target, src_starting_point, dst_size, out,
      row_stride_bytes, flip_y, format, std::move(callback));
}

void GLHelper::InitCopyTextToImpl() {
  // Lazily initialize |copy_texture_to_impl_|
  if (!copy_texture_to_impl_)
    copy_texture_to_impl_ =
        std::make_unique<CopyTextureToImpl>(gl_, context_support_, this);
}

void GLHelper::InitScalerImpl() {
  // Lazily initialize |scaler_impl_|
  if (!scaler_impl_)
    scaler_impl_ = std::make_unique<GLHelperScaling>(gl_, this);
}

GLint GLHelper::MaxDrawBuffers() {
  if (max_draw_buffers_ < 0) {
    max_draw_buffers_ = 0;
    const GLubyte* extensions = gl_->GetString(GL_EXTENSIONS);
    if (extensions) {
      const std::string extensions_string =
          " " + std::string(reinterpret_cast<const char*>(extensions)) + " ";
      if (base::Contains(extensions_string, " GL_EXT_draw_buffers ")) {
        gl_->GetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max_draw_buffers_);
        DCHECK_GE(max_draw_buffers_, 0);
      }
    }
  }

  return max_draw_buffers_;
}

void GLHelper::CopyTextureToImpl::ReadbackPlane(
    const gfx::Size& texture_size,
    int row_stride_bytes,
    base::span<uint8_t> data,
    int size_shift,
    const gfx::Rect& paste_rect,
    ReadbackSwizzle swizzle,
    base::OnceCallback<void(bool)> callback) {
  const size_t offset = row_stride_bytes * (paste_rect.y() >> size_shift) +
                        (paste_rect.x() >> size_shift);

  // We already flipped rows vertically, converting single RGB plane to
  // multiple YUV planes.
  const bool kFlipY = false;
  size_t bytes_per_row = paste_rect.width() >> size_shift;
  ReadbackAsync(gfx::Point(), texture_size, bytes_per_row, row_stride_bytes,
                data.subspan(offset),
                (swizzle == kSwizzleBGRA) ? GL_BGRA_EXT : GL_RGBA,
                GL_UNSIGNED_BYTE, 4, kFlipY, std::move(callback));
}

I420Converter::I420Converter() = default;
I420Converter::~I420Converter() = default;

// static
gfx::Size I420Converter::GetYPlaneTextureSize(const gfx::Size& output_size) {
  return gfx::Size((output_size.width() + 3) / 4, output_size.height());
}

// static
gfx::Size I420Converter::GetChromaPlaneTextureSize(
    const gfx::Size& output_size) {
  return gfx::Size((output_size.width() + 7) / 8,
                   (output_size.height() + 1) / 2);
}

namespace {

I420ConverterImpl::I420ConverterImpl(GLES2Interface* gl,
                                     GLHelperScaling* scaler_impl,
                                     bool flipped_source,
                                     bool flip_output,
                                     bool swizzle,
                                     bool use_mrt)
    : gl_(gl),
      y_planerizer_(
          use_mrt ? scaler_impl->CreateI420MrtPass1Planerizer(flipped_source,
                                                              flip_output,
                                                              swizzle)
                  : scaler_impl->CreateI420Planerizer(0,
                                                      flipped_source,
                                                      flip_output,
                                                      swizzle)),
      u_planerizer_(use_mrt ? scaler_impl->CreateI420MrtPass2Planerizer(swizzle)
                            : scaler_impl->CreateI420Planerizer(1,
                                                                flipped_source,
                                                                flip_output,
                                                                swizzle)),
      v_planerizer_(use_mrt ? nullptr
                            : scaler_impl->CreateI420Planerizer(2,
                                                                flipped_source,
                                                                flip_output,
                                                                swizzle)) {}

I420ConverterImpl::~I420ConverterImpl() = default;

void I420ConverterImpl::Convert(GLuint src_texture,
                                const gfx::Size& src_texture_size,
                                const gfx::Vector2dF& src_offset,
                                GLHelper::ScalerInterface* optional_scaler,
                                const gfx::Rect& output_rect,
                                GLuint y_plane_texture,
                                GLuint u_plane_texture,
                                GLuint v_plane_texture) {
  const gfx::Size scaler_output_size =
      optional_scaler ? output_rect.size() : gfx::Size();
  const gfx::Size y_texture_size = GetYPlaneTextureSize(output_rect.size());
  const gfx::Size chroma_texture_size =
      GetChromaPlaneTextureSize(output_rect.size());
  EnsureTexturesSizedFor(scaler_output_size, y_texture_size,
                         chroma_texture_size, y_plane_texture, u_plane_texture,
                         v_plane_texture);

  // Scale first, if needed.
  if (optional_scaler) {
    // The scaler should not be configured to do any swizzling.
    DCHECK_EQ(optional_scaler->GetReadbackFormat(),
              static_cast<GLenum>(GL_RGBA));
    optional_scaler->Scale(src_texture, src_texture_size, src_offset,
                           intermediate_->texture(), output_rect);
  }

  // Convert the intermediate (or source) texture into Y, U and V planes.
  const GLuint texture =
      optional_scaler ? intermediate_->texture() : src_texture;
  const gfx::Size texture_size =
      optional_scaler ? intermediate_->size() : src_texture_size;
  const gfx::Vector2dF offset = optional_scaler ? gfx::Vector2dF() : src_offset;
  if (use_mrt()) {
    y_planerizer_->ScaleToMultipleOutputs(texture, texture_size, offset,
                                          y_plane_texture, uv_->id(),
                                          gfx::Rect(y_texture_size));
    u_planerizer_->ScaleToMultipleOutputs(
        uv_->id(), y_texture_size, gfx::Vector2dF(), u_plane_texture,
        v_plane_texture, gfx::Rect(chroma_texture_size));
  } else {
    y_planerizer_->Scale(texture, texture_size, offset, y_plane_texture,
                         gfx::Rect(y_texture_size));
    u_planerizer_->Scale(texture, texture_size, offset, u_plane_texture,
                         gfx::Rect(chroma_texture_size));
    v_planerizer_->Scale(texture, texture_size, offset, v_plane_texture,
                         gfx::Rect(chroma_texture_size));
  }
}

bool I420ConverterImpl::IsSamplingFlippedSource() const {
  return y_planerizer_->IsSamplingFlippedSource();
}

bool I420ConverterImpl::IsFlippingOutput() const {
  return y_planerizer_->IsFlippingOutput();
}

GLenum I420ConverterImpl::GetReadbackFormat() const {
  return y_planerizer_->GetReadbackFormat();
}

void I420ConverterImpl::EnsureTexturesSizedFor(
    const gfx::Size& scaler_output_size,
    const gfx::Size& y_texture_size,
    const gfx::Size& chroma_texture_size,
    GLuint y_plane_texture,
    GLuint u_plane_texture,
    GLuint v_plane_texture) {
  // Reallocate the intermediate texture, if needed.
  if (!scaler_output_size.IsEmpty()) {
    if (!intermediate_ || intermediate_->size() != scaler_output_size)
      intermediate_.emplace(gl_, scaler_output_size);
  } else {
    intermediate_ = std::nullopt;
  }

  // Size the interim UV plane and the three output planes.
  const auto SetRGBATextureSize = [this](const gfx::Size& size) {
    gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
                    GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  };
  if (use_mrt()) {
    uv_.emplace(gl_);
    gl_->BindTexture(GL_TEXTURE_2D, uv_->id());
    SetRGBATextureSize(y_texture_size);
  }
  gl_->BindTexture(GL_TEXTURE_2D, y_plane_texture);
  SetRGBATextureSize(y_texture_size);
  gl_->BindTexture(GL_TEXTURE_2D, u_plane_texture);
  SetRGBATextureSize(chroma_texture_size);
  gl_->BindTexture(GL_TEXTURE_2D, v_plane_texture);
  SetRGBATextureSize(chroma_texture_size);
}

}  // namespace

GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUVImpl(
    GLES2Interface* gl,
    CopyTextureToImpl* copy_impl,
    GLHelperScaling* scaler_impl,
    bool flip_vertically,
    ReadbackSwizzle swizzle,
    bool use_mrt)
    : I420ConverterImpl(gl,
                        scaler_impl,
                        false,
                        flip_vertically,
                        swizzle == kSwizzleBGRA,
                        use_mrt),
      gl_(gl),
      copy_impl_(copy_impl),
      swizzle_(swizzle),
      y_(gl_),
      u_(gl_),
      v_(gl_),
      y_readback_framebuffer_(gl_),
      u_readback_framebuffer_(gl_),
      v_readback_framebuffer_(gl_) {}

GLHelper::CopyTextureToImpl::ReadbackYUVImpl::~ReadbackYUVImpl() = default;

void GLHelper::CopyTextureToImpl::ReadbackYUVImpl::SetScaler(
    std::unique_ptr<GLHelper::ScalerInterface> scaler) {
  scaler_ = std::move(scaler);
}

GLHelper::ScalerInterface*
GLHelper::CopyTextureToImpl::ReadbackYUVImpl::scaler() const {
  return scaler_.get();
}

bool GLHelper::CopyTextureToImpl::ReadbackYUVImpl::IsFlippingOutput() const {
  return I420ConverterImpl::IsFlippingOutput();
}

void GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUV(
    GLuint texture,
    const gfx::Size& src_texture_size,
    const gfx::Rect& output_rect,
    int y_plane_row_stride_bytes,
    base::span<uint8_t> y_plane_data,
    int u_plane_row_stride_bytes,
    base::span<uint8_t> u_plane_data,
    int v_plane_row_stride_bytes,
    base::span<uint8_t> v_plane_data,
    const gfx::Point& paste_location,
    base::OnceCallback<void(bool)> callback) {
  DCHECK(!(paste_location.x() & 1));
  DCHECK(!(paste_location.y() & 1));

  I420ConverterImpl::Convert(texture, src_texture_size, gfx::Vector2dF(),
                             scaler_.get(), output_rect, y_, u_, v_);

  // Read back planes, one at a time. Keep the video frame alive while doing
  // the readback.
  const gfx::Rect paste_rect(paste_location, output_rect.size());
  const auto SetUpAndBindFramebuffer = [this](GLuint framebuffer,
                                              GLuint texture) {
    gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_TEXTURE_2D, texture, 0);
  };
  SetUpAndBindFramebuffer(y_readback_framebuffer_, y_);
  copy_impl_->ReadbackPlane(GetYPlaneTextureSize(output_rect.size()),
                            y_plane_row_stride_bytes, y_plane_data, 0,
                            paste_rect, swizzle_, base::DoNothing());
  SetUpAndBindFramebuffer(u_readback_framebuffer_, u_);
  const gfx::Size chroma_texture_size =
      GetChromaPlaneTextureSize(output_rect.size());
  copy_impl_->ReadbackPlane(chroma_texture_size, u_plane_row_stride_bytes,
                            u_plane_data, 1, paste_rect, swizzle_,
                            base::DoNothing());
  SetUpAndBindFramebuffer(v_readback_framebuffer_, v_);
  copy_impl_->ReadbackPlane(chroma_texture_size, v_plane_row_stride_bytes,
                            v_plane_data, 1, paste_rect, swizzle_,
                            std::move(callback));
  gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);
}

std::unique_ptr<I420Converter> GLHelper::CreateI420Converter(
    bool flipped_source,
    bool flip_output,
    bool swizzle,
    bool use_mrt) {
  InitCopyTextToImpl();
  InitScalerImpl();
  return std::make_unique<I420ConverterImpl>(
      gl_, scaler_impl_.get(), flipped_source, flip_output, swizzle,
      use_mrt && (MaxDrawBuffers() >= 2));
}

std::unique_ptr<ReadbackYUVInterface>
GLHelper::CopyTextureToImpl::CreateReadbackPipelineYUV(bool flip_vertically,
                                                       bool use_mrt) {
  helper_->InitScalerImpl();

  if (bgra_preference_ == BGRA_PREFERENCE_UNKNOWN) {
    if (IsBGRAReadbackSupported()) {
      // Test whether GL_BRGA_EXT is preferred for readback by creating a test
      // texture, binding it to a framebuffer as a color attachment, and then
      // querying the implementation for the framebuffer's readback format.
      constexpr int kTestSize = 64;
      GLuint texture = 0;
      gl_->GenTextures(1, &texture);
      gl_->BindTexture(GL_TEXTURE_2D, 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);
      gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTestSize, kTestSize, 0,
                      GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
      GLuint framebuffer = 0;
      gl_->GenFramebuffers(1, &framebuffer);
      gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
      gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                GL_TEXTURE_2D, texture, 0);
      GLint readback_format = 0;
      GLint readback_type = 0;
      gl_->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readback_format);
      gl_->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readback_type);
      if (readback_format == GL_BGRA_EXT && readback_type == GL_UNSIGNED_BYTE) {
        bgra_preference_ = BGRA_PREFERRED;
      } else {
        bgra_preference_ = BGRA_NOT_PREFERRED;
      }
      if (framebuffer != 0)
        gl_->DeleteFramebuffers(1, &framebuffer);
      if (texture != 0)
        gl_->DeleteTextures(1, &texture);
    } else {
      bgra_preference_ = BGRA_NOT_PREFERRED;
    }
  }

  const ReadbackSwizzle swizzle =
      (bgra_preference_ == BGRA_PREFERRED) ? kSwizzleBGRA : kSwizzleNone;
  return std::make_unique<ReadbackYUVImpl>(
      gl_, this, helper_->scaler_impl_.get(), flip_vertically, swizzle,
      use_mrt && (helper_->MaxDrawBuffers() >= 2));
}

std::unique_ptr<ReadbackYUVInterface> GLHelper::CreateReadbackPipelineYUV(
    bool flip_vertically,
    bool use_mrt) {
  InitCopyTextToImpl();
  return copy_texture_to_impl_->CreateReadbackPipelineYUV(flip_vertically,
                                                          use_mrt);
}

ReadbackYUVInterface* GLHelper::GetReadbackPipelineYUV(
    bool vertically_flip_texture) {
  ReadbackYUVInterface* yuv_reader = nullptr;
  if (vertically_flip_texture) {
    if (!shared_readback_yuv_flip_) {
      shared_readback_yuv_flip_ = CreateReadbackPipelineYUV(
          vertically_flip_texture, true /* use_mrt */);
    }
    yuv_reader = shared_readback_yuv_flip_.get();
  } else {
    if (!shared_readback_yuv_noflip_) {
      shared_readback_yuv_noflip_ = CreateReadbackPipelineYUV(
          vertically_flip_texture, true /* use_mrt */);
    }
    yuv_reader = shared_readback_yuv_noflip_.get();
  }
  DCHECK(!yuv_reader->scaler());
  return yuv_reader;
}

}  // namespace gpu