blob: fc7e5ec4bf370933a39cd3ebca0a647539d8b82e [file] [log] [blame]
// Copyright 2014 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/common/gpu/image_transport_surface_fbo_mac.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/gpu/image_transport_surface_calayer_mac.h"
#include "content/common/gpu/image_transport_surface_iosurface_mac.h"
#include "ui/base/cocoa/remote_layer_api.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface_osmesa.h"
namespace content {
ImageTransportSurfaceFBO::ImageTransportSurfaceFBO(
GpuChannelManager* manager,
GpuCommandBufferStub* stub,
gfx::PluginWindowHandle handle)
: backbuffer_suggested_allocation_(true),
frontbuffer_suggested_allocation_(true),
fbo_id_(0),
texture_id_(0),
depth_stencil_renderbuffer_id_(0),
has_complete_framebuffer_(false),
context_(NULL),
scale_factor_(1.f),
made_current_(false),
is_swap_buffers_pending_(false),
did_unschedule_(false) {
if (ui::RemoteLayerAPISupported())
storage_provider_.reset(new CALayerStorageProvider(this));
else
storage_provider_.reset(new IOSurfaceStorageProvider(this));
helper_.reset(new ImageTransportHelper(this, manager, stub, handle));
}
ImageTransportSurfaceFBO::~ImageTransportSurfaceFBO() {
}
bool ImageTransportSurfaceFBO::Initialize() {
// Only support IOSurfaces if the GL implementation is the native desktop GL.
// IO surfaces will not work with, for example, OSMesa software renderer
// GL contexts.
if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL &&
gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL)
return false;
if (!helper_->Initialize())
return false;
helper_->stub()->AddDestructionObserver(this);
return true;
}
void ImageTransportSurfaceFBO::Destroy() {
DestroyFramebuffer();
helper_->Destroy();
}
bool ImageTransportSurfaceFBO::DeferDraws() {
// The command buffer hit a draw/clear command that could clobber the
// IOSurface in use by an earlier SwapBuffers. If a Swap is pending, abort
// processing of the command by returning true and unschedule until the Swap
// Ack arrives.
if(did_unschedule_)
return true; // Still unscheduled, so just return true.
if (is_swap_buffers_pending_) {
did_unschedule_ = true;
helper_->SetScheduled(false);
return true;
}
return false;
}
bool ImageTransportSurfaceFBO::IsOffscreen() {
return false;
}
bool ImageTransportSurfaceFBO::OnMakeCurrent(gfx::GLContext* context) {
context_ = context;
if (made_current_)
return true;
OnResize(gfx::Size(1, 1), 1.f);
made_current_ = true;
return true;
}
unsigned int ImageTransportSurfaceFBO::GetBackingFrameBufferObject() {
return fbo_id_;
}
bool ImageTransportSurfaceFBO::SetBackbufferAllocation(bool allocation) {
if (backbuffer_suggested_allocation_ == allocation)
return true;
backbuffer_suggested_allocation_ = allocation;
AdjustBufferAllocation();
return true;
}
void ImageTransportSurfaceFBO::SetFrontbufferAllocation(bool allocation) {
if (frontbuffer_suggested_allocation_ == allocation)
return;
frontbuffer_suggested_allocation_ = allocation;
AdjustBufferAllocation();
}
void ImageTransportSurfaceFBO::AdjustBufferAllocation() {
// On mac, the frontbuffer and backbuffer are the same buffer. The buffer is
// free'd when both the browser and gpu processes have Unref'd the IOSurface.
if (!backbuffer_suggested_allocation_ &&
!frontbuffer_suggested_allocation_ &&
has_complete_framebuffer_) {
DestroyFramebuffer();
helper_->Suspend();
} else if (backbuffer_suggested_allocation_ && !has_complete_framebuffer_) {
CreateFramebuffer();
}
}
bool ImageTransportSurfaceFBO::SwapBuffers() {
DCHECK(backbuffer_suggested_allocation_);
if (!frontbuffer_suggested_allocation_)
return true;
glFlush();
GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params;
params.surface_handle = storage_provider_->GetSurfaceHandle();
params.size = GetSize();
params.scale_factor = scale_factor_;
params.latency_info.swap(latency_info_);
helper_->SendAcceleratedSurfaceBuffersSwapped(params);
DCHECK(!is_swap_buffers_pending_);
is_swap_buffers_pending_ = true;
storage_provider_->WillSwapBuffers();
return true;
}
bool ImageTransportSurfaceFBO::PostSubBuffer(
int x, int y, int width, int height) {
// Mac does not support sub-buffer swaps.
NOTREACHED();
return false;
}
bool ImageTransportSurfaceFBO::SupportsPostSubBuffer() {
return true;
}
gfx::Size ImageTransportSurfaceFBO::GetSize() {
return size_;
}
void* ImageTransportSurfaceFBO::GetHandle() {
return NULL;
}
void* ImageTransportSurfaceFBO::GetDisplay() {
return NULL;
}
void ImageTransportSurfaceFBO::OnBufferPresented(
const AcceleratedSurfaceMsg_BufferPresented_Params& params) {
context_->share_group()->SetRendererID(params.renderer_id);
storage_provider_->CanFreeSwappedBuffer();
}
void ImageTransportSurfaceFBO::UnblockContextAfterPendingSwap() {
DCHECK(is_swap_buffers_pending_);
is_swap_buffers_pending_ = false;
if (did_unschedule_) {
did_unschedule_ = false;
helper_->SetScheduled(true);
}
}
void ImageTransportSurfaceFBO::OnResize(gfx::Size size,
float scale_factor) {
TRACE_EVENT2("gpu", "ImageTransportSurfaceFBO::OnResize",
"old_width", size_.width(), "new_width", size.width());
// Caching |context_| from OnMakeCurrent. It should still be current.
DCHECK(context_->IsCurrent(this));
size_ = size;
scale_factor_ = scale_factor;
CreateFramebuffer();
}
void ImageTransportSurfaceFBO::SetLatencyInfo(
const std::vector<ui::LatencyInfo>& latency_info) {
for (size_t i = 0; i < latency_info.size(); i++)
latency_info_.push_back(latency_info[i]);
}
void ImageTransportSurfaceFBO::WakeUpGpu() {
NOTIMPLEMENTED();
}
void ImageTransportSurfaceFBO::OnWillDestroyStub() {
helper_->stub()->RemoveDestructionObserver(this);
Destroy();
}
void ImageTransportSurfaceFBO::DestroyFramebuffer() {
// If we have resources to destroy, then make sure that we have a current
// context which we can use to delete the resources.
if (context_ || fbo_id_ || texture_id_ || depth_stencil_renderbuffer_id_) {
DCHECK(gfx::GLContext::GetCurrent() == context_);
DCHECK(context_->IsCurrent(this));
DCHECK(CGLGetCurrentContext());
}
if (fbo_id_) {
glDeleteFramebuffersEXT(1, &fbo_id_);
fbo_id_ = 0;
}
if (texture_id_) {
glDeleteTextures(1, &texture_id_);
texture_id_ = 0;
}
if (depth_stencil_renderbuffer_id_) {
glDeleteRenderbuffersEXT(1, &depth_stencil_renderbuffer_id_);
depth_stencil_renderbuffer_id_ = 0;
}
storage_provider_->FreeColorBufferStorage();
has_complete_framebuffer_ = false;
}
void ImageTransportSurfaceFBO::CreateFramebuffer() {
gfx::Size new_rounded_size = storage_provider_->GetRoundedSize(size_);
// Only recreate surface when the rounded up size has changed.
if (has_complete_framebuffer_ && new_rounded_size == rounded_size_)
return;
TRACE_EVENT2("gpu", "ImageTransportSurfaceFBO::CreateFramebuffer",
"width", new_rounded_size.width(),
"height", new_rounded_size.height());
rounded_size_ = new_rounded_size;
// GL_TEXTURE_RECTANGLE_ARB is the best supported render target on
// Mac OS X and is required for IOSurface interoperability.
GLint previous_texture_id = 0;
glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE_ARB, &previous_texture_id);
// Free the old IO Surface first to reduce memory fragmentation.
DestroyFramebuffer();
glGenFramebuffersEXT(1, &fbo_id_);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_id_);
glGenTextures(1, &texture_id_);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id_);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_RECTANGLE_ARB,
texture_id_,
0);
// Search through the provided attributes; if the caller has
// requested a stencil buffer, try to get one.
int32 stencil_bits =
helper_->stub()->GetRequestedAttribute(EGL_STENCIL_SIZE);
if (stencil_bits > 0) {
// Create and bind the stencil buffer
bool has_packed_depth_stencil =
GLSurface::ExtensionsContain(
reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
"GL_EXT_packed_depth_stencil");
if (has_packed_depth_stencil) {
glGenRenderbuffersEXT(1, &depth_stencil_renderbuffer_id_);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,
depth_stencil_renderbuffer_id_);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT,
rounded_size_.width(), rounded_size_.height());
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
GL_STENCIL_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT,
depth_stencil_renderbuffer_id_);
}
// If we asked for stencil but the extension isn't present,
// it's OK to silently fail; subsequent code will/must check
// for the presence of a stencil buffer before attempting to
// do stencil-based operations.
}
bool allocated_color_buffer = storage_provider_->AllocateColorBufferStorage(
static_cast<CGLContextObj>(context_->GetHandle()), texture_id_,
rounded_size_, scale_factor_);
if (!allocated_color_buffer) {
DLOG(ERROR) << "Failed to allocate color buffer storage.";
DestroyFramebuffer();
return;
}
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
DLOG(ERROR) << "Framebuffer was incomplete: " << status;
DestroyFramebuffer();
return;
}
has_complete_framebuffer_ = true;
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, previous_texture_id);
// The FBO remains bound for this GL context.
}
} // namespace content