| // 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 |