910e62b5创建于 1月15日历史提交
// Copyright 2023 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/service/shared_image/gl_texture_holder.h"

#include "base/bits.h"
#include "build/build_config.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/gl_repack_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_gl_utils.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "third_party/skia/include/gpu/ganesh/GrContextThreadSafeProxy.h"
#include "third_party/skia/include/private/chromium/GrPromiseImageTexture.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_version_info.h"
#include "ui/gl/progress_reporter.h"
#include "ui/gl/scoped_binders.h"
#include "ui/gl/scoped_restore_texture.h"

namespace gpu {
namespace {

// This value can't be cached as it may change for different contexts.
bool SupportsUnpackSubimage() {
  return gl::g_current_gl_version->IsAtLeastGLES(3, 0) ||
         gl::g_current_gl_driver->ext.b_GL_EXT_unpack_subimage;
}

// This value can't be cached as it may change for different contexts.
bool SupportsPackSubimage() {
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86_FAMILY)
  // GL_PACK_ROW_LENGTH is broken in the Android emulator. glReadPixels()
  // modifies bytes between the last pixel in a row and the end of the stride
  // for that row.
  return false;
#else
  return gl::g_current_gl_version->IsAtLeastGLES(3, 0);
#endif
}

// Shared memory GMBs tend to use a stride that is 4 bytes aligned. If
// bytes_per_pixel is 1 or 2 and `stride` is already 4 byte aligned then
// use a 4 byte alignment. Otherwise use `bytes_per_pixel` as alignment. This
// can avoid a copy if GL_[UN]PACK_ROW_LENGTH isn't supported, eg. `stride` is
// 12 and `bytes_per_pixel` is 1 but there are only 10 bytes of data per row.
constexpr int ComputeBestAlignment(size_t bytes_per_pixel, size_t stride) {
  if (bytes_per_pixel < 4 && (stride & 0b11) == 0) {
    return 4;
  }

  return bytes_per_pixel;
}

}  // anonymous namespace

// static
// TODO(hitawala): Check GLFormatCaps for format support.
viz::SharedImageFormat GLTextureHolder::GetPlaneFormat(
    viz::SharedImageFormat format,
    int plane_index) {
  DCHECK(format.IsValidPlaneIndex(plane_index));
  if (format.is_single_plane()) {
    return format;
  }

  int num_channels = format.NumChannelsInPlane(plane_index);
  DCHECK_LE(num_channels, 2);
  switch (format.channel_format()) {
    case viz::SharedImageFormat::ChannelFormat::k8:
      return num_channels == 2 ? viz::SinglePlaneFormat::kRG_88
                               : viz::SinglePlaneFormat::kR_8;
    case viz::SharedImageFormat::ChannelFormat::k10:
    case viz::SharedImageFormat::ChannelFormat::k16:
      return num_channels == 2 ? viz::SinglePlaneFormat::kRG_1616
                               : viz::SinglePlaneFormat::kR_16;
    case viz::SharedImageFormat::ChannelFormat::k16F:
      CHECK_EQ(num_channels, 1);
      return viz::SinglePlaneFormat::kR_F16;
  }
  NOTREACHED();
}

GLTextureHolder::GLTextureHolder(viz::SharedImageFormat format,
                                 const gfx::Size& size,
                                 bool is_passthrough,
                                 gl::ProgressReporter* progress_reporter)
    : format_(format),
      size_(size),
      is_passthrough_(is_passthrough),
      progress_reporter_(progress_reporter) {
  CHECK(format_.is_single_plane());
}

// TODO(kylechar): When `texture_` is removed with validating command decoder
// move constructor/assignment can be defaulted.
GLTextureHolder::GLTextureHolder(GLTextureHolder&& other) {
  operator=(std::move(other));
}

GLTextureHolder& GLTextureHolder::operator=(GLTextureHolder&& other) {
  format_ = other.format_;
  size_ = other.size_;
  is_passthrough_ = other.is_passthrough_;
  context_lost_ = other.context_lost_;
  texture_ = other.texture_;
  other.texture_ = nullptr;
  passthrough_texture_ = std::move(other.passthrough_texture_);
  format_desc_ = other.format_desc_;
  progress_reporter_ = other.progress_reporter_;
  return *this;
}

GLTextureHolder::~GLTextureHolder() {
  if (is_passthrough_) {
    if (passthrough_texture_) {
      if (context_lost_) {
        passthrough_texture_->MarkContextLost();
      }
      passthrough_texture_.reset();
    }
  } else {
    if (texture_) {
      texture_.ExtractAsDangling()->RemoveLightweightRef(!context_lost_);
    }
  }
}

GLuint GLTextureHolder::GetServiceId() const {
  return is_passthrough_ ? passthrough_texture_->service_id()
                         : texture_->service_id();
}

