OLD | NEW |
| (Empty) |
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 "content/browser/compositor/io_surface_texture_mac.h" | |
6 | |
7 #include <OpenGL/CGLIOSurface.h> | |
8 #include <OpenGL/CGLRenderers.h> | |
9 #include <OpenGL/OpenGL.h> | |
10 #include <OpenGL/gl.h> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/bind_helpers.h" | |
14 #include "base/callback_helpers.h" | |
15 #include "base/debug/trace_event.h" | |
16 #include "base/logging.h" | |
17 #include "base/mac/bind_objc_block.h" | |
18 #include "base/mac/mac_util.h" | |
19 #include "base/message_loop/message_loop.h" | |
20 #include "base/threading/platform_thread.h" | |
21 #include "content/browser/compositor/io_surface_context_mac.h" | |
22 #include "content/browser/gpu/gpu_data_manager_impl.h" | |
23 #include "content/browser/renderer_host/render_widget_host_impl.h" | |
24 #include "content/browser/renderer_host/render_widget_host_view_mac.h" | |
25 #include "content/common/content_constants_internal.h" | |
26 #include "gpu/config/gpu_driver_bug_workaround_type.h" | |
27 #include "media/base/video_util.h" | |
28 #include "third_party/skia/include/core/SkBitmap.h" | |
29 #include "ui/gfx/rect.h" | |
30 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" | |
31 #include "ui/gfx/size_conversions.h" | |
32 #include "ui/gl/gl_context.h" | |
33 | |
34 namespace content { | |
35 | |
36 // static | |
37 scoped_refptr<IOSurfaceTexture> IOSurfaceTexture::Create() { | |
38 scoped_refptr<IOSurfaceContext> offscreen_context = | |
39 IOSurfaceContext::Get( | |
40 IOSurfaceContext::kOffscreenContext); | |
41 if (!offscreen_context.get()) { | |
42 LOG(ERROR) << "Failed to create context for offscreen operations"; | |
43 return NULL; | |
44 } | |
45 | |
46 return new IOSurfaceTexture(offscreen_context); | |
47 } | |
48 | |
49 IOSurfaceTexture::IOSurfaceTexture( | |
50 const scoped_refptr<IOSurfaceContext>& offscreen_context) | |
51 : offscreen_context_(offscreen_context), | |
52 texture_(0), | |
53 gl_error_(GL_NO_ERROR), | |
54 eviction_queue_iterator_(eviction_queue_.Get().end()), | |
55 eviction_has_been_drawn_since_updated_(false) { | |
56 CHECK(offscreen_context_.get()); | |
57 } | |
58 | |
59 IOSurfaceTexture::~IOSurfaceTexture() { | |
60 ReleaseIOSurfaceAndTexture(); | |
61 offscreen_context_ = NULL; | |
62 DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end()); | |
63 } | |
64 | |
65 bool IOSurfaceTexture::DrawIOSurface() { | |
66 TRACE_EVENT0("browser", "IOSurfaceTexture::DrawIOSurface"); | |
67 | |
68 // If we have release the IOSurface, clear the screen to light grey and | |
69 // early-out. | |
70 if (!io_surface_) { | |
71 glClearColor(0.9, 0.9, 0.9, 1); | |
72 glClear(GL_COLOR_BUFFER_BIT); | |
73 return false; | |
74 } | |
75 | |
76 // The viewport is the size of the CALayer, which should always match the | |
77 // IOSurface pixel size. | |
78 GLint viewport[4]; | |
79 glGetIntegerv(GL_VIEWPORT, viewport); | |
80 gfx::Rect viewport_rect(viewport[0], viewport[1], viewport[2], viewport[3]); | |
81 DCHECK_EQ(pixel_size_.ToString(), viewport_rect.size().ToString()); | |
82 | |
83 // Set the projection matrix to match 1 unit to 1 pixel. | |
84 glMatrixMode(GL_PROJECTION); | |
85 glLoadIdentity(); | |
86 glOrtho(0, viewport_rect.width(), 0, viewport_rect.height(), -1, 1); | |
87 glMatrixMode(GL_MODELVIEW); | |
88 glLoadIdentity(); | |
89 | |
90 // Draw a quad the size of the IOSurface. This should cover the full viewport. | |
91 glColor4f(1, 1, 1, 1); | |
92 glEnable(GL_TEXTURE_RECTANGLE_ARB); | |
93 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
94 glBegin(GL_QUADS); | |
95 glTexCoord2f(0, 0); | |
96 glVertex2f(0, 0); | |
97 glTexCoord2f(pixel_size_.width(), 0); | |
98 glVertex2f(pixel_size_.width(), 0); | |
99 glTexCoord2f(pixel_size_.width(), pixel_size_.height()); | |
100 glVertex2f(pixel_size_.width(), pixel_size_.height()); | |
101 glTexCoord2f(0, pixel_size_.height()); | |
102 glVertex2f(0, pixel_size_.height()); | |
103 glEnd(); | |
104 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
105 glDisable(GL_TEXTURE_RECTANGLE_ARB); | |
106 | |
107 // Workaround for issue 158469. Issue a dummy draw call with texture_ not | |
108 // bound to a texture, in order to shake all references to the IOSurface out | |
109 // of the driver. | |
110 glBegin(GL_TRIANGLES); | |
111 glEnd(); | |
112 | |
113 bool workaround_needed = | |
114 GpuDataManagerImpl::GetInstance()->IsDriverBugWorkaroundActive( | |
115 gpu::FORCE_GL_FINISH_AFTER_COMPOSITING); | |
116 if (workaround_needed) { | |
117 TRACE_EVENT0("gpu", "glFinish"); | |
118 glFinish(); | |
119 } | |
120 | |
121 // Check if any of the drawing calls result in an error. | |
122 GetAndSaveGLError(); | |
123 bool result = true; | |
124 if (gl_error_ != GL_NO_ERROR) { | |
125 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_; | |
126 result = false; | |
127 // If there was an error, clear the screen to a light grey to avoid | |
128 // rendering artifacts. | |
129 glClearColor(0.8, 0.8, 0.8, 1.0); | |
130 glClear(GL_COLOR_BUFFER_BIT); | |
131 } | |
132 | |
133 eviction_has_been_drawn_since_updated_ = true; | |
134 return result; | |
135 } | |
136 | |
137 bool IOSurfaceTexture::SetIOSurface( | |
138 IOSurfaceID io_surface_id, | |
139 const gfx::Size& pixel_size) { | |
140 TRACE_EVENT0("browser", "IOSurfaceTexture::MapIOSurfaceToTexture"); | |
141 | |
142 // Destroy the old IOSurface and texture if it is no longer needed. | |
143 bool needs_new_iosurface = | |
144 !io_surface_ || io_surface_id != IOSurfaceGetID(io_surface_); | |
145 if (needs_new_iosurface) | |
146 ReleaseIOSurfaceAndTexture(); | |
147 | |
148 // Note that because IOSurface sizes are rounded, the same IOSurface may have | |
149 // two different sizes associated with it, so update the sizes before the | |
150 // early-out. | |
151 pixel_size_ = pixel_size; | |
152 | |
153 // Early-out if the IOSurface has not changed. | |
154 if (!needs_new_iosurface) | |
155 return true; | |
156 | |
157 // If we early-out at any point from now on, it's because of an error, and we | |
158 // should destroy the texture and release the IOSurface. | |
159 base::ScopedClosureRunner error_runner(base::BindBlock(^{ | |
160 ReleaseIOSurfaceAndTexture(); | |
161 })); | |
162 | |
163 // Open the IOSurface handle. | |
164 io_surface_.reset(IOSurfaceLookup(io_surface_id)); | |
165 if (!io_surface_) | |
166 return false; | |
167 | |
168 // Actual IOSurface size is rounded up to reduce reallocations during window | |
169 // resize. Get the actual size to properly map the texture. | |
170 gfx::Size rounded_size(IOSurfaceGetWidth(io_surface_), | |
171 IOSurfaceGetHeight(io_surface_)); | |
172 | |
173 // Create the GL texture and set it to be backed by the IOSurface. | |
174 CGLError cgl_error = kCGLNoError; | |
175 { | |
176 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
177 offscreen_context_->cgl_context()); | |
178 glGenTextures(1, &texture_); | |
179 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); | |
180 glTexParameterf( | |
181 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
182 glTexParameterf( | |
183 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
184 cgl_error = CGLTexImageIOSurface2D( | |
185 offscreen_context_->cgl_context(), | |
186 GL_TEXTURE_RECTANGLE_ARB, | |
187 GL_RGBA, | |
188 rounded_size.width(), | |
189 rounded_size.height(), | |
190 GL_BGRA, | |
191 GL_UNSIGNED_INT_8_8_8_8_REV, | |
192 io_surface_.get(), | |
193 0 /* plane */); | |
194 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); | |
195 GetAndSaveGLError(); | |
196 } | |
197 | |
198 // Return failure if an error was encountered by CGL or GL. | |
199 if (cgl_error != kCGLNoError) { | |
200 LOG(ERROR) << "CGLTexImageIOSurface2D failed with CGL error: " << cgl_error; | |
201 return false; | |
202 } | |
203 if (gl_error_ != GL_NO_ERROR) { | |
204 LOG(ERROR) << "Hit GL error in SetIOSurface: " << gl_error_; | |
205 return false; | |
206 } | |
207 | |
208 ignore_result(error_runner.Release()); | |
209 return true; | |
210 } | |
211 | |
212 void IOSurfaceTexture::ReleaseIOSurfaceAndTexture() { | |
213 gfx::ScopedCGLSetCurrentContext scoped_set_current_context( | |
214 offscreen_context_->cgl_context()); | |
215 | |
216 if (texture_) { | |
217 glDeleteTextures(1, &texture_); | |
218 texture_ = 0; | |
219 } | |
220 pixel_size_ = gfx::Size(); | |
221 io_surface_.reset(); | |
222 | |
223 EvictionMarkEvicted(); | |
224 } | |
225 | |
226 bool IOSurfaceTexture::HasBeenPoisoned() const { | |
227 return offscreen_context_->HasBeenPoisoned(); | |
228 } | |
229 | |
230 GLenum IOSurfaceTexture::GetAndSaveGLError() { | |
231 GLenum gl_error = glGetError(); | |
232 if (gl_error_ == GL_NO_ERROR) | |
233 gl_error_ = gl_error; | |
234 return gl_error; | |
235 } | |
236 | |
237 void IOSurfaceTexture::EvictionMarkUpdated() { | |
238 EvictionMarkEvicted(); | |
239 eviction_queue_.Get().push_back(this); | |
240 eviction_queue_iterator_ = --eviction_queue_.Get().end(); | |
241 eviction_has_been_drawn_since_updated_ = false; | |
242 EvictionScheduleDoEvict(); | |
243 } | |
244 | |
245 void IOSurfaceTexture::EvictionMarkEvicted() { | |
246 if (eviction_queue_iterator_ == eviction_queue_.Get().end()) | |
247 return; | |
248 eviction_queue_.Get().erase(eviction_queue_iterator_); | |
249 eviction_queue_iterator_ = eviction_queue_.Get().end(); | |
250 eviction_has_been_drawn_since_updated_ = false; | |
251 } | |
252 | |
253 // static | |
254 void IOSurfaceTexture::EvictionScheduleDoEvict() { | |
255 if (eviction_scheduled_) | |
256 return; | |
257 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
258 return; | |
259 | |
260 eviction_scheduled_ = true; | |
261 base::MessageLoop::current()->PostTask( | |
262 FROM_HERE, | |
263 base::Bind(&IOSurfaceTexture::EvictionDoEvict)); | |
264 } | |
265 | |
266 // static | |
267 void IOSurfaceTexture::EvictionDoEvict() { | |
268 eviction_scheduled_ = false; | |
269 // Walk the list of allocated surfaces from least recently used to most | |
270 // recently used. | |
271 for (EvictionQueue::iterator it = eviction_queue_.Get().begin(); | |
272 it != eviction_queue_.Get().end();) { | |
273 IOSurfaceTexture* surface = *it; | |
274 ++it; | |
275 | |
276 // If the number of IOSurfaces allocated is less than the threshold, | |
277 // stop walking the list of surfaces. | |
278 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces) | |
279 break; | |
280 | |
281 // Don't evict anything that has not yet been drawn. | |
282 if (!surface->eviction_has_been_drawn_since_updated_) | |
283 continue; | |
284 | |
285 // Evict the surface. | |
286 surface->ReleaseIOSurfaceAndTexture(); | |
287 } | |
288 } | |
289 | |
290 // static | |
291 base::LazyInstance<IOSurfaceTexture::EvictionQueue> | |
292 IOSurfaceTexture::eviction_queue_; | |
293 | |
294 // static | |
295 bool IOSurfaceTexture::eviction_scheduled_ = false; | |
296 | |
297 } // namespace content | |
OLD | NEW |