blob: 4ebd02291c2301fea96cfe5076d3acbf94c23a85 [file] [log] [blame]
// Copyright 2019 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/egl_image_backing.h"
#include <optional>
#include "base/memory/raw_ptr.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_gl_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/command_buffer/service/shared_image/skia_gl_image_representation.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "ui/gl/buildflags.h"
#include "ui/gl/gl_fence_egl.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/scoped_binders.h"
#include "ui/gl/shared_gl_fence_egl.h"
#if BUILDFLAG(USE_DAWN) && BUILDFLAG(DAWN_ENABLE_BACKEND_OPENGLES)
#include "gpu/command_buffer/service/shared_image/dawn_egl_image_representation.h"
#endif
namespace gpu {
class EGLImageBacking::TextureHolder : public base::RefCounted<TextureHolder> {
public:
explicit TextureHolder(gles2::Texture* texture) : texture_(texture) {}
explicit TextureHolder(
scoped_refptr<gles2::TexturePassthrough> texture_passthrough)
: texture_passthrough_(std::move(texture_passthrough)) {}
void MarkContextLost() {
context_lost_ = true;
if (texture_passthrough_)
texture_passthrough_->MarkContextLost();
}
gles2::Texture* texture() { return texture_; }
const scoped_refptr<gles2::TexturePassthrough>& texture_passthrough() const {
return texture_passthrough_;
}
private:
friend class base::RefCounted<TextureHolder>;
~TextureHolder() {
if (texture_) {
texture_.ExtractAsDangling()->RemoveLightweightRef(!context_lost_);
}
}
raw_ptr<gles2::Texture> texture_ = nullptr;
const scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
bool context_lost_ = false;
};
// Implementation of GLTextureImageRepresentation which uses GL texture
// which is an EGLImage sibling.
class EGLImageBacking::GLRepresentationShared {
public:
using TextureHolder = EGLImageBacking::TextureHolder;
GLRepresentationShared(
EGLImageBacking* backing,
std::vector<scoped_refptr<TextureHolder>> texture_holders)
: backing_(backing), texture_holders_(std::move(texture_holders)) {}
GLRepresentationShared(const GLRepresentationShared&) = delete;
GLRepresentationShared& operator=(const GLRepresentationShared&) = delete;
~GLRepresentationShared() {
EndAccess();
if (!backing_->have_context()) {
for (auto texture_holder : texture_holders_) {
texture_holder->MarkContextLost();
}
}
texture_holders_.clear();
}
bool BeginAccess(GLenum mode) {
if (mode == GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM) {
if (!backing_->BeginRead(this))
return false;
mode_ = RepresentationAccessMode::kRead;
} else if (mode == GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM) {
if (!backing_->BeginWrite())
return false;
mode_ = RepresentationAccessMode::kWrite;
} else {
NOTREACHED();
}
return true;
}
void EndAccess() {
if (mode_ == RepresentationAccessMode::kNone)
return;
// Pass this fence to its backing.
if (mode_ == RepresentationAccessMode::kRead) {
backing_->EndRead(this);
} else if (mode_ == RepresentationAccessMode::kWrite) {
backing_->EndWrite();
} else {
NOTREACHED();
}
mode_ = RepresentationAccessMode::kNone;
}
const scoped_refptr<TextureHolder>& texture_holder(int plane_index) const {
return texture_holders_[plane_index];
}
private:
const raw_ptr<EGLImageBacking> backing_;
std::vector<scoped_refptr<TextureHolder>> texture_holders_;
RepresentationAccessMode mode_ = RepresentationAccessMode::kNone;
};
class EGLImageBacking::GLTextureEGLImageRepresentation
: public GLTextureImageRepresentation {
public:
GLTextureEGLImageRepresentation(
SharedImageManager* manager,
EGLImageBacking* backing,
MemoryTypeTracker* tracker,
std::vector<scoped_refptr<TextureHolder>> texture_holders)
: GLTextureImageRepresentation(manager, backing, tracker),
shared_(backing, std::move(texture_holders)) {}
GLTextureEGLImageRepresentation(const GLTextureEGLImageRepresentation&) =
delete;
GLTextureEGLImageRepresentation& operator=(
const GLTextureEGLImageRepresentation&) = delete;
~GLTextureEGLImageRepresentation() override = default;
bool BeginAccess(GLenum mode) override { return shared_.BeginAccess(mode); }
void EndAccess() override { shared_.EndAccess(); }
gles2::Texture* GetTexture(int plane_index) override {
CHECK(format().IsValidPlaneIndex(plane_index));
return shared_.texture_holder(plane_index)->texture();
}
bool SupportsMultipleConcurrentReadAccess() override { return true; }
private:
GLRepresentationShared shared_;
};
class EGLImageBacking::GLTexturePassthroughEGLImageRepresentation
: public GLTexturePassthroughImageRepresentation {
public:
GLTexturePassthroughEGLImageRepresentation(
SharedImageManager* manager,
EGLImageBacking* backing,
MemoryTypeTracker* tracker,
std::vector<scoped_refptr<TextureHolder>> texture_holders)
: GLTexturePassthroughImageRepresentation(manager, backing, tracker),
shared_(backing, std::move(texture_holders)) {}
GLTexturePassthroughEGLImageRepresentation(
const GLTexturePassthroughEGLImageRepresentation&) = delete;
GLTexturePassthroughEGLImageRepresentation& operator=(
const GLTexturePassthroughEGLImageRepresentation&) = delete;
~GLTexturePassthroughEGLImageRepresentation() override = default;
bool BeginAccess(GLenum mode) override { return shared_.BeginAccess(mode); }
void EndAccess() override { shared_.EndAccess(); }
const scoped_refptr<gles2::TexturePassthrough>& GetTexturePassthrough(
int plane_index) override {
CHECK(format().IsValidPlaneIndex(plane_index));
// TODO(crbug.com/40166788): Remove this CHECK.
CHECK(shared_.texture_holder(plane_index)->texture_passthrough());
return shared_.texture_holder(plane_index)->texture_passthrough();
}
bool SupportsMultipleConcurrentReadAccess() override { return true; }
private:
GLRepresentationShared shared_;
};
EGLImageBacking::EGLImageBacking(
const Mailbox& mailbox,
viz::SharedImageFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
SharedImageUsageSet usage,
std::string debug_label,
size_t estimated_size,
const std::vector<GLCommonImageBackingFactory::FormatInfo>& format_info,
const GpuDriverBugWorkarounds& workarounds,
bool use_passthrough,
base::span<const uint8_t> pixel_data)
: ClearTrackingSharedImageBacking(mailbox,
format,
size,
color_space,
surface_origin,
alpha_type,
usage,
std::move(debug_label),
estimated_size,
true /*is_thread_safe*/),
format_info_(format_info),
use_passthrough_(use_passthrough) {
created_on_context_ = gl::g_current_gl_context;
// On some GPUs (NVidia) keeping reference to egl image itself is not enough,
// we must keep reference to at least one sibling. Note that this workaround
// is currently enabled for all android devices.
// When we have pixel data, we want to initialize the texture with pixel data
// first before creating eglimage from it. Hence using GenEGLImageSibling()
// call to do that.
if (workarounds.dont_delete_source_texture_for_egl_image) {
source_texture_holders_ = GenEGLImageSiblings(pixel_data);
} else if (!pixel_data.empty()) {
auto texture_holder = GenEGLImageSiblings(pixel_data);
}
}
EGLImageBacking::~EGLImageBacking() {
CHECK(source_texture_holders_.empty());
}
SharedImageBackingType EGLImageBacking::GetType() const {
return SharedImageBackingType::kEGLImage;
}
void EGLImageBacking::Update(std::unique_ptr<gfx::GpuFence> in_fence) {
NOTREACHED();
}
template <class T>
std::unique_ptr<T> EGLImageBacking::ProduceGLTextureInternal(
SharedImageManager* manager,
MemoryTypeTracker* tracker) {
// On some GPUs (Mali, mostly Android 9, like J7) glTexSubImage fails on egl
// image sibling. So we use the original texture if we're on the same gl
// context. see https://crbug.com/1117370
// If we're on the same context we're on the same thread, so
// source_texture_holder_ is accessed only from thread we created it and
// doesn't need lock.
if (created_on_context_ == gl::g_current_gl_context &&
!source_texture_holders_.empty()) {
return std::make_unique<T>(manager, this, tracker, source_texture_holders_);
}
auto texture_holders =
GenEGLImageSiblings(/*pixel_data=*/base::span<const uint8_t>());
if (texture_holders.empty()) {
return nullptr;
}
return std::make_unique<T>(manager, this, tracker,
std::move(texture_holders));
}
std::unique_ptr<GLTextureImageRepresentation> EGLImageBacking::ProduceGLTexture(
SharedImageManager* manager,
MemoryTypeTracker* tracker) {
return ProduceGLTextureInternal<GLTextureEGLImageRepresentation>(manager,
tracker);
}
std::unique_ptr<GLTexturePassthroughImageRepresentation>
EGLImageBacking::ProduceGLTexturePassthrough(SharedImageManager* manager,
MemoryTypeTracker* tracker) {
return ProduceGLTextureInternal<GLTexturePassthroughEGLImageRepresentation>(
manager, tracker);
}
std::unique_ptr<SkiaGaneshImageRepresentation>
EGLImageBacking::ProduceSkiaGanesh(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
scoped_refptr<SharedContextState> context_state) {
std::unique_ptr<GLTextureImageRepresentationBase> gl_representation;
if (use_passthrough_) {
gl_representation = ProduceGLTexturePassthrough(manager, tracker);
} else {
gl_representation = ProduceGLTexture(manager, tracker);
}
if (!gl_representation) {
return nullptr;
}
return SkiaGLImageRepresentation::Create(std::move(gl_representation),
std::move(context_state), manager,
this, tracker);
}
std::unique_ptr<DawnImageRepresentation> EGLImageBacking::ProduceDawn(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
const wgpu::Device& device,
wgpu::BackendType backend_type,
std::vector<wgpu::TextureFormat> view_formats,
scoped_refptr<SharedContextState> context_state) {
#if BUILDFLAG(USE_DAWN) && BUILDFLAG(DAWN_ENABLE_BACKEND_OPENGLES)
if (backend_type == wgpu::BackendType::OpenGLES) {
std::unique_ptr<GLTextureImageRepresentationBase> gl_representation;
if (use_passthrough_) {
gl_representation = ProduceGLTexturePassthrough(manager, tracker);
} else {
gl_representation = ProduceGLTexture(manager, tracker);
}
if (!gl_representation) {
return nullptr;
}
void* egl_image = nullptr;
{
AutoLock auto_lock(this);
egl_image = egl_images_[0].get();
}
// TODO(crbug.com/40278761): Add multiplanar support to this representation.
return std::make_unique<DawnEGLImageRepresentation>(
std::move(gl_representation), egl_image, manager, this, tracker,
device.Get(), std::move(view_formats));
}
#endif // BUILDFLAG(USE_DAWN) && BUILDFLAG(DAWN_ENABLE_BACKEND_OPENGLES)
return nullptr;
}
bool EGLImageBacking::BeginWrite() {
AutoLock auto_lock(this);
if (is_writing_ || !active_readers_.empty()) {
DLOG(ERROR) << "BeginWrite should only be called when there are no other "
"readers or writers";
return false;
}
is_writing_ = true;
// When multiple threads wants to write to the same backing, writer needs to
// wait on previous reads and writes to be finished.
if (!read_fences_.empty()) {
for (const auto& read_fence : read_fences_) {
read_fence.second->ServerWait();
}
// Once all the read fences have been waited upon, its safe to clear all of
// them. Note that when there is an active writer, no one can read and hence
// can not update |read_fences_|.
read_fences_.clear();
}
if (write_fence_)
write_fence_->ServerWait();
return true;
}
void EGLImageBacking::EndWrite() {
AutoLock auto_lock(this);
if (!is_writing_) {
DLOG(ERROR) << "Attempt to end write to a SharedImageBacking without a "
"successful begin write";
return;
}
is_writing_ = false;
write_fence_ = gl::GLFenceEGL::Create();
}
bool EGLImageBacking::BeginRead(const GLRepresentationShared* reader) {
AutoLock auto_lock(this);
if (is_writing_) {
DLOG(ERROR) << "BeginRead should only be called when there are no writers";
return false;
}
if (active_readers_.contains(reader)) {
LOG(ERROR) << "BeginRead was called twice on the same representation";
return false;
}
active_readers_.insert(reader);
if (write_fence_)
write_fence_->ServerWait();
return true;
}
void EGLImageBacking::EndRead(const GLRepresentationShared* reader) {
{
AutoLock auto_lock(this);
if (!active_readers_.contains(reader)) {
DLOG(ERROR) << "Attempt to end read to a SharedImageBacking without a "
"successful begin read";
return;
}
active_readers_.erase(reader);
}
AutoLock auto_lock(this);
read_fences_[gl::g_current_gl_context] =
base::MakeRefCounted<gl::SharedGLFenceEGL>();
}
gl::ScopedEGLImage EGLImageBacking::GenEGLImageSibling(
base::span<const uint8_t> pixel_data,
std::vector<GLuint>& service_ids,
int plane) {
GLenum target = GL_TEXTURE_2D;
gl::GLApi* api = gl::g_current_gl_context;
api->glGenTexturesFn(1, &service_ids[plane]);
gl::ScopedTextureBinder texture_binder(target, service_ids[plane]);
api->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
api->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gfx::Size plane_size = format().GetPlaneSize(plane, size());
const auto& format_info = format_info_[plane];
// Note that we only want to upload pixel data to a texture during init
// time before we create `egl_images_` from it. If pixel data is
// empty we only allocate memory for the texture object which is
// required to create EGLImage.
if (format_info.supports_storage && IsTexStorage2DAvailable()) {
api->glTexStorage2DEXTFn(target, 1,
format_info.adjusted_storage_internal_format,
plane_size.width(), plane_size.height());
if (!pixel_data.empty()) {
CHECK_EQ(plane, 0);
ScopedUnpackState scoped_unpack_state(
/*uploading_data=*/true);
api->glTexSubImage2DFn(target, 0, 0, 0, plane_size.width(),
plane_size.height(), format_info.adjusted_format,
format_info.gl_type, pixel_data.data());
}
} else if (format_info.is_compressed) {
ScopedUnpackState scoped_unpack_state(!pixel_data.empty());
api->glCompressedTexImage2DFn(target, 0, format_info.image_internal_format,
plane_size.width(), plane_size.height(), 0,
pixel_data.size(), pixel_data.data());
} else {
ScopedUnpackState scoped_unpack_state(!pixel_data.empty());
api->glTexImage2DFn(target, 0, format_info.image_internal_format,
plane_size.width(), plane_size.height(), 0,
format_info.adjusted_format, format_info.gl_type,
pixel_data.data());
}
// Use service id of the texture as a source to create the EGLImage.
const EGLint egl_attrib_list[] = {
EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
return gl::MakeScopedEGLImage(
eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR,
reinterpret_cast<EGLClientBuffer>(service_ids[plane]), egl_attrib_list);
}
std::vector<scoped_refptr<EGLImageBacking::TextureHolder>>
EGLImageBacking::GenEGLImageSiblings(base::span<const uint8_t> pixel_data) {
GLenum target = GL_TEXTURE_2D;
gl::GLApi* api = gl::g_current_gl_context;
int num_planes = format().NumberOfPlanes();
std::vector<GLuint> service_ids;
service_ids.resize(num_planes);
std::vector<EGLImageKHR> egl_images;
egl_images.reserve(num_planes);
// Note that we needed to use `create_egl_images` flag and add some additional
// logic to handle it in order to make the locks more granular since
// BindToTexture() do not need to be behind the lock. We don't need to bind
// the `egl_images_` first time when it's created.
bool create_egl_images = false;
{
AutoLock auto_lock(this);
create_egl_images = egl_images_.empty();
if (create_egl_images) {
for (int plane = 0; plane < num_planes; plane++) {
gl::ScopedEGLImage egl_image =
GenEGLImageSibling(pixel_data, service_ids, plane);
// Check `egl_image` validity and add to `egl_images_` only when created
// a new one.
if (!egl_image.get()) {
for (int i = 0; i <= plane; i++) {
// Delete all textures created so far.
api->glDeleteTexturesFn(1, &service_ids[i]);
}
egl_images_.clear();
return {};
}
egl_images_.push_back(std::move(egl_image));
}
}
for (int plane = 0; plane < num_planes; plane++) {
egl_images.push_back(egl_images_[plane].get());
}
if (!pixel_data.empty()) {
// If pixel data is being uploaded to the texture, that means we are
// sending commands to the gpu. Hence consider it as a write and add a
// fence to synchronize it with corresponding reads. This case happens
// when tab windows are composited by viz for tablet ui. Initial pixel
// data gets uploaded on the gpu main thread and being read on DrDc
// thread.
write_fence_ = gl::GLFenceEGL::Create();
}
}
if (!create_egl_images) {
// `pixel_data` if present should only be used to initialize textures when
// we create `egl_images_` from it and not after it has been already
// created.
DCHECK(pixel_data.empty());
for (int plane = 0; plane < num_planes; plane++) {
api->glGenTexturesFn(1, &service_ids[plane]);
gl::ScopedTextureBinder texture_binder(target, service_ids[plane]);
api->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
api->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// If we already have the `egl_images_`, just bind them to the new
// texture to make an EGLImage sibling.
glEGLImageTargetTexture2DOES(target, egl_images[plane]);
DCHECK_EQ(static_cast<EGLint>(EGL_SUCCESS), eglGetError());
DCHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}
}
// Mark the backing as cleared if pixel data has been uploaded. Note that
// SetCleared() acquires the lock. Hence it is kept outside of previous lock
// above.
if (!pixel_data.empty()) {
SetCleared();
}
std::vector<scoped_refptr<EGLImageBacking::TextureHolder>> texture_holders;
texture_holders.reserve(num_planes);
for (int plane = 0; plane < num_planes; plane++) {
if (use_passthrough_) {
auto texture_passthrough =
base::MakeRefCounted<gpu::gles2::TexturePassthrough>(
service_ids[plane], GL_TEXTURE_2D);
texture_holders.push_back(
base::MakeRefCounted<TextureHolder>(std::move(texture_passthrough)));
} else {
auto* texture = gles2::CreateGLES2TextureWithLightRef(service_ids[plane],
GL_TEXTURE_2D);
gfx::Size plane_size = format().GetPlaneSize(plane, size());
// If the backing is already cleared, no need to clear it again.
gfx::Rect cleared_rect;
if (IsCleared()) {
cleared_rect = gfx::Rect(plane_size);
}
// Set the level info.
texture->SetLevelInfo(GL_TEXTURE_2D, 0, format_info_[plane].gl_format,
plane_size.width(), plane_size.height(), 1, 0,
format_info_[plane].gl_format,
format_info_[plane].gl_type, cleared_rect);
texture->SetImmutable(/*immutable=*/true, /*immutable_storage=*/false);
texture_holders.push_back(
base::MakeRefCounted<TextureHolder>(std::move(texture)));
}
}
return texture_holders;
}
void EGLImageBacking::MarkForDestruction() {
AutoLock auto_lock(this);
DCHECK(!have_context() || created_on_context_ == gl::g_current_gl_context);
if (!have_context()) {
for (auto source_texture_holder : source_texture_holders_) {
source_texture_holder->MarkContextLost();
}
}
source_texture_holders_.clear();
}
} // namespace gpu