void GLTextureHolder::Initialize(
    const GLCommonImageBackingFactory::FormatInfo& format_info,
    bool framebuffer_attachment_angle,
    base::span<const uint8_t> pixel_data,
    const std::string& debug_label) {
  DCHECK(!texture_ && !passthrough_texture_);

  format_desc_.target = GL_TEXTURE_2D;
  format_desc_.data_format = format_info.gl_format;
  format_desc_.data_type = format_info.gl_type;
  format_desc_.image_internal_format = format_info.image_internal_format;
  format_desc_.storage_internal_format = format_info.storage_internal_format;

  MakeTextureAndSetParameters(format_desc_.target, framebuffer_attachment_angle,
                              is_passthrough_ ? &passthrough_texture_ : nullptr,
                              is_passthrough_ ? nullptr : &texture_);

  if (is_passthrough_) {
    passthrough_texture_->SetEstimatedSize(format_.EstimatedSizeInBytes(size_));
  } else {
    // TODO(piman): We pretend the texture was created in an ES2 context, so
    // that it can be used in other ES2 contexts, and so we have to pass
    // gl_format as the internal format in the LevelInfo.
    // https://crbug.com/628064
    texture_->SetLevelInfo(format_desc_.target, 0, format_desc_.data_format,
                           size_.width(), size_.height(), /*depth=*/1, 0,
                           format_desc_.data_format, format_desc_.data_type,
                           /*cleared_rect=*/gfx::Rect());
    texture_->SetImmutable(true, format_info.supports_storage);
  }

  gl::GLApi* api = gl::g_current_gl_context;
  gl::ScopedRestoreTexture scoped_restore(api, format_desc_.target,
                                          GetServiceId());

  // Initialize the texture storage/image parameters and upload initial pixels
  // if available.
  if (format_info.supports_storage) {
    {
#if BUILDFLAG(IS_ANDROID)
      // When using angle via enabling passthrough command decoder on android,
      // disable renderability validation in angle for this texture since it is
      // being created in ES3 context with a format which could be
      // invalid/non-renderable in ES2/WEBGL1 context when this texture gets
      // imported into the ES2/WEBGL1 context.
      if (gl::g_current_gl_driver->ext.b_GL_ANGLE_renderability_validation) {
        api->glTexParameteriFn(format_desc_.target,
                               GL_RENDERABILITY_VALIDATION_ANGLE, GL_FALSE);
      }
#endif
      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
      api->glTexStorage2DEXTFn(format_desc_.target, /*levels=*/1,
                               format_info.adjusted_storage_internal_format,
                               size_.width(), size_.height());
    }

    if (!pixel_data.empty()) {
      ScopedUnpackState scoped_unpack_state(
          /*uploading_data=*/true);
      gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
      api->glTexSubImage2DFn(format_desc_.target, /*level=*/0, /*xoffset=*/0,
                             /*yoffset=*/0, size_.width(), size_.height(),
                             format_info.adjusted_format,
                             format_desc_.data_type, pixel_data.data());
    }
  } else if (format_info.is_compressed) {
    ScopedUnpackState scoped_unpack_state(!pixel_data.empty());
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    api->glCompressedTexImage2DFn(format_desc_.target, 0,
                                  format_desc_.image_internal_format,
                                  size_.width(), size_.height(), /*border=*/0,
                                  pixel_data.size(), pixel_data.data());
  } else {
    ScopedUnpackState scoped_unpack_state(!pixel_data.empty());
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    api->glTexImage2DFn(
        format_desc_.target, /*level=*/0, format_desc_.image_internal_format,
        size_.width(), size_.height(), /*border=*/0,
        format_info.adjusted_format, format_desc_.data_type, pixel_data.data());
  }

  if (!is_passthrough_) {
    // Must be set after initial pixel upload.
    texture_->SetCompatibilitySwizzle(format_info.swizzle);
  }

  // If the extension does not exist, do not pass debug label to avoid crashes.
  if (!debug_label.empty() && gl::g_current_gl_driver->ext.b_GL_KHR_debug) {
    api->glObjectLabelFn(GL_TEXTURE, GetServiceId(), -1, debug_label.c_str());
  }
}

void GLTextureHolder::InitializeWithTexture(
    const GLFormatDesc& format_desc,
    scoped_refptr<gles2::TexturePassthrough> texture) {
  DCHECK(!texture_ && !passthrough_texture_);
  DCHECK(is_passthrough_);

  format_desc_ = format_desc;
  passthrough_texture_ = std::move(texture);
}

void GLTextureHolder::InitializeWithTexture(const GLFormatDesc& format_desc,
                                            gles2::Texture* texture) {
  DCHECK(!texture_ && !passthrough_texture_);
  DCHECK(!is_passthrough_);

  format_desc_ = format_desc;
  texture_ = texture;
}

