// 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 #include #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 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 glPixelFormat( [[NSOpenGLPixelFormat alloc] initWithAttributes:&attributes.front()]); if (!glPixelFormat) { LOG(ERROR) << "NSOpenGLPixelFormat initWithAttributes failed"; return NULL; } scoped_nsobject 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(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(); }