- add sources.
[platform/framework/web/crosswalk.git] / src / ui / surface / accelerated_surface_mac.cc
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, &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
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 }