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>
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"
31 scoped_refptr<IOSurfaceTexture> IOSurfaceTexture::Create(
32 bool needs_gl_finish_workaround,
34 scoped_refptr<IOSurfaceContext> offscreen_context;
36 offscreen_context = IOSurfaceContext::Get(
37 IOSurfaceContext::kOffscreenContext);
38 if (!offscreen_context.get()) {
39 LOG(ERROR) << "Failed to create context for offscreen operations";
43 return new IOSurfaceTexture(
44 offscreen_context, use_ns_apis, needs_gl_finish_workaround);
47 IOSurfaceTexture::IOSurfaceTexture(
48 const scoped_refptr<IOSurfaceContext>& offscreen_context,
50 bool needs_gl_finish_workaround)
51 : offscreen_context_(offscreen_context),
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
77 glClearColor(0.9, 0.9, 0.9, 1);
78 glClear(GL_COLOR_BUFFER_BIT);
82 // The viewport is the size of the CALayer, which should always match the
83 // IOSurface pixel size.
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);
91 glOrtho(0, viewport_rect.width(),
92 pixel_size_.height() - viewport_rect.height(), pixel_size_.height(),
94 glMatrixMode(GL_MODELVIEW);
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_);
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());
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
117 glBegin(GL_TRIANGLES);
120 if (needs_gl_finish_workaround_) {
121 TRACE_EVENT0("gpu", "glFinish");
125 // Check if any of the drawing calls result in an error.
128 if (gl_error_ != GL_NO_ERROR) {
129 LOG(ERROR) << "GL error in DrawIOSurface: " << gl_error_;
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);
137 eviction_has_been_drawn_since_updated_ = true;
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
161 pixel_size_ = pixel_size;
163 // Early-out if the IOSurface has not changed.
164 if (!needs_new_iosurface)
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();
173 // Open the IOSurface handle.
174 io_surface_.reset(IOSurfaceLookup(io_surface_id));
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;
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()));
191 DCHECK(CGLGetCurrentContext());
194 glGenTextures(1, &texture_);
195 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_);
197 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
199 GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
200 cgl_error = CGLTexImageIOSurface2D(
201 CGLGetCurrentContext(),
202 GL_TEXTURE_RECTANGLE_ARB,
204 rounded_size.width(),
205 rounded_size.height(),
207 GL_UNSIGNED_INT_8_8_8_8_REV,
210 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
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;
219 if (gl_error_ != GL_NO_ERROR) {
220 LOG(ERROR) << "Hit GL error in SetIOSurface: " << gl_error_;
224 ignore_result(error_runner.Release());
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()));
234 DCHECK(CGLGetCurrentContext());
238 glDeleteTextures(1, &texture_);
241 pixel_size_ = gfx::Size();
244 EvictionMarkEvicted();
247 bool IOSurfaceTexture::HasBeenPoisoned() const {
248 if (offscreen_context_)
249 return offscreen_context_->HasBeenPoisoned();
253 GLenum IOSurfaceTexture::GetAndSaveGLError() {
254 GLenum gl_error = glGetError();
255 if (gl_error_ == GL_NO_ERROR)
256 gl_error_ = 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())
271 eviction_queue_.Get().erase(eviction_queue_iterator_);
272 eviction_queue_iterator_ = eviction_queue_.Get().end();
273 eviction_has_been_drawn_since_updated_ = false;
277 void IOSurfaceTexture::EvictionScheduleDoEvict() {
278 if (eviction_scheduled_)
280 if (eviction_queue_.Get().size() <= kMaximumUnevictedSurfaces)
283 eviction_scheduled_ = true;
284 base::MessageLoop::current()->PostTask(
286 base::Bind(&IOSurfaceTexture::EvictionDoEvict));
290 void IOSurfaceTexture::EvictionDoEvict() {
291 eviction_scheduled_ = false;
292 // Walk the list of allocated surfaces from least recently used to most
294 for (EvictionQueue::iterator it = eviction_queue_.Get().begin();
295 it != eviction_queue_.Get().end();) {
296 IOSurfaceTexture* surface = *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)
304 // Don't evict anything that has not yet been drawn.
305 if (!surface->eviction_has_been_drawn_since_updated_)
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_)
313 // Evict the surface.
314 surface->ReleaseIOSurfaceAndTexture();
319 base::LazyInstance<IOSurfaceTexture::EvictionQueue>
320 IOSurfaceTexture::eviction_queue_;
323 bool IOSurfaceTexture::eviction_scheduled_ = false;