1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/surface/accelerated_surface_mac.h" 6 7 #include "base/logging.h" 8 #include "base/mac/scoped_cftyperef.h" 9 #include "ui/gfx/rect.h" 10 #include "ui/gl/gl_bindings.h" 11 #include "ui/gl/gl_context.h" 12 #include "ui/gl/gl_implementation.h" 13 #include "ui/gl/gl_surface.h" 14 #include "ui/gl/scoped_make_current.h" 15 16 // Note that this must be included after gl_bindings.h to avoid conflicts. 17 #include <OpenGL/CGLIOSurface.h> 18 19 AcceleratedSurface::AcceleratedSurface() 20 : io_surface_id_(0), 21 allocate_fbo_(false), 22 texture_(0), 23 fbo_(0) { 24 } 25 26 AcceleratedSurface::~AcceleratedSurface() {} 27 28 bool AcceleratedSurface::Initialize( 29 gfx::GLContext* share_context, 30 bool allocate_fbo, 31 gfx::GpuPreference gpu_preference) { 32 allocate_fbo_ = allocate_fbo; 33 34 // GL should be initialized by content::SupportsCoreAnimationPlugins(). 35 DCHECK_NE(gfx::GetGLImplementation(), gfx::kGLImplementationNone); 36 37 // Drawing to IOSurfaces via OpenGL only works with Apple's GL and 38 // not with the OSMesa software renderer. 39 if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL && 40 gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL) 41 return false; 42 43 gl_surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1)); 44 if (!gl_surface_.get()) { 45 Destroy(); 46 return false; 47 } 48 49 gfx::GLShareGroup* share_group = 50 share_context ? share_context->share_group() : NULL; 51 52 gl_context_ = gfx::GLContext::CreateGLContext( 53 share_group, 54 gl_surface_.get(), 55 gpu_preference); 56 if (!gl_context_.get()) { 57 Destroy(); 58 return false; 59 } 60 61 // Now we're ready to handle SetSurfaceSize calls, which will 62 // allocate and/or reallocate the IOSurface and associated offscreen 63 // OpenGL structures for rendering. 64 return true; 65 } 66 67 void AcceleratedSurface::Destroy() { 68 // The FBO and texture objects will be destroyed when the OpenGL context, 69 // and any other contexts sharing resources with it, is. We don't want to 70 // make the context current one last time here just in order to delete 71 // these objects. 72 gl_context_ = NULL; 73 gl_surface_ = NULL; 74 } 75 76 // Call after making changes to the surface which require a visual update. 77 // Makes the rendering show up in other processes. 78 void AcceleratedSurface::SwapBuffers() { 79 if (io_surface_.get() != NULL) { 80 if (allocate_fbo_) { 81 // Bind and unbind the framebuffer to make changes to the 82 // IOSurface show up in the other process. 83 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); 84 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); 85 glFlush(); 86 } else { 87 // Copy the current framebuffer's contents into our "live" texture. 88 // Note that the current GL context might not be ours at this point! 89 // This is deliberate, so that surrounding code using GL can produce 90 // rendering results consumed by the AcceleratedSurface. 91 // Need to save and restore OpenGL state around this call. 92 GLint current_texture = 0; 93 GLenum target_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB; 94 GLenum target = GL_TEXTURE_RECTANGLE_ARB; 95 glGetIntegerv(target_binding, ¤t_texture); 96 glBindTexture(target, texture_); 97 glCopyTexSubImage2D(target, 0, 98 0, 0, 99 0, 0, 100 real_surface_size_.width(), 101 real_surface_size_.height()); 102 glBindTexture(target, current_texture); 103 // This flush is absolutely essential -- it guarantees that the 104 // rendering results are seen by the other process. 105 glFlush(); 106 } 107 } 108 } 109 110 static void AddBooleanValue(CFMutableDictionaryRef dictionary, 111 const CFStringRef key, 112 bool value) { 113 CFDictionaryAddValue(dictionary, key, 114 (value ? kCFBooleanTrue : kCFBooleanFalse)); 115 } 116 117 static void AddIntegerValue(CFMutableDictionaryRef dictionary, 118 const CFStringRef key, 119 int32 value) { 120 base::ScopedCFTypeRef<CFNumberRef> number( 121 CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); 122 CFDictionaryAddValue(dictionary, key, number.get()); 123 } 124 125 // Creates a new OpenGL texture object bound to the given texture target. 126 // Caller owns the returned texture. 127 static GLuint CreateTexture(GLenum target) { 128 GLuint texture = 0; 129 glGenTextures(1, &texture); 130 glBindTexture(target, texture); 131 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 132 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 133 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 134 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 135 return texture; 136 } 137 138 void AcceleratedSurface::AllocateRenderBuffers(GLenum target, 139 const gfx::Size& size) { 140 if (!texture_) { 141 // Generate the texture object. 142 texture_ = CreateTexture(target); 143 // Generate and bind the framebuffer object. 144 glGenFramebuffersEXT(1, &fbo_); 145 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); 146 } 147 148 // Make sure that subsequent set-up code affects the render texture. 149 glBindTexture(target, texture_); 150 } 151 152 bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) { 153 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); 154 GLenum fbo_status; 155 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 156 GL_COLOR_ATTACHMENT0_EXT, 157 target, 158 texture_, 159 0); 160 fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); 161 return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT; 162 } 163 164 gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) { 165 return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1)); 166 } 167 168 bool AcceleratedSurface::MakeCurrent() { 169 if (!gl_context_.get()) 170 return false; 171 return gl_context_->MakeCurrent(gl_surface_.get()); 172 } 173 174 void AcceleratedSurface::Clear(const gfx::Rect& rect) { 175 DCHECK(gl_context_->IsCurrent(gl_surface_.get())); 176 glClearColor(0, 0, 0, 0); 177 glViewport(0, 0, rect.width(), rect.height()); 178 glMatrixMode(GL_PROJECTION); 179 glLoadIdentity(); 180 glOrtho(0, rect.width(), 0, rect.height(), -1, 1); 181 glClear(GL_COLOR_BUFFER_BIT); 182 } 183 184 uint32 AcceleratedSurface::SetSurfaceSize(const gfx::Size& size) { 185 if (surface_size_ == size) { 186 // Return 0 to indicate to the caller that no new backing store 187 // allocation occurred. 188 return 0; 189 } 190 191 // Only support IO surfaces if the GL implementation is the native desktop GL. 192 // IO surfaces will not work with, for example, OSMesa software renderer 193 // GL contexts. 194 if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL) 195 return 0; 196 197 ui::ScopedMakeCurrent make_current(gl_context_.get(), gl_surface_.get()); 198 if (!make_current.Succeeded()) 199 return 0; 200 201 gfx::Size clamped_size = ClampToValidDimensions(size); 202 203 // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on 204 // Mac OS X and is required for IOSurface interoperability. 205 GLenum target = GL_TEXTURE_RECTANGLE_ARB; 206 if (allocate_fbo_) { 207 AllocateRenderBuffers(target, clamped_size); 208 } else if (!texture_) { 209 // Generate the texture object. 210 texture_ = CreateTexture(target); 211 } 212 213 // Allocate a new IOSurface, which is the GPU resource that can be 214 // shared across processes. 215 base::ScopedCFTypeRef<CFMutableDictionaryRef> properties; 216 properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, 217 0, 218 &kCFTypeDictionaryKeyCallBacks, 219 &kCFTypeDictionaryValueCallBacks)); 220 AddIntegerValue(properties, kIOSurfaceWidth, clamped_size.width()); 221 AddIntegerValue(properties, kIOSurfaceHeight, clamped_size.height()); 222 AddIntegerValue(properties, kIOSurfaceBytesPerElement, 4); 223 AddBooleanValue(properties, kIOSurfaceIsGlobal, true); 224 // I believe we should be able to unreference the IOSurfaces without 225 // synchronizing with the browser process because they are 226 // ultimately reference counted by the operating system. 227 io_surface_.reset(IOSurfaceCreate(properties)); 228 229 // Don't think we need to identify a plane. 230 GLuint plane = 0; 231 CGLError error = CGLTexImageIOSurface2D( 232 static_cast<CGLContextObj>(gl_context_->GetHandle()), 233 target, 234 GL_RGBA, 235 clamped_size.width(), 236 clamped_size.height(), 237 GL_BGRA, 238 GL_UNSIGNED_INT_8_8_8_8_REV, 239 io_surface_.get(), 240 plane); 241 if (error != kCGLNoError) { 242 DLOG(ERROR) << "CGL error " << error << " during CGLTexImageIOSurface2D"; 243 } 244 if (allocate_fbo_) { 245 // Set up the frame buffer object. 246 if (!SetupFrameBufferObject(target)) { 247 DLOG(ERROR) << "Failed to set up frame buffer object"; 248 } 249 } 250 surface_size_ = size; 251 real_surface_size_ = clamped_size; 252 253 // Now send back an identifier for the IOSurface. We originally 254 // intended to send back a mach port from IOSurfaceCreateMachPort 255 // but it looks like Chrome IPC would need to be modified to 256 // properly send mach ports between processes. For the time being we 257 // make our IOSurfaces global and send back their identifiers. On 258 // the browser process side the identifier is reconstituted into an 259 // IOSurface for on-screen rendering. 260 io_surface_id_ = IOSurfaceGetID(io_surface_); 261 return io_surface_id_; 262 } 263 264 uint32 AcceleratedSurface::GetSurfaceId() { 265 return io_surface_id_; 266 } 267