blob: 86f5ebcdd75617071fba129ca971a942354fb8ac [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/common/gpu/texture_image_transport_surface.h"
#include <string>
#include <vector>
#include "base/command_line.h"
#include "content/common/gpu/gl_scoped_binders.h"
#include "content/common/gpu/gpu_channel.h"
#include "content/common/gpu/gpu_channel_manager.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/gpu/sync_point_manager.h"
#include "content/public/common/content_switches.h"
#include "gpu/command_buffer/service/context_group.h"
#include "gpu/command_buffer/service/gpu_scheduler.h"
#include "gpu/command_buffer/service/texture_manager.h"
using gpu::gles2::ContextGroup;
using gpu::gles2::TextureManager;
typedef TextureManager::TextureInfo TextureInfo;
TextureImageTransportSurface::Texture::Texture()
: client_id(0),
sent_to_client(false) {
}
TextureImageTransportSurface::Texture::~Texture() {
}
TextureImageTransportSurface::TextureImageTransportSurface(
GpuChannelManager* manager,
GpuCommandBufferStub* stub,
const gfx::GLSurfaceHandle& handle)
: fbo_id_(0),
front_(0),
stub_destroyed_(false),
backbuffer_suggested_allocation_(true),
frontbuffer_suggested_allocation_(true),
frontbuffer_is_protected_(true),
protection_state_id_(0),
handle_(handle),
parent_stub_(NULL) {
helper_.reset(new ImageTransportHelper(this,
manager,
stub,
gfx::kNullPluginWindow));
}
TextureImageTransportSurface::~TextureImageTransportSurface() {
DCHECK(stub_destroyed_);
Destroy();
}
bool TextureImageTransportSurface::Initialize() {
GpuChannelManager* manager = helper_->manager();
GpuChannel* parent_channel = manager->LookupChannel(handle_.parent_client_id);
if (!parent_channel)
return false;
parent_stub_ = parent_channel->LookupCommandBuffer(handle_.parent_context_id);
if (!parent_stub_)
return false;
parent_stub_->AddDestructionObserver(this);
TextureManager* texture_manager =
parent_stub_->decoder()->GetContextGroup()->texture_manager();
DCHECK(texture_manager);
for (int i = 0; i < 2; ++i) {
Texture& texture = textures_[i];
texture.client_id = handle_.parent_texture_id[i];
texture.info = texture_manager->GetTextureInfo(texture.client_id);
if (!texture.info)
return false;
if (!texture.info->target())
texture_manager->SetInfoTarget(texture.info, GL_TEXTURE_2D);
texture_manager->SetParameter(
texture.info, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
texture_manager->SetParameter(
texture.info, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
texture_manager->SetParameter(
texture.info, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
texture_manager->SetParameter(
texture.info, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
surface_ = manager->GetDefaultOffscreenSurface();
if (!surface_.get())
return false;
if (!helper_->Initialize())
return false;
const CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kUIPrioritizeInGpuProcess))
helper_->SetPreemptByCounter(parent_channel->MessagesPendingCount());
return true;
}
void TextureImageTransportSurface::Destroy() {
if (parent_stub_) {
parent_stub_->decoder()->MakeCurrent();
ReleaseParentStub();
}
if (surface_.get())
surface_ = NULL;
helper_->Destroy();
}
bool TextureImageTransportSurface::Resize(const gfx::Size&) {
return true;
}
bool TextureImageTransportSurface::IsOffscreen() {
return true;
}
bool TextureImageTransportSurface::OnMakeCurrent(gfx::GLContext* context) {
if (stub_destroyed_) {
// Early-exit so that we don't recreate the fbo. We still want to return
// true, so that the context is made current and the GLES2DecoderImpl can
// release its own resources.
return true;
}
if (!fbo_id_) {
glGenFramebuffersEXT(1, &fbo_id_);
glBindFramebufferEXT(GL_FRAMEBUFFER, fbo_id_);
CreateBackTexture(gfx::Size(1, 1));
#ifndef NDEBUG
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
DLOG(ERROR) << "Framebuffer incomplete.";
glDeleteFramebuffersEXT(1, &fbo_id_);
fbo_id_ = 0;
return false;
}
#endif
DCHECK(helper_->stub());
helper_->stub()->AddDestructionObserver(this);
}
return true;
}
unsigned int TextureImageTransportSurface::GetBackingFrameBufferObject() {
return fbo_id_;
}
void TextureImageTransportSurface::SetBackbufferAllocation(bool allocation) {
if (backbuffer_suggested_allocation_ == allocation)
return;
backbuffer_suggested_allocation_ = allocation;
if (!helper_->MakeCurrent())
return;
if (backbuffer_suggested_allocation_) {
DCHECK(!textures_[back()].info->service_id() ||
!textures_[back()].sent_to_client);
CreateBackTexture(textures_[back()].size);
} else {
ReleaseTexture(back());
}
}
void TextureImageTransportSurface::SetFrontbufferAllocation(bool allocation) {
if (frontbuffer_suggested_allocation_ == allocation)
return;
frontbuffer_suggested_allocation_ = allocation;
AdjustFrontBufferAllocation();
}
void TextureImageTransportSurface::AdjustFrontBufferAllocation() {
if (!helper_->MakeCurrent())
return;
if (!frontbuffer_suggested_allocation_ && !frontbuffer_is_protected_ &&
textures_[front()].info->service_id()) {
ReleaseTexture(front());
if (textures_[front()].sent_to_client) {
GpuHostMsg_AcceleratedSurfaceRelease_Params params;
params.identifier = textures_[front()].client_id;
helper_->SendAcceleratedSurfaceRelease(params);
textures_[front()].sent_to_client = false;
}
}
}
void* TextureImageTransportSurface::GetShareHandle() {
return GetHandle();
}
void* TextureImageTransportSurface::GetDisplay() {
return surface_.get() ? surface_->GetDisplay() : NULL;
}
void* TextureImageTransportSurface::GetConfig() {
return surface_.get() ? surface_->GetConfig() : NULL;
}
void TextureImageTransportSurface::OnResize(gfx::Size size) {
CreateBackTexture(size);
}
void TextureImageTransportSurface::OnWillDestroyStub(
GpuCommandBufferStub* stub) {
if (stub == parent_stub_) {
ReleaseParentStub();
helper_->SetPreemptByCounter(NULL);
} else {
DCHECK(stub == helper_->stub());
stub->RemoveDestructionObserver(this);
// We are losing the stub owning us, this is our last chance to clean up the
// resources we allocated in the stub's context.
if (fbo_id_) {
glDeleteFramebuffersEXT(1, &fbo_id_);
CHECK_GL_ERROR();
fbo_id_ = 0;
}
stub_destroyed_ = true;
}
}
bool TextureImageTransportSurface::SwapBuffers() {
DCHECK(backbuffer_suggested_allocation_);
if (!frontbuffer_suggested_allocation_ || !frontbuffer_is_protected_)
return true;
if (!parent_stub_) {
LOG(ERROR) << "SwapBuffers failed because no parent stub.";
return false;
}
glFlush();
front_ = back();
previous_damage_rect_ = gfx::Rect(textures_[front()].size);
DCHECK(textures_[front()].client_id != 0);
GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params;
params.surface_handle = textures_[front()].client_id;
params.protection_state_id = protection_state_id_;
params.skip_ack = false;
helper_->SendAcceleratedSurfaceBuffersSwapped(params);
helper_->SetScheduled(false);
return true;
}
bool TextureImageTransportSurface::PostSubBuffer(
int x, int y, int width, int height) {
DCHECK(backbuffer_suggested_allocation_);
DCHECK(textures_[back()].info->service_id());
if (!frontbuffer_suggested_allocation_ || !frontbuffer_is_protected_)
return true;
// If we are recreating the frontbuffer with this swap, make sure we are
// drawing a full frame.
DCHECK(textures_[front()].info->service_id() ||
(!x && !y && gfx::Size(width, height) == textures_[back()].size));
if (!parent_stub_) {
LOG(ERROR) << "PostSubBuffer failed because no parent stub.";
return false;
}
const gfx::Rect new_damage_rect(x, y, width, height);
// An empty damage rect is a successful no-op.
if (new_damage_rect.IsEmpty())
return true;
int back_texture_service_id = textures_[back()].info->service_id();
int front_texture_service_id = textures_[front()].info->service_id();
gfx::Size expected_size = textures_[back()].size;
bool surfaces_same_size = textures_[front()].size == expected_size;
if (surfaces_same_size) {
std::vector<gfx::Rect> regions_to_copy;
GetRegionsToCopy(previous_damage_rect_, new_damage_rect, &regions_to_copy);
content::ScopedFrameBufferBinder fbo_binder(fbo_id_);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
front_texture_service_id,
0);
content::ScopedTextureBinder texture_binder(back_texture_service_id);
for (size_t i = 0; i < regions_to_copy.size(); ++i) {
const gfx::Rect& region_to_copy = regions_to_copy[i];
if (!region_to_copy.IsEmpty()) {
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, region_to_copy.x(),
region_to_copy.y(), region_to_copy.x(), region_to_copy.y(),
region_to_copy.width(), region_to_copy.height());
}
}
} else {
DCHECK(new_damage_rect == gfx::Rect(expected_size));
}
glFlush();
front_ = back();
previous_damage_rect_ = new_damage_rect;
DCHECK(textures_[front()].client_id);
GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params params;
params.surface_handle = textures_[front()].client_id;
params.x = x;
params.y = y;
params.width = width;
params.height = height;
params.protection_state_id = protection_state_id_;
helper_->SendAcceleratedSurfacePostSubBuffer(params);
helper_->SetScheduled(false);
return true;
}
std::string TextureImageTransportSurface::GetExtensions() {
std::string extensions = gfx::GLSurface::GetExtensions();
extensions += extensions.empty() ? "" : " ";
extensions += "GL_CHROMIUM_front_buffer_cached ";
extensions += "GL_CHROMIUM_post_sub_buffer";
return extensions;
}
gfx::Size TextureImageTransportSurface::GetSize() {
gfx::Size size = textures_[back()].size;
// OSMesa expects a non-zero size.
return gfx::Size(size.width() == 0 ? 1 : size.width(),
size.height() == 0 ? 1 : size.height());
}
void* TextureImageTransportSurface::GetHandle() {
return surface_.get() ? surface_->GetHandle() : NULL;
}
unsigned TextureImageTransportSurface::GetFormat() {
return surface_.get() ? surface_->GetFormat() : 0;
}
void TextureImageTransportSurface::OnSetFrontSurfaceIsProtected(
bool is_protected, uint32 protection_state_id) {
protection_state_id_ = protection_state_id;
if (frontbuffer_is_protected_ == is_protected)
return;
frontbuffer_is_protected_ = is_protected;
AdjustFrontBufferAllocation();
// If surface is set to protected, and we haven't actually released it yet,
// we can set the ui surface handle now just by sending a swap message.
if (is_protected && textures_[front()].info->service_id() &&
textures_[front()].sent_to_client) {
GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params;
params.surface_handle = textures_[front()].client_id;
params.protection_state_id = protection_state_id_;
params.skip_ack = true;
helper_->SendAcceleratedSurfaceBuffersSwapped(params);
}
}
void TextureImageTransportSurface::OnBufferPresented(uint32 sync_point) {
if (sync_point == 0) {
BufferPresentedImpl();
} else {
helper_->manager()->sync_point_manager()->AddSyncPointCallback(
sync_point,
base::Bind(&TextureImageTransportSurface::BufferPresentedImpl,
this->AsWeakPtr()));
}
}
void TextureImageTransportSurface::BufferPresentedImpl() {
// We're relying on the fact that the parent context is
// finished with it's context when it inserts the sync point that
// triggers this callback.
if (helper_->MakeCurrent()) {
if (textures_[front()].size != textures_[back()].size ||
!textures_[back()].info->service_id() ||
!textures_[back()].sent_to_client) {
// We may get an ACK from a stale swap just to reschedule. In that case,
// we may not have a backbuffer suggestion and should not recreate one.
if (backbuffer_suggested_allocation_)
CreateBackTexture(textures_[front()].size);
} else {
AttachBackTextureToFBO();
}
}
// Even if MakeCurrent fails, schedule anyway, to trigger the lost context
// logic.
helper_->SetScheduled(true);
}
void TextureImageTransportSurface::OnResizeViewACK() {
NOTREACHED();
}
void TextureImageTransportSurface::ReleaseTexture(int id) {
if (!parent_stub_)
return;
Texture& texture = textures_[id];
TextureInfo* info = texture.info;
DCHECK(info);
GLuint service_id = info->service_id();
if (!service_id)
return;
info->SetServiceId(0);
{
content::ScopedFrameBufferBinder fbo_binder(fbo_id_);
glDeleteTextures(1, &service_id);
}
glFlush();
CHECK_GL_ERROR();
}
void TextureImageTransportSurface::CreateBackTexture(const gfx::Size& size) {
if (!parent_stub_)
return;
Texture& texture = textures_[back()];
TextureInfo* info = texture.info;
DCHECK(info);
GLuint service_id = info->service_id();
if (service_id && texture.size == size && texture.sent_to_client)
return;
if (!service_id) {
glGenTextures(1, &service_id);
info->SetServiceId(service_id);
}
if (size != texture.size) {
texture.size = size;
TextureManager* texture_manager =
parent_stub_->decoder()->GetContextGroup()->texture_manager();
texture_manager->SetLevelInfo(
info,
GL_TEXTURE_2D,
0,
GL_RGBA,
size.width(),
size.height(),
1,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
true);
}
{
content::ScopedTextureBinder texture_binder(service_id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
size.width(), size.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
CHECK_GL_ERROR();
}
AttachBackTextureToFBO();
GpuHostMsg_AcceleratedSurfaceNew_Params params;
params.width = size.width();
params.height = size.height();
params.surface_handle = texture.client_id;
helper_->SendAcceleratedSurfaceNew(params);
texture.sent_to_client = true;
}
void TextureImageTransportSurface::AttachBackTextureToFBO() {
if (!parent_stub_)
return;
TextureInfo* info = textures_[back()].info;
DCHECK(info);
content::ScopedFrameBufferBinder fbo_binder(fbo_id_);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
info->service_id(),
0);
glFlush();
CHECK_GL_ERROR();
#ifndef NDEBUG
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
DLOG(ERROR) << "Framebuffer incomplete.";
}
#endif
}
void TextureImageTransportSurface::ReleaseParentStub() {
DCHECK(parent_stub_);
parent_stub_->RemoveDestructionObserver(this);
for (int i = 0; i < 2; ++i) {
Texture& texture = textures_[i];
texture.info = NULL;
if (!texture.sent_to_client)
continue;
GpuHostMsg_AcceleratedSurfaceRelease_Params params;
params.identifier = texture.client_id;
helper_->SendAcceleratedSurfaceRelease(params);
}
parent_stub_ = NULL;
}