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
17AcceleratedSurface::AcceleratedSurface()
18    : io_surface_id_(0),
19      allocate_fbo_(false),
20      texture_(0),
21      fbo_(0) {
22}
23
24AcceleratedSurface::~AcceleratedSurface() {}
25
26bool 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
66void 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.
77void 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, &current_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
109static void AddBooleanValue(CFMutableDictionaryRef dictionary,
110                            const CFStringRef key,
111                            bool value) {
112  CFDictionaryAddValue(dictionary, key,
113                       (value ? kCFBooleanTrue : kCFBooleanFalse));
114}
115
116static 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.
126static 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
137void 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
151bool 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
163gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) {
164  return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1));
165}
166
167bool AcceleratedSurface::MakeCurrent() {
168  if (!gl_context_.get())
169    return false;
170  return gl_context_->MakeCurrent(gl_surface_.get());
171}
172
173void 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
183uint32 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
273uint32 AcceleratedSurface::GetSurfaceId() {
274  return io_surface_id_;
275}
276