blob: 67fc932df5ab29636704bfe8da4494663168abd1 [file] [log] [blame]
// Copyright (c) 2010 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 "build/build_config.h"
#include "base/ref_counted.h"
#include "base/singleton.h"
#include "base/weak_ptr.h"
#include "chrome/renderer/command_buffer_proxy.h"
#include "chrome/renderer/ggl/ggl.h"
#include "chrome/renderer/gpu_channel_host.h"
#include "chrome/renderer/gpu_video_service_host.h"
#include "chrome/renderer/media/gles2_video_decode_context.h"
#include "chrome/renderer/render_widget.h"
#include "ipc/ipc_channel_handle.h"
#if defined(ENABLE_GPU)
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/client/gles2_lib.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/GLES2/gles2_command_buffer.h"
#endif // ENABLE_GPU
namespace ggl {
#if defined(ENABLE_GPU)
namespace {
const int32 kCommandBufferSize = 1024 * 1024;
// TODO(kbr): make the transfer buffer size configurable via context
// creation attributes.
const int32 kTransferBufferSize = 1024 * 1024;
// Singleton used to initialize and terminate the gles2 library.
class GLES2Initializer {
public:
GLES2Initializer() {
gles2::Initialize();
}
~GLES2Initializer() {
gles2::Terminate();
}
private:
DISALLOW_COPY_AND_ASSIGN(GLES2Initializer);
};
} // namespace anonymous
// Manages a GL context.
class Context : public base::SupportsWeakPtr<Context> {
public:
Context(GpuChannelHost* channel, Context* parent);
~Context();
// Initialize a GGL context that can be used in association with a a GPU
// channel acquired from a RenderWidget or RenderView.
bool Initialize(gfx::NativeViewId view,
int render_view_id,
const gfx::Size& size,
const char* allowed_extensions,
const int32* attrib_list);
#if defined(OS_MACOSX)
// Asynchronously resizes an onscreen frame buffer.
void ResizeOnscreen(const gfx::Size& size);
#endif
// Asynchronously resizes an offscreen frame buffer.
void ResizeOffscreen(const gfx::Size& size);
// Provides a callback that will be invoked when SwapBuffers has completed
// service side.
void SetSwapBuffersCallback(Callback0::Type* callback) {
swap_buffers_callback_.reset(callback);
}
// For an offscreen frame buffer context, return the frame buffer ID with
// respect to the parent.
uint32 parent_texture_id() const {
return parent_texture_id_;
}
uint32 CreateParentTexture(const gfx::Size& size) const;
void DeleteParentTexture(uint32 texture) const;
// Destroy all resources associated with the GGL context.
void Destroy();
// Make a GGL context current for the calling thread.
static bool MakeCurrent(Context* context);
// Display all content rendered since last call to SwapBuffers.
// TODO(apatrick): support rendering to browser window. This function is
// not useful at this point.
bool SwapBuffers();
// Create a hardware accelerated video decoder associated with this context.
media::VideoDecodeEngine* CreateVideoDecodeEngine();
// Create a hardware video decode context associated with this context.
media::VideoDecodeContext* CreateVideoDecodeContext(MessageLoop* message_loop,
bool hardware_decoder);
// Get the current error code. Clears context's error code afterwards.
Error GetError();
// Replace the current error code with this.
void SetError(Error error);
// TODO(gman): Remove this.
void DisableShaderTranslation();
gpu::gles2::GLES2Implementation* gles2_implementation() const {
return gles2_implementation_;
}
private:
void OnSwapBuffers();
scoped_refptr<GpuChannelHost> channel_;
base::WeakPtr<Context> parent_;
scoped_ptr<Callback0::Type> swap_buffers_callback_;
uint32 parent_texture_id_;
CommandBufferProxy* command_buffer_;
gpu::gles2::GLES2CmdHelper* gles2_helper_;
int32 transfer_buffer_id_;
gpu::gles2::GLES2Implementation* gles2_implementation_;
gfx::Size size_;
Error last_error_;
DISALLOW_COPY_AND_ASSIGN(Context);
};
Context::Context(GpuChannelHost* channel, Context* parent)
: channel_(channel),
parent_(parent ? parent->AsWeakPtr() : base::WeakPtr<Context>()),
parent_texture_id_(0),
command_buffer_(NULL),
gles2_helper_(NULL),
transfer_buffer_id_(0),
gles2_implementation_(NULL),
last_error_(SUCCESS) {
DCHECK(channel);
}
Context::~Context() {
Destroy();
}
bool Context::Initialize(gfx::NativeViewId view,
int render_view_id,
const gfx::Size& size,
const char* allowed_extensions,
const int32* attrib_list) {
DCHECK(size.width() >= 0 && size.height() >= 0);
if (channel_->state() != GpuChannelHost::kConnected)
return false;
// Ensure the gles2 library is initialized first in a thread safe way.
Singleton<GLES2Initializer>::get();
// Allocate a frame buffer ID with respect to the parent.
if (parent_.get()) {
// Flush any remaining commands in the parent context to make sure the
// texture id accounting stays consistent.
int32 token = parent_->gles2_helper_->InsertToken();
parent_->gles2_helper_->WaitForToken(token);
parent_texture_id_ = parent_->gles2_implementation_->MakeTextureId();
}
std::vector<int32> attribs;
while (attrib_list) {
int32 attrib = *attrib_list++;
switch (attrib) {
// Known attributes
case ggl::GGL_ALPHA_SIZE:
case ggl::GGL_BLUE_SIZE:
case ggl::GGL_GREEN_SIZE:
case ggl::GGL_RED_SIZE:
case ggl::GGL_DEPTH_SIZE:
case ggl::GGL_STENCIL_SIZE:
case ggl::GGL_SAMPLES:
case ggl::GGL_SAMPLE_BUFFERS:
attribs.push_back(attrib);
attribs.push_back(*attrib_list++);
break;
case ggl::GGL_NONE:
attribs.push_back(attrib);
attrib_list = NULL;
break;
default:
SetError(ggl::BAD_ATTRIBUTE);
attribs.push_back(ggl::GGL_NONE);
attrib_list = NULL;
break;
}
}
// Create a proxy to a command buffer in the GPU process.
if (view) {
command_buffer_ = channel_->CreateViewCommandBuffer(
view,
render_view_id,
allowed_extensions,
attribs);
} else {
CommandBufferProxy* parent_command_buffer =
parent_.get() ? parent_->command_buffer_ : NULL;
command_buffer_ = channel_->CreateOffscreenCommandBuffer(
parent_command_buffer,
size,
allowed_extensions,
attribs,
parent_texture_id_);
}
if (!command_buffer_) {
Destroy();
return false;
}
// Initiaize the command buffer.
if (!command_buffer_->Initialize(kCommandBufferSize)) {
Destroy();
return false;
}
command_buffer_->SetSwapBuffersCallback(NewCallback(this,
&Context::OnSwapBuffers));
// Create the GLES2 helper, which writes the command buffer protocol.
gles2_helper_ = new gpu::gles2::GLES2CmdHelper(command_buffer_);
if (!gles2_helper_->Initialize(kCommandBufferSize)) {
Destroy();
return false;
}
// Create a transfer buffer used to copy resources between the renderer
// process and the GPU process.
transfer_buffer_id_ =
command_buffer_->CreateTransferBuffer(kTransferBufferSize);
if (transfer_buffer_id_ < 0) {
Destroy();
return false;
}
// Map the buffer into the renderer process's address space.
gpu::Buffer transfer_buffer =
command_buffer_->GetTransferBuffer(transfer_buffer_id_);
if (!transfer_buffer.ptr) {
Destroy();
return false;
}
// Create the object exposing the OpenGL API.
gles2_implementation_ = new gpu::gles2::GLES2Implementation(
gles2_helper_,
transfer_buffer.size,
transfer_buffer.ptr,
transfer_buffer_id_,
false);
size_ = size;
return true;
}
#if defined(OS_MACOSX)
void Context::ResizeOnscreen(const gfx::Size& size) {
DCHECK(size.width() > 0 && size.height() > 0);
size_ = size;
command_buffer_->SetWindowSize(size);
}
#endif
void Context::ResizeOffscreen(const gfx::Size& size) {
DCHECK(size.width() > 0 && size.height() > 0);
if (size_ != size) {
command_buffer_->ResizeOffscreenFrameBuffer(size);
size_ = size;
}
}
uint32 Context::CreateParentTexture(const gfx::Size& size) const {
// Allocate a texture ID with respect to the parent.
if (parent_.get()) {
if (!MakeCurrent(parent_.get()))
return 0;
uint32 texture_id = parent_->gles2_implementation_->MakeTextureId();
parent_->gles2_implementation_->BindTexture(GL_TEXTURE_2D, texture_id);
parent_->gles2_implementation_->TexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
parent_->gles2_implementation_->TexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
parent_->gles2_implementation_->TexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
parent_->gles2_implementation_->TexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
parent_->gles2_implementation_->TexImage2D(GL_TEXTURE_2D,
0, // mip level
GL_RGBA,
size.width(),
size.height(),
0, // border
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL);
// Make sure that the parent texture's storage is allocated before we let
// the caller attempt to use it.
int32 token = parent_->gles2_helper_->InsertToken();
parent_->gles2_helper_->WaitForToken(token);
return texture_id;
}
return 0;
}
void Context::DeleteParentTexture(uint32 texture) const {
if (parent_.get()) {
if (!MakeCurrent(parent_.get()))
return;
parent_->gles2_implementation_->DeleteTextures(1, &texture);
}
}
void Context::Destroy() {
if (parent_.get() && parent_texture_id_ != 0)
parent_->gles2_implementation_->FreeTextureId(parent_texture_id_);
delete gles2_implementation_;
gles2_implementation_ = NULL;
if (command_buffer_ && transfer_buffer_id_ != 0) {
command_buffer_->DestroyTransferBuffer(transfer_buffer_id_);
transfer_buffer_id_ = 0;
}
delete gles2_helper_;
gles2_helper_ = NULL;
if (channel_ && command_buffer_) {
channel_->DestroyCommandBuffer(command_buffer_);
command_buffer_ = NULL;
}
channel_ = NULL;
}
bool Context::MakeCurrent(Context* context) {
if (context) {
gles2::SetGLContext(context->gles2_implementation_);
// Don't request latest error status from service. Just use the locally
// cached information from the last flush.
// TODO(apatrick): I'm not sure if this should actually change the
// current context if it fails. For now it gets changed even if it fails
// because making GL calls with a NULL context crashes.
if (context->command_buffer_->GetLastState().error != gpu::error::kNoError)
return false;
} else {
gles2::SetGLContext(NULL);
}
return true;
}
bool Context::SwapBuffers() {
// Don't request latest error status from service. Just use the locally cached
// information from the last flush.
if (command_buffer_->GetLastState().error != gpu::error::kNoError)
return false;
gles2_implementation_->SwapBuffers();
return true;
}
media::VideoDecodeEngine* Context::CreateVideoDecodeEngine() {
return channel_->gpu_video_service_host()->CreateVideoDecoder(
command_buffer_->route_id());
}
media::VideoDecodeContext* Context::CreateVideoDecodeContext(
MessageLoop* message_loop, bool hardware_decoder) {
return new Gles2VideoDecodeContext(message_loop, hardware_decoder, this);
}
Error Context::GetError() {
gpu::CommandBuffer::State state = command_buffer_->GetState();
if (state.error == gpu::error::kNoError) {
Error old_error = last_error_;
last_error_ = SUCCESS;
return old_error;
} else {
// All command buffer errors are unrecoverable. The error is treated as a
// lost context: destroy the context and create another one.
return CONTEXT_LOST;
}
}
void Context::SetError(Error error) {
last_error_ = error;
}
// TODO(gman): Remove This
void Context::DisableShaderTranslation() {
gles2_implementation_->CommandBufferEnable(PEPPER3D_SKIP_GLSL_TRANSLATION);
}
void Context::OnSwapBuffers() {
if (swap_buffers_callback_.get())
swap_buffers_callback_->Run();
}
#endif // ENABLE_GPU
Context* CreateViewContext(GpuChannelHost* channel,
gfx::NativeViewId view,
int render_view_id,
const char* allowed_extensions,
const int32* attrib_list) {
#if defined(ENABLE_GPU)
scoped_ptr<Context> context(new Context(channel, NULL));
if (!context->Initialize(
view, render_view_id, gfx::Size(), allowed_extensions, attrib_list))
return NULL;
return context.release();
#else
return NULL;
#endif
}
#if defined(OS_MACOSX)
void ResizeOnscreenContext(Context* context, const gfx::Size& size) {
#if defined(ENABLE_GPU)
context->ResizeOnscreen(size);
#endif
}
#endif
Context* CreateOffscreenContext(GpuChannelHost* channel,
Context* parent,
const gfx::Size& size,
const char* allowed_extensions,
const int32* attrib_list) {
#if defined(ENABLE_GPU)
scoped_ptr<Context> context(new Context(channel, parent));
if (!context->Initialize(0, 0, size, allowed_extensions, attrib_list))
return NULL;
return context.release();
#else
return NULL;
#endif
}
void ResizeOffscreenContext(Context* context, const gfx::Size& size) {
#if defined(ENABLE_GPU)
context->ResizeOffscreen(size);
#endif
}
uint32 GetParentTextureId(Context* context) {
#if defined(ENABLE_GPU)
return context->parent_texture_id();
#else
return 0;
#endif
}
uint32 CreateParentTexture(Context* context, const gfx::Size& size) {
#if defined(ENABLE_GPU)
return context->CreateParentTexture(size);
#else
return 0;
#endif
}
void DeleteParentTexture(Context* context, uint32 texture) {
#if defined(ENABLE_GPU)
context->DeleteParentTexture(texture);
#endif
}
void SetSwapBuffersCallback(Context* context,
Callback0::Type* callback) {
#if defined(ENABLE_GPU)
context->SetSwapBuffersCallback(callback);
#endif
}
bool MakeCurrent(Context* context) {
#if defined(ENABLE_GPU)
return Context::MakeCurrent(context);
#else
return false;
#endif
}
bool SwapBuffers(Context* context) {
#if defined(ENABLE_GPU)
if (!context)
return false;
return context->SwapBuffers();
#else
return false;
#endif
}
bool DestroyContext(Context* context) {
#if defined(ENABLE_GPU)
if (!context)
return false;
delete context;
return true;
#else
return false;
#endif
}
media::VideoDecodeEngine* CreateVideoDecodeEngine(Context* context) {
return context->CreateVideoDecodeEngine();
}
media::VideoDecodeContext* CreateVideoDecodeContext(
Context* context, MessageLoop* message_loop, bool hardware_decoder) {
return context->CreateVideoDecodeContext(message_loop, hardware_decoder);
}
Error GetError(Context* context) {
#if defined(ENABLE_GPU)
return context->GetError();
#else
return NOT_INITIALIZED;
#endif
}
// TODO(gman): Remove This
void DisableShaderTranslation(Context* context) {
#if defined(ENABLE_GPU)
if (context) {
context->DisableShaderTranslation();
}
#endif
}
gpu::gles2::GLES2Implementation* GetImplementation(Context* context) {
if (!context)
return NULL;
return context->gles2_implementation();
}
} // namespace ggl