blob: 19517b12fb5903398089e32c747697c8715c28d1 [file] [log] [blame]
// 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