bool GLTextureHolder::UploadFromMemory(const SkPixmap& pixmap) {
  DCHECK_EQ(pixmap.width(), size_.width());
  DCHECK_EQ(pixmap.height(), size_.height());

  const GLuint texture_id = GetServiceId();
  const GLenum gl_format = format_desc_.data_format;
  const GLenum gl_type = format_desc_.data_type;
  const GLenum gl_target = format_desc_.target;

  size_t src_stride = pixmap.rowBytes();
  size_t src_total_bytes = pixmap.computeByteSize();
  size_t src_bytes_per_pixel = pixmap.info().bytesPerPixel();

  const int gl_unpack_alignment =
      ComputeBestAlignment(src_bytes_per_pixel, src_stride);
  DCHECK_EQ(src_stride % gl_unpack_alignment, 0u);

  std::vector<uint8_t> repacked_data;
  if (format_ == viz::SinglePlaneFormat::kBGRX_8888 ||
      format_ == viz::SinglePlaneFormat::kRGBX_8888) {
    DCHECK_EQ(gl_format, static_cast<GLenum>(GL_RGB));
    DCHECK_EQ(gl_unpack_alignment, 4);

    // BGRX and RGBX data is uploaded as GL_RGB. Repack from 4 to 3 bytes per
    // pixel.
    repacked_data = RepackPixelDataAsRgb(
        size_, pixmap, format_ == viz::SinglePlaneFormat::kBGRX_8888);
    src_stride =
        base::bits::AlignUp<size_t>(size_.width() * 3, gl_unpack_alignment);
    src_total_bytes = repacked_data.size();
    src_bytes_per_pixel = 3;
  }

  // Compute expected stride and total bytes glTexSubImage2D() will access and
  // verify that works with source pixel data.
  uint32_t expected_total_bytes = 0;
  uint32_t expected_stride = 0;
  bool result = gles2::GLES2Util::ComputeImageDataSizes(
      size_.width(), size_.height(), /*depth=*/1, gl_format, gl_type,
      gl_unpack_alignment, &expected_total_bytes, nullptr, &expected_stride);
  DCHECK(result);
  DCHECK_GE(src_total_bytes, expected_total_bytes);
  DCHECK_GE(src_stride, expected_stride);

  GLuint gl_unpack_row_length = 0;
  if (src_stride != expected_stride) {
    // RGBX/BGRX has been repacked so it should always have expected stride.
    DCHECK(repacked_data.empty());

    if (SupportsUnpackSubimage()) {
      // Use GL_UNPACK_ROW_LENGTH to skip data past end of each row on upload.
      gl_unpack_row_length = src_stride / src_bytes_per_pixel;
    } else {
      // If GL_UNPACK_ROW_LENGTH isn't supported then repack pixels with the
      // expected stride.
      repacked_data = RepackPixelDataWithStride(size_, pixmap, expected_stride);
    }
  }

  gl::ScopedTextureBinder scoped_texture_binder(gl_target, texture_id);
  ScopedUnpackState scoped_unpack_state(
      /*uploading_data=*/true, gl_unpack_row_length, gl_unpack_alignment);

  const void* pixels =
      !repacked_data.empty() ? repacked_data.data() : pixmap.addr();
  gl::GLApi* api = gl::g_current_gl_context;
  {
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    api->glTexSubImage2DFn(gl_target, /*level=*/0, 0, 0, size_.width(),
                           size_.height(), gl_format, gl_type, pixels);
  }

  return true;
}

