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