// Copyright (c) 2010 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 "app/surface/accelerated_surface_mac.h" #include "app/surface/io_surface_support_mac.h" #include "base/logging.h" #include "gfx/rect.h" AcceleratedSurface::AcceleratedSurface() : gl_context_(NULL), pbuffer_(NULL), allocate_fbo_(false), texture_(0), fbo_(0), depth_stencil_renderbuffer_(0) { } bool AcceleratedSurface::Initialize(CGLContextObj share_context, bool allocate_fbo) { allocate_fbo_ = allocate_fbo; // TODO(kbr): we should reuse the code for PbufferGLContext here instead // of duplicating it. However, in order to do so, we need to move the // GLContext classes out of gpu/ and into gfx/. // Create a 1x1 pbuffer and associated context to bootstrap things static const CGLPixelFormatAttribute attribs[] = { (CGLPixelFormatAttribute) kCGLPFAPBuffer, (CGLPixelFormatAttribute) 0 }; CGLPixelFormatObj pixel_format; GLint num_pixel_formats; if (CGLChoosePixelFormat(attribs, &pixel_format, &num_pixel_formats) != kCGLNoError) { DLOG(ERROR) << "Error choosing pixel format."; return false; } if (!pixel_format) { return false; } CGLContextObj context; CGLError res = CGLCreateContext(pixel_format, share_context, &context); CGLDestroyPixelFormat(pixel_format); if (res != kCGLNoError) { DLOG(ERROR) << "Error creating context."; return false; } CGLPBufferObj pbuffer; if (CGLCreatePBuffer(1, 1, GL_TEXTURE_2D, GL_RGBA, 0, &pbuffer) != kCGLNoError) { CGLDestroyContext(context); DLOG(ERROR) << "Error creating pbuffer."; return false; } if (CGLSetPBuffer(context, pbuffer, 0, 0, 0) != kCGLNoError) { CGLDestroyContext(context); CGLDestroyPBuffer(pbuffer); DLOG(ERROR) << "Error attaching pbuffer to context."; return false; } gl_context_ = context; pbuffer_ = pbuffer; // Now we're ready to handle SetWindowSize 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_.get() && transport_dib_.get()) { dib_free_callback_->Run(transport_dib_->id()); } transport_dib_.reset(); if (gl_context_) CGLDestroyContext(gl_context_); if (pbuffer_) CGLDestroyPBuffer(pbuffer_); } // 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. glFlush(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); } 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, surface_size_.width(), surface_size_.height()); glBindTexture(target, current_texture); } } 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, surface_size_.width(), 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) { CFNumberRef number = CFNumberCreate(NULL, kCFNumberSInt32Type, &value); CFDictionaryAddValue(dictionary, key, number); } void AcceleratedSurface::AllocateRenderBuffers(GLenum target, const gfx::Size& size) { if (!texture_) { // Generate the texture object. glGenTextures(1, &texture_); glBindTexture(target, texture_); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Generate and bind the framebuffer object. glGenFramebuffersEXT(1, &fbo_); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); // Generate (but don't bind) the depth buffer -- we don't need // this bound in order to do offscreen rendering. glGenRenderbuffersEXT(1, &depth_stencil_renderbuffer_); } // Reallocate the depth buffer. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_stencil_renderbuffer_); glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, size.width(), size.height()); // Unbind the renderbuffers. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // 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); if (fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT) { glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_stencil_renderbuffer_); fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); } // Attach the depth and stencil buffer. if (fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT) { glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, 0x8D20, // GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER_EXT, depth_stencil_renderbuffer_); fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); } return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT; } bool AcceleratedSurface::MakeCurrent() { if (CGLGetCurrentContext() != gl_context_) { if (CGLSetCurrentContext(gl_context_) != kCGLNoError) { DLOG(ERROR) << "Unable to make gl context current."; return false; } } return true; } void AcceleratedSurface::Clear(const gfx::Rect& rect) { glClearColor(1.0, 1.0, 1.0, 1.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); } uint64 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; } IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); if (!io_surface_support) return 0; // Caller can try using SetWindowSizeForTransportDIB(). if (!MakeCurrent()) return 0; // 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, size); } else if (!texture_) { // Generate the texture object. glGenTextures(1, &texture_); glBindTexture(target, texture_); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } // Allocate a new IOSurface, which is the GPU resource that can be // shared across processes. scoped_cftyperef properties; properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); AddIntegerValue(properties, io_surface_support->GetKIOSurfaceWidth(), size.width()); AddIntegerValue(properties, io_surface_support->GetKIOSurfaceHeight(), 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; io_surface_support->CGLTexImageIOSurface2D(gl_context_, target, GL_RGBA, size.width(), size.height(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, io_surface_.get(), plane); if (allocate_fbo_) { // Set up the frame buffer object. SetupFrameBufferObject(target); } surface_size_ = 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. return io_surface_support->IOSurfaceGetID(io_surface_); } 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; // Release the old TransportDIB in the browser. if (dib_free_callback_.get() && transport_dib_.get()) { dib_free_callback_->Run(transport_dib_->id()); } transport_dib_.reset(); // Ask the renderer to create a TransportDIB. size_t dib_size = size.width() * 4 * size.height(); // 4 bytes per pixel. TransportDIB::Handle dib_handle; if (dib_alloc_callback_.get()) { dib_alloc_callback_->Run(dib_size, &dib_handle); } if (!TransportDIB::is_valid(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_) { // Set up the render buffers and reserve enough space on the card for the // framebuffer texture. GLenum target = GL_TEXTURE_RECTANGLE_ARB; AllocateRenderBuffers(target, size); glTexImage2D(target, 0, // mipmap level 0 GL_RGBA8, // internal pixel format size.width(), 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( Callback2::Type* allocator, Callback1::Type* deallocator) { dib_alloc_callback_.reset(allocator); dib_free_callback_.reset(deallocator); }