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