// Copyright (c) 2012 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 "ui/surface/accelerated_surface_mac.h" #include "base/logging.h" #include "base/mac/scoped_cftyperef.h" #include "ui/gfx/geometry/rect.h" #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_implementation.h" #include "ui/gl/gl_surface.h" #include "ui/gl/scoped_make_current.h" // Note that this must be included after gl_bindings.h to avoid conflicts. #include AcceleratedSurface::AcceleratedSurface() : io_surface_id_(0), allocate_fbo_(false), texture_(0), fbo_(0) { } AcceleratedSurface::~AcceleratedSurface() {} bool AcceleratedSurface::Initialize( gfx::GLContext* share_context, bool allocate_fbo, gfx::GpuPreference gpu_preference) { allocate_fbo_ = allocate_fbo; // GL should be initialized by content::SupportsCoreAnimationPlugins(). DCHECK_NE(gfx::GetGLImplementation(), gfx::kGLImplementationNone); // Drawing to IOSurfaces via OpenGL only works with Apple's GL and // not with the OSMesa software renderer. if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL && gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL) return false; gl_surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1)); if (!gl_surface_.get()) { Destroy(); return false; } gfx::GLShareGroup* share_group = share_context ? share_context->share_group() : NULL; gl_context_ = gfx::GLContext::CreateGLContext( share_group, gl_surface_.get(), gpu_preference); if (!gl_context_.get()) { Destroy(); return false; } // Now we're ready to handle SetSurfaceSize calls, which will // allocate and/or reallocate the IOSurface and associated offscreen // OpenGL structures for rendering. return true; } void AcceleratedSurface::Destroy() { // The FBO and texture objects will be destroyed when the OpenGL context, // and any other contexts sharing resources with it, is. We don't want to // make the context current one last time here just in order to delete // these objects. gl_context_ = NULL; gl_surface_ = NULL; } // Call after making changes to the surface which require a visual update. // Makes the rendering show up in other processes. void AcceleratedSurface::SwapBuffers() { if (io_surface_.get() != NULL) { if (allocate_fbo_) { // Bind and unbind the framebuffer to make changes to the // IOSurface show up in the other process. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); glFlush(); } else { // Copy the current framebuffer's contents into our "live" texture. // Note that the current GL context might not be ours at this point! // This is deliberate, so that surrounding code using GL can produce // rendering results consumed by the AcceleratedSurface. // Need to save and restore OpenGL state around this call. GLint current_texture = 0; GLenum target_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB; GLenum target = GL_TEXTURE_RECTANGLE_ARB; glGetIntegerv(target_binding, ¤t_texture); glBindTexture(target, texture_); glCopyTexSubImage2D(target, 0, 0, 0, 0, 0, real_surface_size_.width(), real_surface_size_.height()); glBindTexture(target, current_texture); // This flush is absolutely essential -- it guarantees that the // rendering results are seen by the other process. glFlush(); } } } static void AddBooleanValue(CFMutableDictionaryRef dictionary, const CFStringRef key, bool value) { CFDictionaryAddValue(dictionary, key, (value ? kCFBooleanTrue : kCFBooleanFalse)); } static void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef key, int32 value) { base::ScopedCFTypeRef number( CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); CFDictionaryAddValue(dictionary, key, number.get()); } // Creates a new OpenGL texture object bound to the given texture target. // Caller owns the returned texture. static GLuint CreateTexture(GLenum target) { GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(target, texture); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); return texture; } void AcceleratedSurface::AllocateRenderBuffers(GLenum target, const gfx::Size& size) { if (!texture_) { // Generate the texture object. texture_ = CreateTexture(target); // Generate and bind the framebuffer object. glGenFramebuffersEXT(1, &fbo_); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); } // Make sure that subsequent set-up code affects the render texture. glBindTexture(target, texture_); } bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); GLenum fbo_status; glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, texture_, 0); fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT; } gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) { return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1)); } bool AcceleratedSurface::MakeCurrent() { if (!gl_context_.get()) return false; return gl_context_->MakeCurrent(gl_surface_.get()); } void AcceleratedSurface::Clear(const gfx::Rect& rect) { DCHECK(gl_context_->IsCurrent(gl_surface_.get())); glClearColor(0, 0, 0, 0); glViewport(0, 0, rect.width(), rect.height()); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, rect.width(), 0, rect.height(), -1, 1); glClear(GL_COLOR_BUFFER_BIT); } uint32 AcceleratedSurface::SetSurfaceSize(const gfx::Size& size) { if (surface_size_ == size) { // Return 0 to indicate to the caller that no new backing store // allocation occurred. return 0; } // Only support IO surfaces if the GL implementation is the native desktop GL. // IO surfaces will not work with, for example, OSMesa software renderer // GL contexts. if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL) return 0; ui::ScopedMakeCurrent make_current(gl_context_.get(), gl_surface_.get()); if (!make_current.Succeeded()) return 0; gfx::Size clamped_size = ClampToValidDimensions(size); // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on // Mac OS X and is required for IOSurface interoperability. GLenum target = GL_TEXTURE_RECTANGLE_ARB; if (allocate_fbo_) { AllocateRenderBuffers(target, clamped_size); } else if (!texture_) { // Generate the texture object. texture_ = CreateTexture(target); } // Allocate a new IOSurface, which is the GPU resource that can be // shared across processes. base::ScopedCFTypeRef properties; properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); AddIntegerValue(properties, kIOSurfaceWidth, clamped_size.width()); AddIntegerValue(properties, kIOSurfaceHeight, clamped_size.height()); AddIntegerValue(properties, kIOSurfaceBytesPerElement, 4); AddBooleanValue(properties, kIOSurfaceIsGlobal, true); // I believe we should be able to unreference the IOSurfaces without // synchronizing with the browser process because they are // ultimately reference counted by the operating system. io_surface_.reset(IOSurfaceCreate(properties)); // Don't think we need to identify a plane. GLuint plane = 0; CGLError error = CGLTexImageIOSurface2D( static_cast(gl_context_->GetHandle()), target, GL_RGBA, clamped_size.width(), clamped_size.height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, io_surface_.get(), plane); if (error != kCGLNoError) { DLOG(ERROR) << "CGL error " << error << " during CGLTexImageIOSurface2D"; } if (allocate_fbo_) { // Set up the frame buffer object. if (!SetupFrameBufferObject(target)) { DLOG(ERROR) << "Failed to set up frame buffer object"; } } surface_size_ = size; real_surface_size_ = clamped_size; // Now send back an identifier for the IOSurface. We originally // intended to send back a mach port from IOSurfaceCreateMachPort // but it looks like Chrome IPC would need to be modified to // properly send mach ports between processes. For the time being we // make our IOSurfaces global and send back their identifiers. On // the browser process side the identifier is reconstituted into an // IOSurface for on-screen rendering. io_surface_id_ = IOSurfaceGetID(io_surface_); return io_surface_id_; } uint32 AcceleratedSurface::GetSurfaceId() { return io_surface_id_; }