blob: 556868a25dfc26ad0f96889cb8317d846dfb58a8 [file] [log] [blame]
// Copyright 2019 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 "gpu/command_buffer/service/shared_image_backing_egl_image.h"
#include "gpu/command_buffer/service/native_image_buffer.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image_batch_access_manager.h"
#include "gpu/command_buffer/service/shared_image_representation.h"
#include "gpu/command_buffer/service/shared_image_representation_skia_gl.h"
#include "gpu/command_buffer/service/texture_manager.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"
namespace gpu {
class SharedImageBackingEglImage::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_->RemoveLightweightRef(!context_lost_);
}
gles2::Texture* const texture_ = nullptr;
const scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
bool context_lost_ = false;
};
// Implementation of SharedImageRepresentationGLTexture which uses GL texture
// which is an EGLImage sibling.
class SharedImageBackingEglImage::RepresentationGLShared {
public:
using TextureHolder = SharedImageBackingEglImage::TextureHolder;
RepresentationGLShared(SharedImageBackingEglImage* backing,
scoped_refptr<TextureHolder> texture_holder)
: backing_(backing), texture_holder_(std::move(texture_holder)) {}
RepresentationGLShared(const RepresentationGLShared&) = delete;
RepresentationGLShared& operator=(const RepresentationGLShared&) = delete;
~RepresentationGLShared() {
EndAccess();
if (!backing_->have_context())
texture_holder_->MarkContextLost();
texture_holder_.reset();
}
bool BeginAccess(GLenum mode) {
if (mode == GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM ||
mode == GL_SHARED_IMAGE_ACCESS_MODE_OVERLAY_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() const {
return texture_holder_;
}
private:
SharedImageBackingEglImage* const backing_;
scoped_refptr<TextureHolder> texture_holder_;
RepresentationAccessMode mode_ = RepresentationAccessMode::kNone;
};
class SharedImageBackingEglImage::RepresentationGLTexture
: public SharedImageRepresentationGLTexture {
public:
RepresentationGLTexture(SharedImageManager* manager,
SharedImageBackingEglImage* backing,
MemoryTypeTracker* tracker,
scoped_refptr<TextureHolder> texture_holder)
: SharedImageRepresentationGLTexture(manager, backing, tracker),
shared_(backing, std::move(texture_holder)) {}
RepresentationGLTexture(const RepresentationGLTexture&) = delete;
RepresentationGLTexture& operator=(const RepresentationGLTexture&) = delete;
~RepresentationGLTexture() override = default;
bool BeginAccess(GLenum mode) override { return shared_.BeginAccess(mode); }
void EndAccess() override { shared_.EndAccess(); }
gles2::Texture* GetTexture() override {
return shared_.texture_holder()->texture();
}
bool SupportsMultipleConcurrentReadAccess() override { return true; }
private:
RepresentationGLShared shared_;
};
class SharedImageBackingEglImage::RepresentationGLTexturePassthrough
: public SharedImageRepresentationGLTexturePassthrough {
public:
RepresentationGLTexturePassthrough(
SharedImageManager* manager,
SharedImageBackingEglImage* backing,
MemoryTypeTracker* tracker,
scoped_refptr<TextureHolder> texture_holder)
: SharedImageRepresentationGLTexturePassthrough(manager,
backing,
tracker),
shared_(backing, std::move(texture_holder)) {}
RepresentationGLTexturePassthrough(
const RepresentationGLTexturePassthrough&) = delete;
RepresentationGLTexturePassthrough& operator=(
const RepresentationGLTexturePassthrough&) = delete;
~RepresentationGLTexturePassthrough() override = default;
bool BeginAccess(GLenum mode) override { return shared_.BeginAccess(mode); }
void EndAccess() override { shared_.EndAccess(); }
const scoped_refptr<gles2::TexturePassthrough>& GetTexturePassthrough()
override {
// TODO(https://crbug.com/1172769): Remove this CHECK.
CHECK(shared_.texture_holder()->texture_passthrough());
return shared_.texture_holder()->texture_passthrough();
}
bool SupportsMultipleConcurrentReadAccess() override { return true; }
private:
RepresentationGLShared shared_;
};
SharedImageBackingEglImage::SharedImageBackingEglImage(
const Mailbox& mailbox,
viz::ResourceFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
uint32_t usage,
size_t estimated_size,
const SharedImageBackingFactoryGLCommon::FormatInfo format_info,
SharedImageBatchAccessManager* batch_access_manager,
const GpuDriverBugWorkarounds& workarounds,
const SharedImageBackingGLCommon::UnpackStateAttribs& attribs,
bool use_passthrough,
base::span<const uint8_t> pixel_data)
: ClearTrackingSharedImageBacking(mailbox,
format,
size,
color_space,
surface_origin,
alpha_type,
usage,
estimated_size,
true /*is_thread_safe*/),
format_info_(format_info),
batch_access_manager_(batch_access_manager),
gl_unpack_attribs_(attribs),
use_passthrough_(use_passthrough) {
DCHECK(batch_access_manager_);
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_holder_ = GenEGLImageSibling(pixel_data);
else if (!pixel_data.empty())
auto texture_holder = GenEGLImageSibling(pixel_data);
}
SharedImageBackingEglImage::~SharedImageBackingEglImage() {
// Un-Register this backing from the |batch_access_manager_|.
batch_access_manager_->UnregisterEglBacking(this);
DCHECK(!source_texture_holder_);
}
void SharedImageBackingEglImage::Update(
std::unique_ptr<gfx::GpuFence> in_fence) {
NOTREACHED();
}
bool SharedImageBackingEglImage::ProduceLegacyMailbox(
MailboxManager* mailbox_manager) {
// This backing doe not support legacy mailbox system.
return false;
}
template <class T>
std::unique_ptr<T> SharedImageBackingEglImage::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_holder_) {
return std::make_unique<T>(manager, this, tracker, source_texture_holder_);
}
auto texture_holder =
GenEGLImageSibling(/*pixel_data=*/base::span<const uint8_t>());
if (!texture_holder)
return nullptr;
return std::make_unique<T>(manager, this, tracker, std::move(texture_holder));
}
std::unique_ptr<SharedImageRepresentationGLTexture>
SharedImageBackingEglImage::ProduceGLTexture(SharedImageManager* manager,
MemoryTypeTracker* tracker) {
return ProduceGLTextureInternal<RepresentationGLTexture>(manager, tracker);
}
std::unique_ptr<SharedImageRepresentationGLTexturePassthrough>
SharedImageBackingEglImage::ProduceGLTexturePassthrough(
SharedImageManager* manager,
MemoryTypeTracker* tracker) {
return ProduceGLTextureInternal<RepresentationGLTexturePassthrough>(manager,
tracker);
}
std::unique_ptr<SharedImageRepresentationSkia>
SharedImageBackingEglImage::ProduceSkia(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
scoped_refptr<SharedContextState> context_state) {
if (use_passthrough_) {
auto gl_representation = ProduceGLTexturePassthrough(manager, tracker);
if (!gl_representation)
return nullptr;
return SharedImageRepresentationSkiaGL::Create(std::move(gl_representation),
std::move(context_state),
manager, this, tracker);
} else {
auto gl_representation = ProduceGLTexture(manager, tracker);
if (!gl_representation)
return nullptr;
return SharedImageRepresentationSkiaGL::Create(std::move(gl_representation),
std::move(context_state),
manager, this, tracker);
}
}
bool SharedImageBackingEglImage::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 SharedImageBackingEglImage::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 SharedImageBackingEglImage::BeginRead(
const RepresentationGLShared* 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 SharedImageBackingEglImage::EndRead(const RepresentationGLShared* 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);
}
// For batch reads, we only need to create 1 fence after the last
// EndRead() for the whole batch of reads. Hence we just register this backing
// here with the |batch_access_manager_| so that it can set an end read fence
// on this backing later after the last read of the batch. This improves
// performance because creating and inserting gl fences are costly. For non
// batch reads/regular reads, we create 1 fence per EndRead().
if (batch_access_manager_->IsDoingBatchReads()) {
batch_access_manager_->RegisterEglBackingForEndReadFence(this);
return;
}
AutoLock auto_lock(this);
read_fences_[gl::g_current_gl_context] =
base::MakeRefCounted<gl::SharedGLFenceEGL>();
}
scoped_refptr<SharedImageBackingEglImage::TextureHolder>
SharedImageBackingEglImage::GenEGLImageSibling(
base::span<const uint8_t> pixel_data) {
// Create a gles2::texture.
GLenum target = GL_TEXTURE_2D;
gl::GLApi* api = gl::g_current_gl_context;
GLuint service_id = 0;
api->glGenTexturesFn(1, &service_id);
gl::ScopedTextureBinder texture_binder(target, service_id);
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);
// Note that we needed to use |bind_egl_image| 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_image_buffer_| first time when it's created.
bool bind_egl_image = true;
scoped_refptr<gles2::NativeImageBuffer> buffer;
{
AutoLock auto_lock(this);
// |pixel_data| if present should only be used to initialize texture when we
// create |egl_image_buffer_| from it and not after it has been already
// created.
DCHECK(pixel_data.empty() || !egl_image_buffer_);
if (!egl_image_buffer_) {
// Note that we only want to upload pixel data to a texture during init
// time before we create |egl_image_buffer_| 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) {
api->glTexStorage2DEXTFn(target, 1,
format_info_.storage_internal_format,
size().width(), size().height());
if (!pixel_data.empty()) {
SharedImageBackingGLCommon::ScopedResetAndRestoreUnpackState
scoped_unpack_state(api, gl_unpack_attribs_,
true /* uploading_data */);
api->glTexSubImage2DFn(target, 0, 0, 0, size().width(),
size().height(), format_info_.adjusted_format,
format_info_.gl_type, pixel_data.data());
}
} else if (format_info_.is_compressed) {
SharedImageBackingGLCommon::ScopedResetAndRestoreUnpackState
scoped_unpack_state(api, gl_unpack_attribs_, !pixel_data.empty());
api->glCompressedTexImage2DFn(
target, 0, format_info_.image_internal_format, size().width(),
size().height(), 0, pixel_data.size(), pixel_data.data());
} else {
SharedImageBackingGLCommon::ScopedResetAndRestoreUnpackState
scoped_unpack_state(api, gl_unpack_attribs_, !pixel_data.empty());
api->glTexImage2DFn(target, 0, format_info_.image_internal_format,
size().width(), 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 native buffer.
egl_image_buffer_ = gles2::NativeImageBuffer::Create(service_id);
if (!egl_image_buffer_) {
api->glDeleteTexturesFn(1, &service_id);
return {};
}
bind_egl_image = false;
}
buffer = egl_image_buffer_;
}
// 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();
if (bind_egl_image) {
// If we already have the |egl_image_buffer_|, just bind it to the new
// texture to make it an EGLImage sibling.
buffer->BindToTexture(target);
}
if (use_passthrough_) {
auto texture_passthrough =
base::MakeRefCounted<gpu::gles2::TexturePassthrough>(
service_id, GL_TEXTURE_2D, format_info_.gl_format, size().width(),
size().height(),
/*depth=*/1, /*border=*/0, format_info_.gl_format,
format_info_.gl_type);
return base::MakeRefCounted<TextureHolder>(std::move(texture_passthrough));
}
auto* texture = new gles2::Texture(service_id);
texture->SetLightweightRef();
texture->SetTarget(GL_TEXTURE_2D, 1 /*max_levels*/);
texture->set_min_filter(GL_LINEAR);
texture->set_mag_filter(GL_LINEAR);
texture->set_wrap_t(GL_CLAMP_TO_EDGE);
texture->set_wrap_s(GL_CLAMP_TO_EDGE);
// If the backing is already cleared, no need to clear it again.
gfx::Rect cleared_rect;
if (IsCleared())
cleared_rect = gfx::Rect(size());
// Set the level info.
texture->SetLevelInfo(
GL_TEXTURE_2D, 0, format_info_.gl_format, size().width(), size().height(),
1, 0, format_info_.gl_format, format_info_.gl_type, cleared_rect);
texture->SetImmutable(true /*immutable*/, false /*immutable_storage*/);
return base::MakeRefCounted<TextureHolder>(std::move(texture));
}
void SharedImageBackingEglImage::SetEndReadFence(
scoped_refptr<gl::SharedGLFenceEGL> shared_egl_fence) {
AutoLock auto_lock(this);
read_fences_[gl::g_current_gl_context] = std::move(shared_egl_fence);
}
void SharedImageBackingEglImage::MarkForDestruction() {
AutoLock auto_lock(this);
DCHECK(!have_context() || created_on_context_ == gl::g_current_gl_context);
if (source_texture_holder_ && !have_context())
source_texture_holder_->MarkContextLost();
source_texture_holder_.reset();
}
} // namespace gpu