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
19AcceleratedSurface::AcceleratedSurface()
20    : io_surface_id_(0),
21      allocate_fbo_(false),
22      texture_(0),
23      fbo_(0) {
24}
25
26AcceleratedSurface::~AcceleratedSurface() {}
27
28bool 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
67void 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.
78void 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, &current_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
110static void AddBooleanValue(CFMutableDictionaryRef dictionary,
111                            const CFStringRef key,
112                            bool value) {
113  CFDictionaryAddValue(dictionary, key,
114                       (value ? kCFBooleanTrue : kCFBooleanFalse));
115}
116
117static 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.
127static 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
138void 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
152bool 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
164gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) {
165  return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1));
166}
167
168bool AcceleratedSurface::MakeCurrent() {
169  if (!gl_context_.get())
170    return false;
171  return gl_context_->MakeCurrent(gl_surface_.get());
172}
173
174void 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
184uint32 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
264uint32 AcceleratedSurface::GetSurfaceId() {
265  return io_surface_id_;
266}
267