Roll src/third_party/WebKit 06cb9e9:a978ee5 (svn 202558:202559)
[chromium-blink-merge.git] / ui / accelerated_widget_mac / io_surface_texture.mm
blob1069a0b9c51daeb9d069cc96366122bff96cc220
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.
5 #include "ui/accelerated_widget_mac/io_surface_texture.h"
7 #include <OpenGL/CGLIOSurface.h>
8 #include <OpenGL/CGLRenderers.h>
9 #include <OpenGL/gl.h>
10 #include <OpenGL/OpenGL.h>
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/callback_helpers.h"
15 #include "base/command_line.h"
16 #include "base/logging.h"
17 #include "base/mac/bind_objc_block.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/threading/platform_thread.h"
20 #include "base/trace_event/trace_event.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "ui/accelerated_widget_mac/io_surface_context.h"
23 #include "ui/base/ui_base_switches.h"
24 #include "ui/gfx/geometry/rect.h"
25 #include "ui/gfx/geometry/size_conversions.h"
26 #include "ui/gl/gl_context.h"
28 namespace ui {
30 // static
31 scoped_refptr<IOSurfaceTexture> IOSurfaceTexture::Create(
32     bool needs_gl_finish_workaround,
33     bool use_ns_apis) {
34   scoped_refptr<IOSurfaceContext> offscreen_context;
35   if (!use_ns_apis) {
36     offscreen_context = IOSurfaceContext::Get(
37         IOSurfaceContext::kOffscreenContext);
38     if (!offscreen_context.get()) {
39       LOG(ERROR) << "Failed to create context for offscreen operations";
40       return NULL;
41     }
42   }
43   return new IOSurfaceTexture(
44       offscreen_context, use_ns_apis, needs_gl_finish_workaround);
47 IOSurfaceTexture::IOSurfaceTexture(
48     const scoped_refptr<IOSurfaceContext>& offscreen_context,
49     bool use_ns_apis,
50     bool needs_gl_finish_workaround)
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       needs_gl_finish_workaround_(needs_gl_finish_workaround),
57       using_ns_apis_(use_ns_apis) {
60 IOSurfaceTexture::~IOSurfaceTexture() {
61   ReleaseIOSurfaceAndTexture();
62   offscreen_context_ = NULL;
63   DCHECK(eviction_queue_iterator_ == eviction_queue_.Get().end());
66 bool IOSurfaceTexture::DrawIOSurface() {
67   return DrawIOSurfaceWithDamageRect(gfx::Rect(pixel_size_));
70 bool IOSurfaceTexture::DrawIOSurfaceWithDamageRect(gfx::Rect damage_rect) {
71   TRACE_EVENT0("browser", "IOSurfaceTexture::DrawIOSurfaceWithDamageRect");
72   DCHECK(CGLGetCurrentContext());
74   // If we have release the IOSurface, clear the screen to light grey and
75   // early-out.
76   if (!io_surface_) {
77     glClearColor(0.9, 0.9, 0.9, 1);
78     glClear(GL_COLOR_BUFFER_BIT);
79     return false;
80   }
82   // The viewport is the size of the CALayer, which should always match the
83   // IOSurface pixel size.
84   GLint viewport[4];
85   glGetIntegerv(GL_VIEWPORT, viewport);
86   gfx::Rect viewport_rect(viewport[0], viewport[1], viewport[2], viewport[3]);
88   // Set the projection matrix to match 1 unit to 1 pixel.
89   glMatrixMode(GL_PROJECTION);
90   glLoadIdentity();
91   glOrtho(0, viewport_rect.width(),
92           pixel_size_.height() - viewport_rect.height(), pixel_size_.height(),
93           -1, 1);
94   glMatrixMode(GL_MODELVIEW);
95   glLoadIdentity();
97   // Draw a quad the size of the IOSurface. This should cover the full viewport.
98   glColor4f(1, 1, 1, 1);
99   glEnable(GL_TEXTURE_RECTANGLE_ARB);
100   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
101   glBegin(GL_QUADS);
102   glTexCoord2f(damage_rect.x(), damage_rect.y());
103   glVertex2f(damage_rect.x(), damage_rect.y());
104   glTexCoord2f(damage_rect.right(), damage_rect.y());
105   glVertex2f(damage_rect.right(), damage_rect.y());
106   glTexCoord2f(damage_rect.right(), damage_rect.bottom());
107   glVertex2f(damage_rect.right(), damage_rect.bottom());
108   glTexCoord2f(damage_rect.x(), damage_rect.bottom());
109   glVertex2f(damage_rect.x(), damage_rect.bottom());
110   glEnd();
111   glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
112   glDisable(GL_TEXTURE_RECTANGLE_ARB);
114   // Workaround for issue 158469. Issue a dummy draw call with texture_ not
115   // bound to a texture, in order to shake all references to the IOSurface out
116   // of the driver.
117   glBegin(GL_TRIANGLES);
118   glEnd();
120   if (needs_gl_finish_workaround_) {
121     TRACE_EVENT0("gpu", "glFinish");
122     glFinish();
123   }
125   // Check if any of the drawing calls result in an error.
126   GetAndSaveGLError();
127   bool result = true;
128   if (gl_error_ != GL_NO_ERROR) {
129     LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
130     result = false;
131     // If there was an error, clear the screen to a light grey to avoid
132     // rendering artifacts.
133     glClearColor(0.8, 0.8, 0.8, 1.0);
134     glClear(GL_COLOR_BUFFER_BIT);
135   }
137   eviction_has_been_drawn_since_updated_ = true;
138   return result;
141 bool IOSurfaceTexture::IsUpToDate(
142     IOSurfaceID io_surface_id, const gfx::Size& pixel_size) const {
143   return io_surface_ &&
144          io_surface_id == IOSurfaceGetID(io_surface_) &&
145          pixel_size == pixel_size_;
148 bool IOSurfaceTexture::SetIOSurface(
149     IOSurfaceID io_surface_id, const gfx::Size& pixel_size) {
150   TRACE_EVENT0("browser", "IOSurfaceTexture::MapIOSurfaceToTexture");
152   // Destroy the old IOSurface and texture if it is no longer needed.
153   bool needs_new_iosurface =
154       !io_surface_ || io_surface_id != IOSurfaceGetID(io_surface_);
155   if (needs_new_iosurface)
156     ReleaseIOSurfaceAndTexture();
158   // Note that because IOSurface sizes are rounded, the same IOSurface may have
159   // two different sizes associated with it, so update the sizes before the
160   // early-out.
161   pixel_size_ = pixel_size;
163   // Early-out if the IOSurface has not changed.
164   if (!needs_new_iosurface)
165     return true;
167   // If we early-out at any point from now on, it's because of an error, and we
168   // should destroy the texture and release the IOSurface.
169   base::ScopedClosureRunner error_runner(base::BindBlock(^{
170       ReleaseIOSurfaceAndTexture();
171   }));
173   // Open the IOSurface handle.
174   io_surface_.reset(IOSurfaceLookup(io_surface_id));
175   if (!io_surface_)
176     return false;
178   // Actual IOSurface size is rounded up to reduce reallocations during window
179   // resize. Get the actual size to properly map the texture.
180   gfx::Size rounded_size(IOSurfaceGetWidth(io_surface_),
181                          IOSurfaceGetHeight(io_surface_));
183   // Create the GL texture and set it to be backed by the IOSurface.
184   CGLError cgl_error = kCGLNoError;
185   {
186     scoped_ptr<gfx::ScopedCGLSetCurrentContext> scoped_set_current_context;
187     if (offscreen_context_) {
188       scoped_set_current_context.reset(new gfx::ScopedCGLSetCurrentContext(
189           offscreen_context_->cgl_context()));
190     } else {
191       DCHECK(CGLGetCurrentContext());
192     }
194     glGenTextures(1, &texture_);
195     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
196     glTexParameterf(
197         GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
198     glTexParameterf(
199         GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
200     cgl_error = CGLTexImageIOSurface2D(
201         CGLGetCurrentContext(),
202         GL_TEXTURE_RECTANGLE_ARB,
203         GL_RGBA,
204         rounded_size.width(),
205         rounded_size.height(),
206         GL_BGRA,
207         GL_UNSIGNED_INT_8_8_8_8_REV,
208         io_surface_.get(),
209         0 /* plane */);
210     glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
211     GetAndSaveGLError();
212   }
214   // Return failure if an error was encountered by CGL or GL.
215   if (cgl_error != kCGLNoError) {
216     LOG(ERROR) << "CGLTexImageIOSurface2D failed with CGL error: " << cgl_error;
217     return false;
218   }
219   if (gl_error_ != GL_NO_ERROR) {
220     LOG(ERROR) << "Hit GL error in SetIOSurface: " << gl_error_;
221     return false;
222   }
224   ignore_result(error_runner.Release());
225   return true;
228 void IOSurfaceTexture::ReleaseIOSurfaceAndTexture() {
229   scoped_ptr<gfx::ScopedCGLSetCurrentContext> scoped_set_current_context;
230   if (offscreen_context_) {
231     scoped_set_current_context.reset(new gfx::ScopedCGLSetCurrentContext(
232         offscreen_context_->cgl_context()));
233   } else {
234     DCHECK(CGLGetCurrentContext());
235   }
237   if (texture_) {
238     glDeleteTextures(1, &texture_);
239     texture_ = 0;
240   }
241   pixel_size_ = gfx::Size();
242   io_surface_.reset();
244   EvictionMarkEvicted();
247 bool IOSurfaceTexture::HasBeenPoisoned() const {
248   if (offscreen_context_)
249     return offscreen_context_->HasBeenPoisoned();
250   return false;
253 GLenum IOSurfaceTexture::GetAndSaveGLError() {
254   GLenum gl_error = glGetError();
255   if (gl_error_ == GL_NO_ERROR)
256     gl_error_ = gl_error;
257   return gl_error;
260 void IOSurfaceTexture::EvictionMarkUpdated() {
261   EvictionMarkEvicted();
262   eviction_queue_.Get().push_back(this);
263   eviction_queue_iterator_ = --eviction_queue_.Get().end();
264   eviction_has_been_drawn_since_updated_ = false;
265   EvictionScheduleDoEvict();
268 void IOSurfaceTexture::EvictionMarkEvicted() {
269   if (eviction_queue_iterator_ == eviction_queue_.Get().end())
270     return;
271   eviction_queue_.Get().erase(eviction_queue_iterator_);
272   eviction_queue_iterator_ = eviction_queue_.Get().end();
273   eviction_has_been_drawn_since_updated_ = false;
276 // static
277 void IOSurfaceTexture::EvictionScheduleDoEvict() {
278   if (eviction_scheduled_)
279     return;
280   if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
281     return;
283   eviction_scheduled_ = true;
284   base::MessageLoop::current()->PostTask(
285       FROM_HERE,
286       base::Bind(&IOSurfaceTexture::EvictionDoEvict));
289 // static
290 void IOSurfaceTexture::EvictionDoEvict() {
291   eviction_scheduled_ = false;
292   // Walk the list of allocated surfaces from least recently used to most
293   // recently used.
294   for (EvictionQueue::iterator it = eviction_queue_.Get().begin();
295        it != eviction_queue_.Get().end();) {
296     IOSurfaceTexture* surface = *it;
297     ++it;
299     // If the number of IOSurfaces allocated is less than the threshold,
300     // stop walking the list of surfaces.
301     if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
302       break;
304     // Don't evict anything that has not yet been drawn.
305     if (!surface->eviction_has_been_drawn_since_updated_)
306       continue;
308     // Don't evict anything that doesn't have an offscreen context (as we have
309     // context in which to delete the texture).
310     if (!surface->offscreen_context_)
311       continue;
313     // Evict the surface.
314     surface->ReleaseIOSurfaceAndTexture();
315   }
318 // static
319 base::LazyInstance<IOSurfaceTexture::EvictionQueue>
320     IOSurfaceTexture::eviction_queue_;
322 // static
323 bool IOSurfaceTexture::eviction_scheduled_ = false;
325 }  // namespace ui