bool GLTextureHolder::ReadbackToMemory(const SkPixmap& pixmap) {
  DCHECK_EQ(pixmap.width(), size_.width());
  DCHECK_EQ(pixmap.height(), size_.height());

  const GLuint texture_id = GetServiceId();
  GLenum gl_format = format_desc_.data_format;
  GLenum gl_type = format_desc_.data_type;

  if (format_ == viz::SinglePlaneFormat::kBGRX_8888 ||
      format_ == viz::SinglePlaneFormat::kRGBX_8888) {
    DCHECK_EQ(gl_format, static_cast<GLenum>(GL_RGB));
    DCHECK_EQ(gl_type, static_cast<GLenum>(GL_UNSIGNED_BYTE));

    // Always readback RGBX/BGRX as RGBA/BGRA instead of RGB to avoid needing a
    // temporary buffer.
    gl_format =
        format_ == viz::SinglePlaneFormat::kBGRX_8888 ? GL_BGRA_EXT : GL_RGBA;
  }

  gl::GLApi* api = gl::g_current_gl_context;
  GLuint framebuffer;
  api->glGenFramebuffersEXTFn(1, &framebuffer);
  gl::ScopedFramebufferBinder scoped_framebuffer_binder(framebuffer);
  // This uses GL_FRAMEBUFFER instead of GL_READ_FRAMEBUFFER as the target for
  // GLES2 compatibility.
  api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_2D, texture_id, /*level=*/0);
  DCHECK_EQ(api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER),
            static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE));

  bool needs_rb_swizzle = false;

  // GL_RGBA + GL_UNSIGNED_BYTE are always supported. Otherwise there is a
  // preferred format + type that can be queried and is based on what is bound
  // to GL_READ_FRAMEBUFFER.
  if (gl_format != GL_RGBA || gl_type != GL_UNSIGNED_BYTE) {
    GLint preferred_format = 0;
    api->glGetIntegervFn(GL_IMPLEMENTATION_COLOR_READ_FORMAT,
                         &preferred_format);
    GLint preferred_type = 0;
    api->glGetIntegervFn(GL_IMPLEMENTATION_COLOR_READ_TYPE, &preferred_type);

    if (gl_format != static_cast<GLenum>(preferred_format) ||
        gl_type != static_cast<GLenum>(preferred_type)) {
      if (format_ == viz::SinglePlaneFormat::kBGRA_8888 ||
          format_ == viz::SinglePlaneFormat::kBGRX_8888) {
        DCHECK_EQ(gl_format, static_cast<GLenum>(GL_BGRA_EXT));
        DCHECK_EQ(gl_type, static_cast<GLenum>(GL_UNSIGNED_BYTE));

        // If BGRA readback isn't support then use RGBA and swizzle.
        gl_format = GL_RGBA;
        needs_rb_swizzle = true;
      } else {
        DLOG(ERROR) << format_.ToString()
                    << " is not supported by glReadPixels()";
        return false;
      }
    }
  }

  const size_t dst_stride = pixmap.rowBytes();
  const size_t dst_bytes_per_pixel = pixmap.info().bytesPerPixel();

  const int gl_pack_alignment =
      ComputeBestAlignment(dst_bytes_per_pixel, dst_stride);
  DCHECK_EQ(dst_stride % gl_pack_alignment, 0u);

  // Compute expected stride and total bytes glReadPixels() will access and
  // verify that works with destination pixel data.
  uint32_t expected_total_bytes = 0;
  uint32_t expected_stride = 0;
  bool result = gles2::GLES2Util::ComputeImageDataSizes(
      size_.width(), size_.height(), /*depth=*/1, gl_format, gl_type,
      gl_pack_alignment, &expected_total_bytes, nullptr, &expected_stride);
  DCHECK(result);
  DCHECK_GE(pixmap.computeByteSize(), expected_total_bytes);
  DCHECK_GE(dst_stride, expected_stride);

  std::vector<uint8_t> unpack_buffer;
  GLuint gl_pack_row_length = 0;
  if (dst_stride != expected_stride) {
    if (SupportsPackSubimage()) {
      // Use GL_PACK_ROW_LENGTH to avoid temporary buffer.
      gl_pack_row_length = dst_stride / dst_bytes_per_pixel;
    } else {
      // If GL_PACK_ROW_LENGTH isn't supported then readback to a temporary
      // buffer with expected stride.
      unpack_buffer = std::vector<uint8_t>(expected_stride * size_.height());
    }
  }

  ScopedPackState scoped_pack_state(gl_pack_row_length, gl_pack_alignment);

  void* pixels =
      !unpack_buffer.empty() ? unpack_buffer.data() : pixmap.writable_addr();
  {
    gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
    api->glReadPixelsFn(0, 0, size_.width(), size_.height(), gl_format, gl_type,
                        pixels);
  }

  api->glDeleteFramebuffersEXTFn(1, &framebuffer);

  if (!unpack_buffer.empty()) {
    DCHECK_GT(dst_stride, expected_stride);
    UnpackPixelDataWithStride(size_, unpack_buffer, expected_stride, pixmap);
  }

  if (needs_rb_swizzle) {
    SwizzleRedAndBlue(pixmap);
  }

  return true;
}

sk_sp<GrPromiseImageTexture> GLTextureHolder::GetPromiseImage(
    SharedContextState* context_state) {
  GrBackendTexture backend_texture;
  GetGrBackendTexture(context_state->feature_info(), format_desc_.target, size_,
                      GetServiceId(), format_desc_.storage_internal_format,
                      context_state->gr_context()->threadSafeProxy(),
                      &backend_texture);
  return GrPromiseImageTexture::Make(backend_texture);
}

gfx::Rect GLTextureHolder::GetClearedRect() const {
  DCHECK(!is_passthrough_);
  return texture_->GetLevelClearedRect(format_desc_.target, 0);
}

void GLTextureHolder::SetClearedRect(const gfx::Rect& cleared_rect) {
  DCHECK(!is_passthrough_);
  texture_->SetLevelClearedRect(format_desc_.target, 0, cleared_rect);
}

void GLTextureHolder::SetContextLost() {
  context_lost_ = true;
}

}  // namespace gpu