blob: eb41e82ccd9571f397a07da350d16075d3af2f0b [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/renderer/pepper/ppb_graphics_3d_impl.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/common/gpu_stream_constants.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/web_preferences.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/plugin_instance_throttler_impl.h"
#include "content/renderer/pepper/plugin_module.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_view_impl.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/ipc/client/command_buffer_proxy_impl.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "ppapi/c/ppp_graphics_3d.h"
#include "ppapi/thunk/enter.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/web/WebConsoleMessage.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebPluginContainer.h"
#include "third_party/khronos/GLES2/gl2.h"
using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_Graphics3D_API;
using blink::WebConsoleMessage;
using blink::WebLocalFrame;
using blink::WebPluginContainer;
using blink::WebString;
namespace content {
PPB_Graphics3D_Impl::PPB_Graphics3D_Impl(PP_Instance instance)
: PPB_Graphics3D_Shared(instance),
bound_to_instance_(false),
commit_pending_(false),
has_alpha_(false),
image_chromium_enabled_(
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisablePepper3DImageChromium) &&
base::FeatureList::IsEnabled(features::kPepper3DImageChromium)),
use_image_chromium_(false),
weak_ptr_factory_(this) {}
PPB_Graphics3D_Impl::~PPB_Graphics3D_Impl() {
// Unset the client before the command_buffer_ is destroyed, similar to how
// WeakPtrFactory invalidates before it.
if (command_buffer_)
command_buffer_->SetGpuControlClient(nullptr);
}
// static
PP_Resource PPB_Graphics3D_Impl::CreateRaw(
PP_Instance instance,
PP_Resource share_context,
const gpu::gles2::ContextCreationAttribHelper& attrib_helper,
gpu::Capabilities* capabilities,
base::SharedMemoryHandle* shared_state_handle,
gpu::CommandBufferId* command_buffer_id) {
PPB_Graphics3D_API* share_api = nullptr;
if (share_context) {
EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
if (enter.failed())
return 0;
share_api = enter.object();
}
scoped_refptr<PPB_Graphics3D_Impl> graphics_3d(
new PPB_Graphics3D_Impl(instance));
if (!graphics_3d->InitRaw(share_api, attrib_helper, capabilities,
shared_state_handle, command_buffer_id))
return 0;
return graphics_3d->GetReference();
}
PP_Bool PPB_Graphics3D_Impl::SetGetBuffer(int32_t transfer_buffer_id) {
GetCommandBuffer()->SetGetBuffer(transfer_buffer_id);
return PP_TRUE;
}
scoped_refptr<gpu::Buffer> PPB_Graphics3D_Impl::CreateTransferBuffer(
uint32_t size,
int32_t* id) {
return GetCommandBuffer()->CreateTransferBuffer(size, id);
}
PP_Bool PPB_Graphics3D_Impl::DestroyTransferBuffer(int32_t id) {
GetCommandBuffer()->DestroyTransferBuffer(id);
return PP_TRUE;
}
PP_Bool PPB_Graphics3D_Impl::Flush(int32_t put_offset) {
GetCommandBuffer()->Flush(put_offset);
return PP_TRUE;
}
gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForTokenInRange(
int32_t start,
int32_t end) {
return GetCommandBuffer()->WaitForTokenInRange(start, end);
}
gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForGetOffsetInRange(
uint32_t set_get_buffer_count,
int32_t start,
int32_t end) {
return GetCommandBuffer()->WaitForGetOffsetInRange(set_get_buffer_count,
start, end);
}
void PPB_Graphics3D_Impl::EnsureWorkVisible() {
command_buffer_->EnsureWorkVisible();
}
void PPB_Graphics3D_Impl::TakeFrontBuffer() {
if (!taken_front_buffer_.IsZero()) {
DLOG(ERROR)
<< "TakeFrontBuffer should only be called once before DoSwapBuffers";
return;
}
taken_front_buffer_ = GenerateMailbox();
command_buffer_->TakeFrontBuffer(taken_front_buffer_);
}
void PPB_Graphics3D_Impl::ReturnFrontBuffer(const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token,
bool is_lost) {
command_buffer_->ReturnFrontBuffer(mailbox, sync_token, is_lost);
mailboxes_to_reuse_.push_back(mailbox);
}
bool PPB_Graphics3D_Impl::BindToInstance(bool bind) {
bound_to_instance_ = bind;
return true;
}
bool PPB_Graphics3D_Impl::IsOpaque() { return !has_alpha_; }
void PPB_Graphics3D_Impl::ViewInitiatedPaint() {
commit_pending_ = false;
if (HasPendingSwap())
SwapBuffersACK(PP_OK);
}
gpu::CommandBufferProxyImpl* PPB_Graphics3D_Impl::GetCommandBufferProxy() {
DCHECK(command_buffer_);
return command_buffer_.get();
}
gpu::CommandBuffer* PPB_Graphics3D_Impl::GetCommandBuffer() {
return command_buffer_.get();
}
gpu::GpuControl* PPB_Graphics3D_Impl::GetGpuControl() {
return command_buffer_.get();
}
int32_t PPB_Graphics3D_Impl::DoSwapBuffers(const gpu::SyncToken& sync_token,
const gfx::Size& size) {
DCHECK(command_buffer_);
if (taken_front_buffer_.IsZero()) {
DLOG(ERROR) << "TakeFrontBuffer should be called before DoSwapBuffers";
return PP_ERROR_FAILED;
}
if (bound_to_instance_) {
// If we are bound to the instance, we need to ask the compositor
// to commit our backing texture so that the graphics appears on the page.
// When the backing texture will be committed we get notified via
// ViewFlushedPaint().
//
// Don't need to check for NULL from GetPluginInstance since when we're
// bound, we know our instance is valid.
bool is_overlay_candidate = use_image_chromium_;
viz::TextureMailbox texture_mailbox(
taken_front_buffer_, sync_token,
// TODO(reveman): Get texture target from browser process.
#if defined(OS_MACOSX)
use_image_chromium_ ? GL_TEXTURE_RECTANGLE_ARB : GL_TEXTURE_2D,
#else
GL_TEXTURE_2D,
#endif
size, is_overlay_candidate);
taken_front_buffer_.SetZero();
HostGlobals::Get()
->GetInstance(pp_instance())
->CommitTextureMailbox(texture_mailbox);
commit_pending_ = true;
} else {
// Wait for the command to complete on the GPU to allow for throttling.
command_buffer_->SignalSyncToken(
sync_token, base::Bind(&PPB_Graphics3D_Impl::OnSwapBuffers,
weak_ptr_factory_.GetWeakPtr()));
}
return PP_OK_COMPLETIONPENDING;
}
bool PPB_Graphics3D_Impl::InitRaw(
PPB_Graphics3D_API* share_context,
const gpu::gles2::ContextCreationAttribHelper& requested_attribs,
gpu::Capabilities* capabilities,
base::SharedMemoryHandle* shared_state_handle,
gpu::CommandBufferId* command_buffer_id) {
PepperPluginInstanceImpl* plugin_instance =
HostGlobals::Get()->GetInstance(pp_instance());
if (!plugin_instance)
return false;
RenderFrame* render_frame = plugin_instance->GetRenderFrame();
if (!render_frame)
return false;
const WebPreferences& prefs = render_frame->GetWebkitPreferences();
// 3D access might be disabled.
if (!prefs.pepper_3d_enabled)
return false;
// Force SW rendering for keyframe extraction to avoid pixel reads from VRAM.
PluginInstanceThrottlerImpl* throttler = plugin_instance->throttler();
if (throttler && throttler->needs_representative_keyframe())
return false;
RenderThreadImpl* render_thread = RenderThreadImpl::current();
if (!render_thread)
return false;
scoped_refptr<gpu::GpuChannelHost> channel =
render_thread->EstablishGpuChannelSync();
if (!channel)
return false;
// 3D access might be blacklisted.
if (channel->gpu_feature_info()
.status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL] ==
gpu::kGpuFeatureStatusBlacklisted) {
return false;
}
has_alpha_ = requested_attribs.alpha_size > 0;
gpu::gles2::ContextCreationAttribHelper attrib_helper = requested_attribs;
// At this point we have a GPU channel so we can see whether
// features have been blacklisted.
use_image_chromium_ = image_chromium_enabled_ &&
!channel->gpu_feature_info().IsWorkaroundEnabled(
gpu::DISABLE_GPU_MEMORY_BUFFERS_AS_RENDER_TARGETS);
attrib_helper.should_use_native_gmb_for_backbuffer = use_image_chromium_;
attrib_helper.context_type = gpu::gles2::CONTEXT_TYPE_OPENGLES2;
gpu::CommandBufferProxyImpl* share_buffer = nullptr;
if (!plugin_instance->is_flash_plugin())
UMA_HISTOGRAM_BOOLEAN("Pepper.Graphics3DHasShareGroup", !!share_context);
if (share_context) {
PPB_Graphics3D_Impl* share_graphics =
static_cast<PPB_Graphics3D_Impl*>(share_context);
share_buffer = share_graphics->GetCommandBufferProxy();
}
command_buffer_ = std::make_unique<gpu::CommandBufferProxyImpl>(
std::move(channel), kGpuStreamIdDefault,
base::ThreadTaskRunnerHandle::Get());
auto result = command_buffer_->Initialize(
gpu::kNullSurfaceHandle, share_buffer, kGpuStreamPriorityDefault,
attrib_helper, GURL::EmptyGURL());
if (result != gpu::ContextResult::kSuccess)
return false;
command_buffer_->SetGpuControlClient(this);
if (shared_state_handle)
*shared_state_handle = command_buffer_->GetSharedStateHandle();
if (capabilities)
*capabilities = command_buffer_->GetCapabilities();
if (command_buffer_id)
*command_buffer_id = command_buffer_->GetCommandBufferID();
return true;
}
void PPB_Graphics3D_Impl::OnGpuControlErrorMessage(const char* message,
int32_t id) {
if (!bound_to_instance_)
return;
WebPluginContainer* container =
HostGlobals::Get()->GetInstance(pp_instance())->container();
if (!container)
return;
WebLocalFrame* frame = container->GetDocument().GetFrame();
if (!frame)
return;
WebConsoleMessage console_message = WebConsoleMessage(
WebConsoleMessage::kLevelError, WebString::FromUTF8(message));
frame->AddMessageToConsole(console_message);
}
void PPB_Graphics3D_Impl::OnGpuControlLostContext() {
#if DCHECK_IS_ON()
// This should never occur more than once.
DCHECK(!lost_context_);
lost_context_ = true;
#endif
// Don't need to check for null from GetPluginInstance since when we're
// bound, we know our instance is valid.
if (bound_to_instance_) {
HostGlobals::Get()->GetInstance(pp_instance())->BindGraphics(pp_instance(),
0);
}
// Send context lost to plugin. This may have been caused by a PPAPI call, so
// avoid re-entering.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&PPB_Graphics3D_Impl::SendContextLost,
weak_ptr_factory_.GetWeakPtr()));
}
void PPB_Graphics3D_Impl::OnGpuControlLostContextMaybeReentrant() {
// No internal state to update on lost context.
}
void PPB_Graphics3D_Impl::OnSwapBuffers() {
if (HasPendingSwap()) {
// If we're off-screen, no need to trigger and wait for compositing.
// Just send the swap-buffers ACK to the plugin immediately.
commit_pending_ = false;
SwapBuffersACK(PP_OK);
}
}
void PPB_Graphics3D_Impl::SendContextLost() {
// By the time we run this, the instance may have been deleted, or in the
// process of being deleted. Even in the latter case, we don't want to send a
// callback after DidDestroy.
PepperPluginInstanceImpl* instance =
HostGlobals::Get()->GetInstance(pp_instance());
if (!instance || !instance->container())
return;
// This PPB_Graphics3D_Impl could be deleted during the call to
// GetPluginInterface (which sends a sync message in some cases). We still
// send the Graphics3DContextLost to the plugin; the instance may care about
// that event even though this context has been destroyed.
PP_Instance this_pp_instance = pp_instance();
const PPP_Graphics3D* ppp_graphics_3d = static_cast<const PPP_Graphics3D*>(
instance->module()->GetPluginInterface(PPP_GRAPHICS_3D_INTERFACE));
// We have to check *again* that the instance exists, because it could have
// been deleted during GetPluginInterface(). Even the PluginModule could be
// deleted, but in that case, the instance should also be gone, so the
// GetInstance check covers both cases.
if (ppp_graphics_3d && HostGlobals::Get()->GetInstance(this_pp_instance))
ppp_graphics_3d->Graphics3DContextLost(this_pp_instance);
}
gpu::Mailbox PPB_Graphics3D_Impl::GenerateMailbox() {
if (!mailboxes_to_reuse_.empty()) {
gpu::Mailbox mailbox = mailboxes_to_reuse_.back();
mailboxes_to_reuse_.pop_back();
return mailbox;
}
return gpu::Mailbox::Generate();
}
} // namespace content