// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "content/renderer/pepper/ppb_graphics_3d_impl.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/not_fatal_until.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/gpu_stream_constants.h"
#include "content/renderer/pepper/host_globals.h"
#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
#include "content/renderer/pepper/plugin_module.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/common/context_creation_attribs.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/client/client_shared_image_interface.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/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/web_console_message.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_plugin_container.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "ui/gfx/switches.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 {
// This class encapsulates ColorBuffer for the plugin. It wraps corresponding
// SharedImage that we draw to and that we send to display compositor.
// Can be in one of the 3 states:
// Detached -- ColorBuffer is initialized and ready to use.
// Attached -- ColorBuffer is currently attached to the default frame buffer and
// we're drawing to it. Should be at most one in this state.
// InCompositor -- SharedImage from the ColorBuffer was sent to display
// compositor. It's considered busy until display compositor will return the
// resources.
// ColorBuffers created detached and transitioned to other states in a Detached
// => Attached => Detached => InCompositor => Detached sequence.
class PPB_Graphics3D_Impl::ColorBuffer {
public:
ColorBuffer(gpu::SharedImageInterface* sii,
gfx::Size size,
bool has_alpha,
bool is_single_buffered)
: sii_(sii), size_(size), is_single_buffered_(is_single_buffered) {
gpu::SharedImageUsageSet usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
gpu::SHARED_IMAGE_USAGE_GLES2_WRITE;
if (is_single_buffered_)
usage |= gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
// It's possible to create Graphics3D with zero size. To avoid creating
// shared image with zero size which will fail, we create 1x1. This matches
// legacy behaviour where command decoders would use 1x1 for any empty
// `offscreen_framebuffer_size`. Note, that to avoid any size mismatches, we
// keep `size_` intact.
auto shared_image_size = size.IsEmpty() ? gfx::Size(1, 1) : size;
// Note, that we intentionally don't handle SCANOUT here. While
// kPepper3DImageChromium is enabled on some CrOS devices, SkiaRenderer
// don't support overlays for legacy mailboxes. To avoid any problems with
// overlays, we don't introduce them here.
client_shared_image_ = sii_->CreateSharedImage(
{has_alpha ? viz::SinglePlaneFormat::kRGBA_8888
: viz::SinglePlaneFormat::kRGBX_8888,
shared_image_size, gfx::ColorSpace::CreateSRGB(),
kTopLeft_GrSurfaceOrigin, kUnpremul_SkAlphaType, usage,
"PPBGraphics3DImpl"},
gpu::SurfaceHandle());
CHECK(client_shared_image_);
sync_token_ = sii_->GenVerifiedSyncToken();
}
~ColorBuffer() {
DCHECK_NE(state, State::kAttached);
sii_->DestroySharedImage(destruction_sync_token_,
std::move(client_shared_image_));
}
void Attach(gpu::CommandBufferProxyImpl* command_buffer,
bool samples_count,
bool preserve,
bool needs_depth,
bool needs_stencil) {
DCHECK_EQ(state, State::kDetached);
command_buffer->SetDefaultFramebufferSharedImage(
client_shared_image_->mailbox(), sync_token_, samples_count, preserve,
needs_depth, needs_stencil);
state = State::kAttached;
sync_token_.Clear();
}
void Detach(gpu::CommandBufferProxyImpl* command_buffer) {
DCHECK_EQ(state, State::kAttached);
command_buffer->SetDefaultFramebufferSharedImage(
gpu::Mailbox(), gpu::SyncToken(), 0, false, false, false);
state = State::kDetached;
}
// Note that the pointer returned from Export() is never null.
const scoped_refptr<gpu::ClientSharedImage>& Export() {
DCHECK_EQ(state, State::kDetached);
// In single buffered mode we use same image regardless if it's in
// compositor or not, so don't track here.
if (!is_single_buffered_)
state = State::kInCompositor;
return client_shared_image_;
}
void UpdateDestructionSyncToken(const gpu::SyncToken& token) {
destruction_sync_token_ = token;
}
void Recycle(const gpu::SyncToken& sync_token) {
DCHECK_EQ(state, State::kInCompositor);
state = State::kDetached;
// Update both `sync_token_` which we supposed to wait on before reattaching
// this color buffer and `destruction_sync_token_` which wait on before
// destroying the underlying shared image, so we don't destroy it while
// display compositor still uses it.
sync_token_ = sync_token;
destruction_sync_token_ = sync_token;
}
const gfx::Size& size() { return size_; }
bool IsAttached() { return state == State::kAttached; }
private:
enum class State { kDetached, kAttached, kInCompositor };
State state = State::kDetached;
const raw_ptr<gpu::SharedImageInterface> sii_;
const gfx::Size size_;
scoped_refptr<gpu::ClientSharedImage> client_shared_image_;
// SyncToken to wait on before re-using this color buffer.
gpu::SyncToken sync_token_;
// SyncToken to wait before destroying the underlying shared image.
gpu::SyncToken destruction_sync_token_;
const bool is_single_buffered_;
};
PPB_Graphics3D_Impl::PPB_Graphics3D_Impl(PP_Instance instance)
: PPB_Graphics3D_Shared(instance),
bound_to_instance_(false),
commit_pending_(false) {}
PPB_Graphics3D_Impl::~PPB_Graphics3D_Impl() {
if (current_color_buffer_ && current_color_buffer_->IsAttached()) {
current_color_buffer_->Detach(command_buffer_.get());
}
current_color_buffer_.reset();
available_color_buffers_.clear();
inflight_color_buffers_.clear();
// 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 ppapi::Graphics3DContextAttribs& context_attribs,
gpu::Capabilities* capabilities,
gpu::GLCapabilities* gl_capabilities,
const base::UnsafeSharedMemoryRegion** shared_state_region,
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, context_attribs, capabilities,
gl_capabilities, shared_state_region,
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::ReturnFrontBuffer(const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token,
bool is_lost) {
if (is_single_buffered_) {
// We don't verify that mailbox is the same we have in the
// `current_color_buffer_` because it could have changed do to resize.
} else {
auto it = inflight_color_buffers_.find(mailbox);
CHECK(it != inflight_color_buffers_.end(), base::NotFatalUntil::M130);
RecycleColorBuffer(std::move(it->second), sync_token, is_lost);
inflight_color_buffers_.erase(it);
}
}
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();
}
bool PPB_Graphics3D_Impl::InitRaw(
PPB_Graphics3D_API* share_context,
const ppapi::Graphics3DContextAttribs& requested_attribs,
gpu::Capabilities* capabilities,
gpu::GLCapabilities* gl_capabilities,
const base::UnsafeSharedMemoryRegion** shared_state_region,
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 blink::web_pref::WebPreferences& prefs =
render_frame->GetBlinkPreferences();
// 3D access might be disabled.
if (!prefs.pepper_3d_enabled)
return false;
RenderThreadImpl* render_thread = RenderThreadImpl::current();
if (!render_thread)
return false;
if (render_thread->IsGpuCompositingDisabled())
return false;
scoped_refptr<gpu::GpuChannelHost> channel =
render_thread->EstablishGpuChannelSync();
if (!channel)
return false;
// 3D access might be blocklisted.
if (channel->gpu_feature_info()
.status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL] ==
gpu::kGpuFeatureStatusBlocklisted) {
return false;
}
has_alpha_ = requested_attribs.alpha_size > 0;
is_single_buffered_ = requested_attribs.single_buffer;
needs_depth_ = requested_attribs.depth_size > 0;
needs_stencil_ = requested_attribs.stencil_size > 0;
swapchain_size_ = requested_attribs.offscreen_framebuffer_size;
// If we're in single buffered mode, we don't need additional buffer to
// preserve contents.
preserve_ = requested_attribs.buffer_preserved && !is_single_buffered_;
if (requested_attribs.samples > 0 && requested_attribs.sample_buffers > 0 &&
!requested_attribs.single_buffer)
samples_count_ = requested_attribs.samples;
gpu::ContextCreationAttribs attrib_helper;
attrib_helper.context_type = gpu::CONTEXT_TYPE_OPENGLES2;
gpu::CommandBufferProxyImpl* share_buffer = nullptr;
if (share_context) {
PPB_Graphics3D_Impl* share_graphics =
static_cast<PPB_Graphics3D_Impl*>(share_context);
share_buffer = share_graphics->GetCommandBufferProxy();
}
shared_image_interface_ = channel->CreateClientSharedImageInterface();
command_buffer_ = std::make_unique<gpu::CommandBufferProxyImpl>(
std::move(channel), kGpuStreamIdDefault,
base::SingleThreadTaskRunner::GetCurrentDefault());
auto result = command_buffer_->Initialize(
gpu::kNullSurfaceHandle, share_buffer, kGpuStreamPriorityDefault,
attrib_helper, GURL());
if (result != gpu::ContextResult::kSuccess)
return false;
command_buffer_->SetGpuControlClient(this);
if (shared_state_region)
*shared_state_region = &command_buffer_->GetSharedStateRegion();
if (capabilities) {
*capabilities = command_buffer_->GetCapabilities();
}
if (gl_capabilities) {
*gl_capabilities = command_buffer_->GetGLCapabilities();
}
if (command_buffer_id)
*command_buffer_id = command_buffer_->GetCommandBufferID();
current_color_buffer_ = GetOrCreateColorBuffer();
current_color_buffer_->Attach(command_buffer_.get(), samples_count_,
preserve_, needs_depth_, needs_stencil_);
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(
blink::mojom::ConsoleMessageLevel::kError, 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::SingleThreadTaskRunner::GetCurrentDefault()->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::OnGpuControlReturnData(
base::span<const uint8_t> data) {
NOTIMPLEMENTED();
}
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);
}
int32_t PPB_Graphics3D_Impl::DoSwapBuffers(const gpu::SyncToken& sync_token,
const gfx::Size& size) {
DCHECK(command_buffer_);
DCHECK(current_color_buffer_);
DCHECK_EQ(size, current_color_buffer_->size());
if (current_color_buffer_->IsAttached()) {
DLOG(ERROR)
<< "ResolveAndDetachFramebuffer should be called before DoSwapBuffers";
return PP_ERROR_FAILED;
}
current_color_buffer_->UpdateDestructionSyncToken(sync_token);
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.
// Note, that we intentionally don't handle SCANOUT here. While
// kPepper3DImageChromium is enabled on some CrOS devices, SkiaRenderer
// don't support overlays for legacy mailboxes. To avoid any problems with
// overlays, we don't introduce them here.
constexpr bool is_overlay_candidate = false;
constexpr uint32_t target = GL_TEXTURE_2D;
const auto& shared_image = current_color_buffer_->Export();
viz::TransferableResource resource = viz::TransferableResource::MakeGpu(
shared_image, target, sync_token, current_color_buffer_->size(),
viz::SinglePlaneFormat::kRGBA_8888, is_overlay_candidate,
viz::TransferableResource::ResourceSource::kPPBGraphics3D);
HostGlobals::Get()
->GetInstance(pp_instance())
->CommitTransferableResource(resource);
commit_pending_ = true;
if (!is_single_buffered_) {
inflight_color_buffers_.emplace(shared_image->mailbox(),
std::move(current_color_buffer_));
current_color_buffer_ = GetOrCreateColorBuffer();
}
} else {
// Wait for the command to complete on the GPU to allow for throttling.
command_buffer_->SignalSyncToken(
sync_token, base::BindOnce(&PPB_Graphics3D_Impl::OnSwapBuffers,
weak_ptr_factory_.GetWeakPtr()));
}
current_color_buffer_->Attach(command_buffer_.get(), samples_count_,
preserve_, needs_depth_, needs_stencil_);
return PP_OK_COMPLETIONPENDING;
}
void PPB_Graphics3D_Impl::ResolveAndDetachFramebuffer() {
DCHECK(current_color_buffer_);
current_color_buffer_->Detach(command_buffer_.get());
}
void PPB_Graphics3D_Impl::DoResize(gfx::Size size) {
if (swapchain_size_ == size)
return;
swapchain_size_ = size;
// Drop all available buffers as they are wrong size now;
available_color_buffers_.clear();
DCHECK(current_color_buffer_);
current_color_buffer_->Detach(command_buffer_.get());
current_color_buffer_ = GetOrCreateColorBuffer();
current_color_buffer_->Attach(command_buffer_.get(), samples_count_,
preserve_, needs_depth_, needs_stencil_);
}
std::unique_ptr<PPB_Graphics3D_Impl::ColorBuffer>
PPB_Graphics3D_Impl::GetOrCreateColorBuffer() {
if (!available_color_buffers_.empty()) {
auto result = std::move(*available_color_buffers_.begin());
available_color_buffers_.erase(available_color_buffers_.begin());
return result;
}
return std::make_unique<ColorBuffer>(shared_image_interface_.get(),
swapchain_size_, has_alpha_,
is_single_buffered_);
}
void PPB_Graphics3D_Impl::RecycleColorBuffer(
std::unique_ptr<ColorBuffer> buffer,
const gpu::SyncToken& sync_token,
bool is_lost) {
buffer->Recycle(sync_token);
if (is_lost || buffer->size() != swapchain_size_)
return;
available_color_buffers_.push_back(std::move(buffer));
}
} // namespace content