| // 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/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" |
| #include "ui/surface/io_surface_support_mac.h" |
| |
| 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; |
| |
| // Ensure GL is initialized before trying to create an offscreen GL context. |
| if (!gfx::GLSurface::InitializeOneOff()) |
| return false; |
| |
| // 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( |
| false, 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. |
| |
| // Release the old TransportDIB in the browser. |
| if (!dib_free_callback_.is_null() && transport_dib_.get()) { |
| dib_free_callback_.Run(transport_dib_->id()); |
| } |
| transport_dib_.reset(); |
| |
| 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(); |
| } |
| } else if (transport_dib_.get() != NULL) { |
| // Pre-Mac OS X 10.6, fetch the rendered image from the current frame |
| // buffer and copy it into the TransportDIB. |
| // TODO(dspringer): There are a couple of options that can speed this up. |
| // First is to use async reads into a PBO, second is to use SPI that |
| // allows many tasks to access the same CGSSurface. |
| void* pixel_memory = transport_dib_->memory(); |
| if (pixel_memory) { |
| // Note that glReadPixels does an implicit glFlush(). |
| glReadPixels(0, |
| 0, |
| real_surface_size_.width(), |
| real_surface_size_.height(), |
| GL_BGRA, // This pixel format should have no conversion. |
| GL_UNSIGNED_INT_8_8_8_8_REV, |
| pixel_memory); |
| } |
| } |
| } |
| |
| 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::mac::ScopedCFTypeRef<CFNumberRef> 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; |
| |
| IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); |
| if (!io_surface_support) |
| return 0; // Caller can try using SetWindowSizeForTransportDIB(). |
| |
| 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::mac::ScopedCFTypeRef<CFMutableDictionaryRef> properties; |
| properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, |
| 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| AddIntegerValue(properties, |
| io_surface_support->GetKIOSurfaceWidth(), |
| clamped_size.width()); |
| AddIntegerValue(properties, |
| io_surface_support->GetKIOSurfaceHeight(), |
| clamped_size.height()); |
| AddIntegerValue(properties, |
| io_surface_support->GetKIOSurfaceBytesPerElement(), 4); |
| AddBooleanValue(properties, |
| io_surface_support->GetKIOSurfaceIsGlobal(), 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(io_surface_support->IOSurfaceCreate(properties)); |
| |
| // Don't think we need to identify a plane. |
| GLuint plane = 0; |
| CGLError error = io_surface_support->CGLTexImageIOSurface2D( |
| static_cast<CGLContextObj>(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_ = io_surface_support->IOSurfaceGetID(io_surface_); |
| return io_surface_id_; |
| } |
| |
| uint32 AcceleratedSurface::GetSurfaceId() { |
| return io_surface_id_; |
| } |
| |
| TransportDIB::Handle AcceleratedSurface::SetTransportDIBSize( |
| const gfx::Size& size) { |
| if (surface_size_ == size) { |
| // Return an invalid handle to indicate to the caller that no new backing |
| // store allocation occurred. |
| return TransportDIB::DefaultHandleValue(); |
| } |
| surface_size_ = size; |
| gfx::Size clamped_size = ClampToValidDimensions(size); |
| real_surface_size_ = clamped_size; |
| |
| // Release the old TransportDIB in the browser. |
| if (!dib_free_callback_.is_null() && transport_dib_.get()) { |
| dib_free_callback_.Run(transport_dib_->id()); |
| } |
| transport_dib_.reset(); |
| |
| // Ask the renderer to create a TransportDIB. |
| size_t dib_size = |
| clamped_size.width() * 4 * clamped_size.height(); // 4 bytes per pixel. |
| TransportDIB::Handle dib_handle; |
| if (!dib_alloc_callback_.is_null()) { |
| dib_alloc_callback_.Run(dib_size, &dib_handle); |
| } |
| if (!TransportDIB::is_valid_handle(dib_handle)) { |
| // If the allocator fails, it means the DIB was not created in the browser, |
| // so there is no need to run the deallocator here. |
| return TransportDIB::DefaultHandleValue(); |
| } |
| transport_dib_.reset(TransportDIB::Map(dib_handle)); |
| if (transport_dib_.get() == NULL) { |
| // TODO(dspringer): if the Map() fails, should the deallocator be run so |
| // that the DIB is deallocated in the browser? |
| return TransportDIB::DefaultHandleValue(); |
| } |
| |
| if (allocate_fbo_) { |
| DCHECK(gl_context_->IsCurrent(gl_surface_.get())); |
| // Set up the render buffers and reserve enough space on the card for the |
| // framebuffer texture. |
| GLenum target = GL_TEXTURE_RECTANGLE_ARB; |
| AllocateRenderBuffers(target, clamped_size); |
| glTexImage2D(target, |
| 0, // mipmap level 0 |
| GL_RGBA8, // internal pixel format |
| clamped_size.width(), |
| clamped_size.height(), |
| 0, // 0 border |
| GL_BGRA, // Used for consistency |
| GL_UNSIGNED_INT_8_8_8_8_REV, |
| NULL); // No data, just reserve room on the card. |
| SetupFrameBufferObject(target); |
| } |
| return transport_dib_->handle(); |
| } |
| |
| void AcceleratedSurface::SetTransportDIBAllocAndFree( |
| const base::Callback<void(size_t, TransportDIB::Handle*)>& allocator, |
| const base::Callback<void(TransportDIB::Id)>& deallocator) { |
| dib_alloc_callback_ = allocator; |
| dib_free_callback_ = deallocator; |
| } |