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