// Copyright 2014 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/texture_definition.h" #include <list> #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "gpu/command_buffer/service/texture_manager.h" #include "ui/gl/gl_image.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/scoped_binders.h" #if !defined(OS_MACOSX) #include "ui/gl/gl_fence_egl.h" #include "ui/gl/gl_surface_egl.h" #endif namespace gpu { namespace gles2 { namespace { class GLImageSync : public gfx::GLImage { public: explicit GLImageSync(const scoped_refptr<NativeImageBuffer>& buffer, const gfx::Size& size); // Implement GLImage. virtual void Destroy() OVERRIDE; virtual gfx::Size GetSize() OVERRIDE; virtual bool BindTexImage(unsigned target) OVERRIDE; virtual void ReleaseTexImage(unsigned target) OVERRIDE; virtual void WillUseTexImage() OVERRIDE; virtual void WillModifyTexImage() OVERRIDE; virtual void DidModifyTexImage() OVERRIDE; virtual void DidUseTexImage() OVERRIDE; virtual void SetReleaseAfterUse() OVERRIDE; protected: virtual ~GLImageSync(); private: scoped_refptr<NativeImageBuffer> buffer_; gfx::Size size_; DISALLOW_COPY_AND_ASSIGN(GLImageSync); }; GLImageSync::GLImageSync(const scoped_refptr<NativeImageBuffer>& buffer, const gfx::Size& size) : buffer_(buffer), size_(size) { if (buffer) buffer->AddClient(this); } GLImageSync::~GLImageSync() { if (buffer_) buffer_->RemoveClient(this); } void GLImageSync::Destroy() {} gfx::Size GLImageSync::GetSize() { return size_; } bool GLImageSync::BindTexImage(unsigned target) { NOTREACHED(); return false; } void GLImageSync::ReleaseTexImage(unsigned target) { NOTREACHED(); } void GLImageSync::WillUseTexImage() { if (buffer_) buffer_->WillRead(this); } void GLImageSync::DidUseTexImage() { if (buffer_) buffer_->DidRead(this); } void GLImageSync::WillModifyTexImage() { if (buffer_) buffer_->WillWrite(this); } void GLImageSync::DidModifyTexImage() { if (buffer_) buffer_->DidWrite(this); } void GLImageSync::SetReleaseAfterUse() { NOTREACHED(); } #if !defined(OS_MACOSX) class NativeImageBufferEGL : public NativeImageBuffer { public: static scoped_refptr<NativeImageBufferEGL> Create(GLuint texture_id); private: NativeImageBufferEGL(EGLDisplay display, EGLImageKHR image); virtual ~NativeImageBufferEGL(); virtual void AddClient(gfx::GLImage* client) OVERRIDE; virtual void RemoveClient(gfx::GLImage* client) OVERRIDE; virtual bool IsClient(gfx::GLImage* client) OVERRIDE; virtual void BindToTexture(GLenum target) OVERRIDE; virtual void WillRead(gfx::GLImage* client) OVERRIDE; virtual void WillWrite(gfx::GLImage* client) OVERRIDE; virtual void DidRead(gfx::GLImage* client) OVERRIDE; virtual void DidWrite(gfx::GLImage* client) OVERRIDE; void ClearCompletedReadFencesLocked(); EGLDisplay egl_display_; EGLImageKHR egl_image_; base::Lock lock_; struct ClientInfo { ClientInfo(gfx::GLImage* client); ~ClientInfo(); gfx::GLImage* client; bool needs_wait_before_read; linked_ptr<gfx::GLFence> read_fence; }; std::list<ClientInfo> client_infos_; scoped_ptr<gfx::GLFence> write_fence_; gfx::GLImage* write_client_; DISALLOW_COPY_AND_ASSIGN(NativeImageBufferEGL); }; scoped_refptr<NativeImageBufferEGL> NativeImageBufferEGL::Create( GLuint texture_id) { EGLDisplay egl_display = gfx::GLSurfaceEGL::GetHardwareDisplay(); EGLContext egl_context = eglGetCurrentContext(); DCHECK_NE(EGL_NO_CONTEXT, egl_context); DCHECK_NE(EGL_NO_DISPLAY, egl_display); DCHECK(glIsTexture(texture_id)); DCHECK(gfx::g_driver_egl.ext.b_EGL_KHR_image_base && gfx::g_driver_egl.ext.b_EGL_KHR_gl_texture_2D_image && gfx::g_driver_gl.ext.b_GL_OES_EGL_image && gfx::g_driver_egl.ext.b_EGL_KHR_fence_sync); const EGLint egl_attrib_list[] = { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture_id); EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; // TODO EGLImageKHR egl_image = eglCreateImageKHR( egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list); if (egl_image == EGL_NO_IMAGE_KHR) return NULL; return new NativeImageBufferEGL(egl_display, egl_image); } NativeImageBufferEGL::ClientInfo::ClientInfo(gfx::GLImage* client) : client(client), needs_wait_before_read(true) {} NativeImageBufferEGL::ClientInfo::~ClientInfo() {} NativeImageBufferEGL::NativeImageBufferEGL(EGLDisplay display, EGLImageKHR image) : NativeImageBuffer(), egl_display_(display), egl_image_(image), write_fence_(new gfx::GLFenceEGL(true)), write_client_(NULL) { DCHECK(egl_display_ != EGL_NO_DISPLAY); DCHECK(egl_image_ != EGL_NO_IMAGE_KHR); } NativeImageBufferEGL::~NativeImageBufferEGL() { DCHECK(client_infos_.empty()); if (egl_image_ != EGL_NO_IMAGE_KHR) eglDestroyImageKHR(egl_display_, egl_image_); } void NativeImageBufferEGL::AddClient(gfx::GLImage* client) { base::AutoLock lock(lock_); client_infos_.push_back(ClientInfo(client)); } void NativeImageBufferEGL::RemoveClient(gfx::GLImage* client) { base::AutoLock lock(lock_); if (write_client_ == client) write_client_ = NULL; for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { if (it->client == client) { if (it->read_fence.get()) { it->client = NULL; } else { client_infos_.erase(it); } ClearCompletedReadFencesLocked(); return; } } NOTREACHED(); } bool NativeImageBufferEGL::IsClient(gfx::GLImage* client) { base::AutoLock lock(lock_); for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { if (it->client == client) return true; } return false; } void NativeImageBufferEGL::BindToTexture(GLenum target) { DCHECK(egl_image_ != EGL_NO_IMAGE_KHR); glEGLImageTargetTexture2DOES(target, egl_image_); DCHECK_EQ(static_cast<EGLint>(EGL_SUCCESS), eglGetError()); DCHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError()); } void NativeImageBufferEGL::WillRead(gfx::GLImage* client) { base::AutoLock lock(lock_); if (!write_fence_.get() || write_client_ == client) return; for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { if (it->client == client) { if (it->needs_wait_before_read) { it->needs_wait_before_read = false; write_fence_->ServerWait(); } return; } } NOTREACHED(); } void NativeImageBufferEGL::WillWrite(gfx::GLImage* client) { base::AutoLock lock(lock_); if (write_client_ != client) write_fence_->ServerWait(); ClearCompletedReadFencesLocked(); for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { if (it->read_fence.get() && it->client != client) it->read_fence->ServerWait(); } } void NativeImageBufferEGL::DidRead(gfx::GLImage* client) { base::AutoLock lock(lock_); ClearCompletedReadFencesLocked(); for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { if (it->client == client) { it->read_fence = make_linked_ptr(new gfx::GLFenceEGL(true)); return; } } NOTREACHED(); } void NativeImageBufferEGL::DidWrite(gfx::GLImage* client) { base::AutoLock lock(lock_); // Sharing semantics require the client to flush in order to make changes // visible to other clients. write_fence_.reset(new gfx::GLFenceEGL(false)); write_client_ = client; for (std::list<ClientInfo>::iterator it = client_infos_.begin(); it != client_infos_.end(); it++) { it->needs_wait_before_read = true; } } void NativeImageBufferEGL::ClearCompletedReadFencesLocked() { lock_.AssertAcquired(); std::list<ClientInfo>::iterator it = client_infos_.begin(); while (it != client_infos_.end()) { if (!it->client && it->read_fence->HasCompleted()) { it = client_infos_.erase(it); } else { it++; } } } #endif class NativeImageBufferStub : public NativeImageBuffer { public: NativeImageBufferStub() : NativeImageBuffer() {} private: virtual ~NativeImageBufferStub() {} virtual void AddClient(gfx::GLImage* client) OVERRIDE {} virtual void RemoveClient(gfx::GLImage* client) OVERRIDE {} virtual bool IsClient(gfx::GLImage* client) OVERRIDE { return true; } virtual void BindToTexture(GLenum target) OVERRIDE {} virtual void WillRead(gfx::GLImage* client) OVERRIDE {} virtual void WillWrite(gfx::GLImage* client) OVERRIDE {} virtual void DidRead(gfx::GLImage* client) OVERRIDE {} virtual void DidWrite(gfx::GLImage* client) OVERRIDE {} DISALLOW_COPY_AND_ASSIGN(NativeImageBufferStub); }; } // anonymous namespace // static scoped_refptr<NativeImageBuffer> NativeImageBuffer::Create(GLuint texture_id) { switch (gfx::GetGLImplementation()) { #if !defined(OS_MACOSX) case gfx::kGLImplementationEGLGLES2: return NativeImageBufferEGL::Create(texture_id); #endif case gfx::kGLImplementationMockGL: return new NativeImageBufferStub; default: NOTREACHED(); return NULL; } } TextureDefinition::LevelInfo::LevelInfo(GLenum target, GLenum internal_format, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, bool cleared) : target(target), internal_format(internal_format), width(width), height(height), depth(depth), border(border), format(format), type(type), cleared(cleared) {} TextureDefinition::LevelInfo::~LevelInfo() {} TextureDefinition::TextureDefinition( GLenum target, Texture* texture, unsigned int version, const scoped_refptr<NativeImageBuffer>& image_buffer) : version_(version), target_(target), image_buffer_(image_buffer ? image_buffer : NativeImageBuffer::Create( texture->service_id())), min_filter_(texture->min_filter()), mag_filter_(texture->mag_filter()), wrap_s_(texture->wrap_s()), wrap_t_(texture->wrap_t()), usage_(texture->usage()), immutable_(texture->IsImmutable()) { // TODO DCHECK(!texture->level_infos_.empty()); DCHECK(!texture->level_infos_[0].empty()); DCHECK(!texture->NeedsMips()); DCHECK(texture->level_infos_[0][0].width); DCHECK(texture->level_infos_[0][0].height); scoped_refptr<gfx::GLImage> gl_image( new GLImageSync(image_buffer_, gfx::Size(texture->level_infos_[0][0].width, texture->level_infos_[0][0].height))); texture->SetLevelImage(NULL, target, 0, gl_image); // TODO: all levels level_infos_.clear(); const Texture::LevelInfo& level = texture->level_infos_[0][0]; LevelInfo info(level.target, level.internal_format, level.width, level.height, level.depth, level.border, level.format, level.type, level.cleared); std::vector<LevelInfo> infos; infos.push_back(info); level_infos_.push_back(infos); } TextureDefinition::~TextureDefinition() { } Texture* TextureDefinition::CreateTexture() const { if (!image_buffer_) return NULL; GLuint texture_id; glGenTextures(1, &texture_id); Texture* texture(new Texture(texture_id)); UpdateTexture(texture); return texture; } void TextureDefinition::UpdateTexture(Texture* texture) const { gfx::ScopedTextureBinder texture_binder(target_, texture->service_id()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t_); if (image_buffer_) image_buffer_->BindToTexture(target_); // We have to make sure the changes are visible to other clients in this share // group. As far as the clients are concerned, the mailbox semantics only // demand a single flush from the client after changes are first made, // and it is not visible to them when another share group boundary is crossed. // We could probably track this and be a bit smarter about when to flush // though. glFlush(); texture->level_infos_.resize(1); for (size_t i = 0; i < level_infos_.size(); i++) { const LevelInfo& base_info = level_infos_[i][0]; const size_t levels_needed = TextureManager::ComputeMipMapCount( base_info.target, base_info.width, base_info.height, base_info.depth); DCHECK(level_infos_.size() <= levels_needed); texture->level_infos_[0].resize(levels_needed); for (size_t n = 0; n < level_infos_.size(); n++) { const LevelInfo& info = level_infos_[i][n]; texture->SetLevelInfo(NULL, info.target, i, info.internal_format, info.width, info.height, info.depth, info.border, info.format, info.type, info.cleared); } } if (image_buffer_) { texture->SetLevelImage( NULL, target_, 0, new GLImageSync( image_buffer_, gfx::Size(level_infos_[0][0].width, level_infos_[0][0].height))); } texture->target_ = target_; texture->SetImmutable(immutable_); texture->min_filter_ = min_filter_; texture->mag_filter_ = mag_filter_; texture->wrap_s_ = wrap_s_; texture->wrap_t_ = wrap_t_; texture->usage_ = usage_; } bool TextureDefinition::Matches(const Texture* texture) const { DCHECK(target_ == texture->target()); if (texture->min_filter_ != min_filter_ || texture->mag_filter_ != mag_filter_ || texture->wrap_s_ != wrap_s_ || texture->wrap_t_ != wrap_t_) { return false; } // All structural changes should have orphaned the texture. if (image_buffer_ && !texture->GetLevelImage(texture->target(), 0)) return false; return true; } } // namespace gles2 } // namespace gpu