OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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/gfx/surface/accelerated_surface_mac.h" | |
6 | |
7 #include "base/logging.h" | |
8 #include "base/mac/scoped_cftyperef.h" | |
9 #include "ui/gfx/gl/gl_bindings.h" | |
10 #include "ui/gfx/gl/gl_context.h" | |
11 #include "ui/gfx/gl/gl_implementation.h" | |
12 #include "ui/gfx/gl/gl_surface.h" | |
13 #include "ui/gfx/gl/scoped_make_current.h" | |
14 #include "ui/gfx/rect.h" | |
15 #include "ui/gfx/surface/io_surface_support_mac.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( | |
43 false, 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 | |
73 // Release the old TransportDIB in the browser. | |
74 if (!dib_free_callback_.is_null() && transport_dib_.get()) { | |
75 dib_free_callback_.Run(transport_dib_->id()); | |
76 } | |
77 transport_dib_.reset(); | |
78 | |
79 gl_context_ = NULL; | |
80 gl_surface_ = NULL; | |
81 } | |
82 | |
83 // Call after making changes to the surface which require a visual update. | |
84 // Makes the rendering show up in other processes. | |
85 void AcceleratedSurface::SwapBuffers() { | |
86 if (io_surface_.get() != NULL) { | |
87 if (allocate_fbo_) { | |
88 // Bind and unbind the framebuffer to make changes to the | |
89 // IOSurface show up in the other process. | |
90 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); | |
91 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); | |
92 glFlush(); | |
93 } else { | |
94 // Copy the current framebuffer's contents into our "live" texture. | |
95 // Note that the current GL context might not be ours at this point! | |
96 // This is deliberate, so that surrounding code using GL can produce | |
97 // rendering results consumed by the AcceleratedSurface. | |
98 // Need to save and restore OpenGL state around this call. | |
99 GLint current_texture = 0; | |
100 GLenum target_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB; | |
101 GLenum target = GL_TEXTURE_RECTANGLE_ARB; | |
102 glGetIntegerv(target_binding, ¤t_texture); | |
103 glBindTexture(target, texture_); | |
104 glCopyTexSubImage2D(target, 0, | |
105 0, 0, | |
106 0, 0, | |
107 real_surface_size_.width(), | |
108 real_surface_size_.height()); | |
109 glBindTexture(target, current_texture); | |
110 // This flush is absolutely essential -- it guarantees that the | |
111 // rendering results are seen by the other process. | |
112 glFlush(); | |
113 } | |
114 } else if (transport_dib_.get() != NULL) { | |
115 // Pre-Mac OS X 10.6, fetch the rendered image from the current frame | |
116 // buffer and copy it into the TransportDIB. | |
117 // TODO(dspringer): There are a couple of options that can speed this up. | |
118 // First is to use async reads into a PBO, second is to use SPI that | |
119 // allows many tasks to access the same CGSSurface. | |
120 void* pixel_memory = transport_dib_->memory(); | |
121 if (pixel_memory) { | |
122 // Note that glReadPixels does an implicit glFlush(). | |
123 glReadPixels(0, | |
124 0, | |
125 real_surface_size_.width(), | |
126 real_surface_size_.height(), | |
127 GL_BGRA, // This pixel format should have no conversion. | |
128 GL_UNSIGNED_INT_8_8_8_8_REV, | |
129 pixel_memory); | |
130 } | |
131 } | |
132 } | |
133 | |
134 static void AddBooleanValue(CFMutableDictionaryRef dictionary, | |
135 const CFStringRef key, | |
136 bool value) { | |
137 CFDictionaryAddValue(dictionary, key, | |
138 (value ? kCFBooleanTrue : kCFBooleanFalse)); | |
139 } | |
140 | |
141 static void AddIntegerValue(CFMutableDictionaryRef dictionary, | |
142 const CFStringRef key, | |
143 int32 value) { | |
144 base::mac::ScopedCFTypeRef<CFNumberRef> number( | |
145 CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); | |
146 CFDictionaryAddValue(dictionary, key, number.get()); | |
147 } | |
148 | |
149 // Creates a new OpenGL texture object bound to the given texture target. | |
150 // Caller owns the returned texture. | |
151 static GLuint CreateTexture(GLenum target) { | |
152 GLuint texture = 0; | |
153 glGenTextures(1, &texture); | |
154 glBindTexture(target, texture); | |
155 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
156 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
157 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
158 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
159 return texture; | |
160 } | |
161 | |
162 void AcceleratedSurface::AllocateRenderBuffers(GLenum target, | |
163 const gfx::Size& size) { | |
164 if (!texture_) { | |
165 // Generate the texture object. | |
166 texture_ = CreateTexture(target); | |
167 // Generate and bind the framebuffer object. | |
168 glGenFramebuffersEXT(1, &fbo_); | |
169 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); | |
170 } | |
171 | |
172 // Make sure that subsequent set-up code affects the render texture. | |
173 glBindTexture(target, texture_); | |
174 } | |
175 | |
176 bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) { | |
177 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_); | |
178 GLenum fbo_status; | |
179 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, | |
180 GL_COLOR_ATTACHMENT0_EXT, | |
181 target, | |
182 texture_, | |
183 0); | |
184 fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); | |
185 return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT; | |
186 } | |
187 | |
188 gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) { | |
189 return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1)); | |
190 } | |
191 | |
192 bool AcceleratedSurface::MakeCurrent() { | |
193 if (!gl_context_.get()) | |
194 return false; | |
195 return gl_context_->MakeCurrent(gl_surface_.get()); | |
196 } | |
197 | |
198 void AcceleratedSurface::Clear(const gfx::Rect& rect) { | |
199 DCHECK(gl_context_->IsCurrent(gl_surface_.get())); | |
200 glClearColor(0, 0, 0, 0); | |
201 glViewport(0, 0, rect.width(), rect.height()); | |
202 glMatrixMode(GL_PROJECTION); | |
203 glLoadIdentity(); | |
204 glOrtho(0, rect.width(), 0, rect.height(), -1, 1); | |
205 glClear(GL_COLOR_BUFFER_BIT); | |
206 } | |
207 | |
208 uint32 AcceleratedSurface::SetSurfaceSize(const gfx::Size& size) { | |
209 if (surface_size_ == size) { | |
210 // Return 0 to indicate to the caller that no new backing store | |
211 // allocation occurred. | |
212 return 0; | |
213 } | |
214 | |
215 // Only support IO surfaces if the GL implementation is the native desktop GL. | |
216 // IO surfaces will not work with, for example, OSMesa software renderer | |
217 // GL contexts. | |
218 if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL) | |
219 return 0; | |
220 | |
221 IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); | |
222 if (!io_surface_support) | |
223 return 0; // Caller can try using SetWindowSizeForTransportDIB(). | |
224 | |
225 gfx::ScopedMakeCurrent make_current(gl_context_.get(), gl_surface_.get()); | |
226 if (!make_current.Succeeded()) | |
227 return 0; | |
228 | |
229 gfx::Size clamped_size = ClampToValidDimensions(size); | |
230 | |
231 // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on | |
232 // Mac OS X and is required for IOSurface interoperability. | |
233 GLenum target = GL_TEXTURE_RECTANGLE_ARB; | |
234 if (allocate_fbo_) { | |
235 AllocateRenderBuffers(target, clamped_size); | |
236 } else if (!texture_) { | |
237 // Generate the texture object. | |
238 texture_ = CreateTexture(target); | |
239 } | |
240 | |
241 // Allocate a new IOSurface, which is the GPU resource that can be | |
242 // shared across processes. | |
243 base::mac::ScopedCFTypeRef<CFMutableDictionaryRef> properties; | |
244 properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault, | |
245 0, | |
246 &kCFTypeDictionaryKeyCallBacks, | |
247 &kCFTypeDictionaryValueCallBacks)); | |
248 AddIntegerValue(properties, | |
249 io_surface_support->GetKIOSurfaceWidth(), | |
250 clamped_size.width()); | |
251 AddIntegerValue(properties, | |
252 io_surface_support->GetKIOSurfaceHeight(), | |
253 clamped_size.height()); | |
254 AddIntegerValue(properties, | |
255 io_surface_support->GetKIOSurfaceBytesPerElement(), 4); | |
256 AddBooleanValue(properties, | |
257 io_surface_support->GetKIOSurfaceIsGlobal(), true); | |
258 // I believe we should be able to unreference the IOSurfaces without | |
259 // synchronizing with the browser process because they are | |
260 // ultimately reference counted by the operating system. | |
261 io_surface_.reset(io_surface_support->IOSurfaceCreate(properties)); | |
262 | |
263 // Don't think we need to identify a plane. | |
264 GLuint plane = 0; | |
265 CGLError error = io_surface_support->CGLTexImageIOSurface2D( | |
266 static_cast<CGLContextObj>(gl_context_->GetHandle()), | |
267 target, | |
268 GL_RGBA, | |
269 clamped_size.width(), | |
270 clamped_size.height(), | |
271 GL_BGRA, | |
272 GL_UNSIGNED_INT_8_8_8_8_REV, | |
273 io_surface_.get(), | |
274 plane); | |
275 if (error != kCGLNoError) { | |
276 DLOG(ERROR) << "CGL error " << error << " during CGLTexImageIOSurface2D"; | |
277 } | |
278 if (allocate_fbo_) { | |
279 // Set up the frame buffer object. | |
280 if (!SetupFrameBufferObject(target)) { | |
281 DLOG(ERROR) << "Failed to set up frame buffer object"; | |
282 } | |
283 } | |
284 surface_size_ = size; | |
285 real_surface_size_ = clamped_size; | |
286 | |
287 // Now send back an identifier for the IOSurface. We originally | |
288 // intended to send back a mach port from IOSurfaceCreateMachPort | |
289 // but it looks like Chrome IPC would need to be modified to | |
290 // properly send mach ports between processes. For the time being we | |
291 // make our IOSurfaces global and send back their identifiers. On | |
292 // the browser process side the identifier is reconstituted into an | |
293 // IOSurface for on-screen rendering. | |
294 io_surface_id_ = io_surface_support->IOSurfaceGetID(io_surface_); | |
295 return io_surface_id_; | |
296 } | |
297 | |
298 uint32 AcceleratedSurface::GetSurfaceId() { | |
299 return io_surface_id_; | |
300 } | |
301 | |
302 TransportDIB::Handle AcceleratedSurface::SetTransportDIBSize( | |
303 const gfx::Size& size) { | |
304 if (surface_size_ == size) { | |
305 // Return an invalid handle to indicate to the caller that no new backing | |
306 // store allocation occurred. | |
307 return TransportDIB::DefaultHandleValue(); | |
308 } | |
309 surface_size_ = size; | |
310 gfx::Size clamped_size = ClampToValidDimensions(size); | |
311 real_surface_size_ = clamped_size; | |
312 | |
313 // Release the old TransportDIB in the browser. | |
314 if (!dib_free_callback_.is_null() && transport_dib_.get()) { | |
315 dib_free_callback_.Run(transport_dib_->id()); | |
316 } | |
317 transport_dib_.reset(); | |
318 | |
319 // Ask the renderer to create a TransportDIB. | |
320 size_t dib_size = | |
321 clamped_size.width() * 4 * clamped_size.height(); // 4 bytes per pixel. | |
322 TransportDIB::Handle dib_handle; | |
323 if (!dib_alloc_callback_.is_null()) { | |
324 dib_alloc_callback_.Run(dib_size, &dib_handle); | |
325 } | |
326 if (!TransportDIB::is_valid_handle(dib_handle)) { | |
327 // If the allocator fails, it means the DIB was not created in the browser, | |
328 // so there is no need to run the deallocator here. | |
329 return TransportDIB::DefaultHandleValue(); | |
330 } | |
331 transport_dib_.reset(TransportDIB::Map(dib_handle)); | |
332 if (transport_dib_.get() == NULL) { | |
333 // TODO(dspringer): if the Map() fails, should the deallocator be run so | |
334 // that the DIB is deallocated in the browser? | |
335 return TransportDIB::DefaultHandleValue(); | |
336 } | |
337 | |
338 if (allocate_fbo_) { | |
339 DCHECK(gl_context_->IsCurrent(gl_surface_.get())); | |
340 // Set up the render buffers and reserve enough space on the card for the | |
341 // framebuffer texture. | |
342 GLenum target = GL_TEXTURE_RECTANGLE_ARB; | |
343 AllocateRenderBuffers(target, clamped_size); | |
344 glTexImage2D(target, | |
345 0, // mipmap level 0 | |
346 GL_RGBA8, // internal pixel format | |
347 clamped_size.width(), | |
348 clamped_size.height(), | |
349 0, // 0 border | |
350 GL_BGRA, // Used for consistency | |
351 GL_UNSIGNED_INT_8_8_8_8_REV, | |
352 NULL); // No data, just reserve room on the card. | |
353 SetupFrameBufferObject(target); | |
354 } | |
355 return transport_dib_->handle(); | |
356 } | |
357 | |
358 void AcceleratedSurface::SetTransportDIBAllocAndFree( | |
359 const base::Callback<void(size_t, TransportDIB::Handle*)>& allocator, | |
360 const base::Callback<void(TransportDIB::Id)>& deallocator) { | |
361 dib_alloc_callback_ = allocator; | |
362 dib_free_callback_ = deallocator; | |
363 } | |
OLD | NEW |