// Copyright (c) 2011 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/accelerated_surface_container_mac.h" #include "base/logging.h" #include "content/browser/renderer_host/accelerated_surface_container_manager_mac.h" #include "ui/gfx/surface/io_surface_support_mac.h" #include "webkit/plugins/npapi/webplugin.h" AcceleratedSurfaceContainerMac::AcceleratedSurfaceContainerMac( AcceleratedSurfaceContainerManagerMac* manager, bool opaque) : manager_(manager), opaque_(opaque), surface_id_(0), width_(0), height_(0), texture_(0), texture_needs_upload_(true), texture_pending_deletion_(0), visible_(false), was_painted_to_(false) { } AcceleratedSurfaceContainerMac::~AcceleratedSurfaceContainerMac() { } void AcceleratedSurfaceContainerMac::SetSizeAndIOSurface( int32 width, int32 height, uint64 io_surface_identifier) { // Ignore |io_surface_identifier|: The surface hasn't been painted to and // only contains garbage data. Update the surface in |set_was_painted_to()| // instead. width_ = width; height_ = height; } void AcceleratedSurfaceContainerMac::SetSizeAndTransportDIB( int32 width, int32 height, TransportDIB::Handle transport_dib) { if (TransportDIB::is_valid_handle(transport_dib)) { transport_dib_.reset(TransportDIB::Map(transport_dib)); EnqueueTextureForDeletion(); width_ = width; height_ = height; } } void AcceleratedSurfaceContainerMac::SetGeometry( const webkit::npapi::WebPluginGeometry& geom) { visible_ = geom.visible; if (geom.rects_valid) clip_rect_ = geom.clip_rect; } void AcceleratedSurfaceContainerMac::Draw(CGLContextObj context) { IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); GLenum target = GL_TEXTURE_RECTANGLE_ARB; if (texture_pending_deletion_) { // Clean up an old texture object. This is essentially a pre-emptive // cleanup, as the resources will be released when the OpenGL context // associated with our containing NSView is destroyed. However, if we // resize a plugin often, we might generate a lot of textures, so we // should try to eagerly reclaim their resources. Note also that the // OpenGL context must be current when performing the deletion, and it // seems risky to make the OpenGL context current at an arbitrary point // in time, which is why the deletion does not occur in the container's // destructor. glDeleteTextures(1, &texture_pending_deletion_); texture_pending_deletion_ = 0; } if (!texture_) { if ((io_surface_support && !surface_.get()) || (!io_surface_support && !transport_dib_.get())) return; glGenTextures(1, &texture_); glBindTexture(target, texture_); glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); if (io_surface_support) { texture_needs_upload_ = true; } else { // Reserve space on the card for the actual texture upload, done with the // glTexSubImage2D() call, below. glTexImage2D(target, 0, // mipmap level 0 GL_RGBA, // internal format width_, height_, 0, // no border GL_BGRA, // The GPU plugin read BGRA pixels GL_UNSIGNED_INT_8_8_8_8_REV, NULL); // No data, this call just reserves room. } } // When using an IOSurface, the texture does not need to be repeatedly // uploaded, just when we've been told we have to. if (io_surface_support && texture_needs_upload_) { DCHECK(surface_.get()); glBindTexture(target, texture_); // Don't think we need to identify a plane. GLuint plane = 0; io_surface_support->CGLTexImageIOSurface2D(context, target, GL_RGBA, surface_width_, surface_height_, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface_.get(), plane); texture_needs_upload_ = false; } // If using TransportDIBs, the texture needs to be uploaded every frame. if (transport_dib_.get() != NULL) { void* pixel_memory = transport_dib_->memory(); if (pixel_memory) { glBindTexture(target, texture_); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Needed for NPOT textures. glTexSubImage2D(target, 0, // mipmap level 0 0, // x-offset 0, // y-offset width_, height_, GL_BGRA, // The GPU plugin gave us BGRA pixels GL_UNSIGNED_INT_8_8_8_8_REV, pixel_memory); } } if (texture_) { int texture_width = io_surface_support ? surface_width_ : width_; int texture_height = io_surface_support ? surface_height_ : height_; // TODO(kbr): convert this to use only OpenGL ES 2.0 functionality. // TODO(kbr): may need to pay attention to cutout rects. int clipX = clip_rect_.x(); int clipY = clip_rect_.y(); int clipWidth = clip_rect_.width(); int clipHeight = clip_rect_.height(); if (clipX + clipWidth > texture_width) clipWidth = texture_width - clipX; if (clipY + clipHeight > texture_height) clipHeight = texture_height - clipY; if (opaque_) { // Pepper 3D's output is currently considered opaque even if the // program draws pixels with alpha less than 1. In order to have // this effect, we need to drop the alpha channel of the input, // replacing it with alpha = 1. // First fill the rectangle with alpha=1. glColorMask(false, false, false, true); glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glBegin(GL_TRIANGLE_STRIP); glVertex3f(0, 0, 0); glVertex3f(clipWidth, 0, 0); glVertex3f(0, clipHeight, 0); glVertex3f(clipWidth, clipHeight, 0); glEnd(); // Now draw the color channels from the incoming texture. glColorMask(true, true, true, false); // This call shouldn't be necessary -- we are using the GL_REPLACE // texture environment mode -- but it appears to be. glColor4f(1.0f, 1.0f, 1.0f, 1.0f); } else { glColorMask(true, true, true, true); } // Draw the color channels from the incoming texture. glBindTexture(target, texture_); glEnable(target); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(clipX, texture_height - clipY); glVertex3f(0, 0, 0); glTexCoord2f(clipX + clipWidth, texture_height - clipY); glVertex3f(clipWidth, 0, 0); glTexCoord2f(clipX, texture_height - clipY - clipHeight); glVertex3f(0, clipHeight, 0); glTexCoord2f(clipX + clipWidth, texture_height - clipY - clipHeight); glVertex3f(clipWidth, clipHeight, 0); glEnd(); glDisable(target); } } bool AcceleratedSurfaceContainerMac::ShouldBeVisible() const { return visible_ && was_painted_to_ && !clip_rect_.IsEmpty(); } void AcceleratedSurfaceContainerMac::set_was_painted_to(uint64 surface_id) { if (surface_id && (!surface_ || surface_id != surface_id_)) { // Keep the surface that was most recently painted to around. if (IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize()) { CFTypeRef surface = io_surface_support->IOSurfaceLookup( static_cast(surface_id)); // Can fail if IOSurface with that ID was already released by the // gpu process or the plugin process. We will get a |set_was_painted_to()| // message with a new surface soon in that case. if (surface) { surface_.reset(surface); surface_id_ = surface_id; surface_width_ = io_surface_support->IOSurfaceGetWidth(surface_); surface_height_ = io_surface_support->IOSurfaceGetHeight(surface_); EnqueueTextureForDeletion(); } } } was_painted_to_ = true; } void AcceleratedSurfaceContainerMac::EnqueueTextureForDeletion() { if (texture_) { DCHECK(texture_pending_deletion_ == 0); texture_pending_deletion_ = texture_; texture_ = 0; } }