blob: f1754cf0f97593dca7eaf38fbdc46cc320bb7d3b [file] [log] [blame]
// 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 "content/browser/renderer_host/compositing_iosurface_mac.h"
#include <OpenGL/OpenGL.h>
#include <vector>
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/gl/gl_context.h"
#include "ui/gfx/gl/gl_switches.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/gfx/surface/io_surface_support_mac.h"
#ifdef NDEBUG
#define CHECK_GL_ERROR()
#else
#define CHECK_GL_ERROR() do { \
GLenum gl_error = glGetError(); \
LOG_IF(ERROR, gl_error != GL_NO_ERROR) << "GL Error :" << gl_error; \
} while (0)
#endif
CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::Create");
IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
if (!io_surface_support) {
LOG(WARNING) << "No IOSurface support";
return NULL;
}
std::vector<NSOpenGLPixelFormatAttribute> attributes;
attributes.push_back(NSOpenGLPFADoubleBuffer);
// We don't need a depth buffer - try setting its size to 0...
attributes.push_back(NSOpenGLPFADepthSize); attributes.push_back(0);
if (gfx::GLContext::SupportsDualGpus())
attributes.push_back(NSOpenGLPFAAllowOfflineRenderers);
attributes.push_back(0);
scoped_nsobject<NSOpenGLPixelFormat> glPixelFormat(
[[NSOpenGLPixelFormat alloc] initWithAttributes:&attributes.front()]);
if (!glPixelFormat) {
LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed";
return NULL;
}
scoped_nsobject<NSOpenGLContext> glContext(
[[NSOpenGLContext alloc] initWithFormat:glPixelFormat
shareContext:nil]);
if (!glContext) {
LOG(ERROR) << "NSOpenGLContext initWithFormat failed";
return NULL;
}
// We "punch a hole" in the window, and have the WindowServer render the
// OpenGL surface underneath so we can draw over it.
GLint belowWindow = -1;
[glContext setValues:&belowWindow forParameter:NSOpenGLCPSurfaceOrder];
CGLContextObj cglContext = (CGLContextObj)[glContext CGLContextObj];
if (!cglContext) {
LOG(ERROR) << "CGLContextObj failed";
return NULL;
}
// Draw at beam vsync.
GLint swapInterval;
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync))
swapInterval = 0;
else
swapInterval = 1;
[glContext setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
return new CompositingIOSurfaceMac(io_surface_support, glContext.release(),
cglContext);
}
CompositingIOSurfaceMac::CompositingIOSurfaceMac(
IOSurfaceSupport* io_surface_support,
NSOpenGLContext* glContext,
CGLContextObj cglContext)
: io_surface_support_(io_surface_support),
glContext_(glContext),
cglContext_(cglContext),
io_surface_handle_(0) {
}
CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
UnrefIOSurface();
}
void CompositingIOSurfaceMac::SetIOSurface(uint64 io_surface_handle) {
CGLSetCurrentContext(cglContext_);
MapIOSurfaceToTexture(io_surface_handle);
CGLSetCurrentContext(0);
}
void CompositingIOSurfaceMac::DrawIOSurface(NSView* view) {
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::DrawIOSurface");
CGLSetCurrentContext(cglContext_);
bool has_io_surface = MapIOSurfaceToTexture(io_surface_handle_);
[glContext_ setView:view];
NSSize window_size = [view frame].size;
glViewport(0, 0, window_size.width, window_size.height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, window_size.width, window_size.height, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glColorMask(true, true, true, true);
// Should match the clear color of RenderWidgetHostViewMac.
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// TODO(jbates): Just clear the right and bottom edges when the size doesn't
// match the window. Then use a shader to blit the texture without its alpha
// channel.
glClear(GL_COLOR_BUFFER_BIT);
if (has_io_surface) {
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// Draw only the color channels from the incoming texture.
glColorMask(true, true, true, false);
// Draw the color channels from the incoming texture.
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); CHECK_GL_ERROR();
glEnable(GL_TEXTURE_RECTANGLE_ARB); CHECK_GL_ERROR();
DrawQuad(quad_);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_GL_ERROR();
}
CGLFlushDrawable(cglContext_);
CGLSetCurrentContext(0);
}
bool CompositingIOSurfaceMac::CopyTo(const gfx::Size& dst_size, void* out) {
if (!MapIOSurfaceToTexture(io_surface_handle_))
return false;
CGLSetCurrentContext(cglContext_);
GLuint target = GL_TEXTURE_RECTANGLE_ARB;
GLuint dst_texture = 0;
glGenTextures(1, &dst_texture); CHECK_GL_ERROR();
glBindTexture(target, dst_texture); CHECK_GL_ERROR();
GLuint dst_framebuffer = 0;
glGenFramebuffersEXT(1, &dst_framebuffer); CHECK_GL_ERROR();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dst_framebuffer); CHECK_GL_ERROR();
glTexImage2D(target,
0,
GL_RGBA,
dst_size.width(),
dst_size.height(),
0,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
NULL); CHECK_GL_ERROR();
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
target,
dst_texture,
0); CHECK_GL_ERROR();
glBindTexture(target, 0); CHECK_GL_ERROR();
glViewport(0, 0, dst_size.width(), dst_size.height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, dst_size.width(), 0, dst_size.height(), -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// Draw only the color channels from the incoming texture.
glColorMask(true, true, true, false);
// Draw the color channels from the incoming texture.
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_); CHECK_GL_ERROR();
glEnable(GL_TEXTURE_RECTANGLE_ARB); CHECK_GL_ERROR();
SurfaceQuad quad;
quad.set_size(dst_size, io_surface_size_);
DrawQuad(quad);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); CHECK_GL_ERROR();
CGLFlushDrawable(cglContext_);
glReadPixels(0, 0, dst_size.width(), dst_size.height(),
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, out);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); CHECK_GL_ERROR();
glDeleteFramebuffersEXT(1, &dst_framebuffer);
glDeleteTextures(1, &dst_texture);
CGLSetCurrentContext(0);
return true;
}
bool CompositingIOSurfaceMac::MapIOSurfaceToTexture(
uint64 io_surface_handle) {
if (io_surface_.get() && io_surface_handle == io_surface_handle_)
return true;
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::MapIOSurfaceToTexture");
UnrefIOSurfaceWithContextCurrent();
io_surface_.reset(io_surface_support_->IOSurfaceLookup(
static_cast<uint32>(io_surface_handle)));
// Can fail if IOSurface with that ID was already released by the gpu
// process.
if (!io_surface_.get()) {
io_surface_handle_ = 0;
return false;
}
io_surface_handle_ = io_surface_handle;
io_surface_size_.SetSize(
io_surface_support_->IOSurfaceGetWidth(io_surface_),
io_surface_support_->IOSurfaceGetHeight(io_surface_));
quad_.set_size(io_surface_size_, io_surface_size_);
GLenum target = GL_TEXTURE_RECTANGLE_ARB;
glGenTextures(1, &texture_);
glBindTexture(target, texture_);
glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); CHECK_GL_ERROR();
GLuint plane = 0;
CGLError cglerror =
io_surface_support_->CGLTexImageIOSurface2D(cglContext_,
target,
GL_RGBA,
io_surface_size_.width(),
io_surface_size_.height(),
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
io_surface_.get(),
plane);
CHECK_GL_ERROR();
if (cglerror != kCGLNoError) {
LOG(ERROR) << "CGLTexImageIOSurface2D: " << cglerror;
return false;
}
return true;
}
void CompositingIOSurfaceMac::UnrefIOSurface() {
CGLSetCurrentContext(cglContext_);
UnrefIOSurfaceWithContextCurrent();
CGLSetCurrentContext(0);
}
void CompositingIOSurfaceMac::DrawQuad(const SurfaceQuad& quad) {
glEnableClientState(GL_VERTEX_ARRAY); CHECK_GL_ERROR();
glEnableClientState(GL_TEXTURE_COORD_ARRAY); CHECK_GL_ERROR();
glVertexPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].x_);
glTexCoordPointer(2, GL_FLOAT, sizeof(SurfaceVertex), &quad.verts_[0].tx_);
glDrawArrays(GL_QUADS, 0, 4); CHECK_GL_ERROR();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
void CompositingIOSurfaceMac::UnrefIOSurfaceWithContextCurrent() {
if (texture_) {
glDeleteTextures(1, &texture_);
texture_ = 0;
}
io_surface_.reset();
}
void CompositingIOSurfaceMac::GlobalFrameDidChange() {
[glContext_ update];
}
void CompositingIOSurfaceMac::ClearDrawable() {
[glContext_ clearDrawable];
UnrefIOSurface();
}