#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/metrics/histogram_macros.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/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 {
namespace {
bool UseSharedImagesSwapChainForPPAPI() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisablePPAPISharedImagesSwapChain)) {
LOG(WARNING) << "NaCL SwapChain: Disabled by policy";
return false;
}
auto enabled =
base::FeatureList::IsEnabled(features::kPPAPISharedImagesSwapChain);
LOG(WARNING) << "NaCL SwapChain: Feature Controled: " << enabled;
return enabled;
}
}
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) {
uint32_t usage =
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_GLES2;
if (is_single_buffered_)
usage |= gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE;
auto shared_image_size = size.IsEmpty() ? gfx::Size(1, 1) : size;
mailbox_ = 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());
sync_token_ = sii_->GenVerifiedSyncToken();
}
~ColorBuffer() {
DCHECK_NE(state, State::kAttached);
sii_->DestroySharedImage(destruction_sync_token_, mailbox_);
}
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(
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;
}
gpu::Mailbox Export() {
DCHECK_EQ(state, State::kDetached);
if (!is_single_buffered_)
state = State::kInCompositor;
return mailbox_;
}
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;
sync_token_ = sync_token;
destruction_sync_token_ = sync_token;
}
const gfx::Size& size() { return size_; }
bool IsAttached() { return state == State::kAttached; }
bool IsSame(const gpu::Mailbox& mailbox) { return mailbox == mailbox_; }
private:
enum class State { kDetached, kAttached, kInCompositor };
State state = State::kDetached;
gpu::SharedImageInterface* const sii_;
const gfx::Size size_;
gpu::Mailbox mailbox_;
gpu::SyncToken sync_token_;
gpu::SyncToken destruction_sync_token_;
const bool is_single_buffered_;
};
PPB_Graphics3D_Impl::PPB_Graphics3D_Impl(PP_Instance instance)
: PPB_Graphics3D_Shared(instance,
UseSharedImagesSwapChainForPPAPI()),
bound_to_instance_(false),
commit_pending_(false),
has_alpha_(false),
use_image_chromium_(
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisablePepper3DImageChromium) &&
base::FeatureList::IsEnabled(features::kPepper3DImageChromium)) {}
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();
if (command_buffer_)
command_buffer_->SetGpuControlClient(nullptr);
}
PP_Resource PPB_Graphics3D_Impl::CreateRaw(
PP_Instance instance,
PP_Resource share_context,
const gpu::ContextCreationAttribs& attrib_helper,
gpu::Capabilities* 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, attrib_helper, 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::TakeFrontBuffer() {
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) {
if (use_shared_images_swapchain_) {
if (is_single_buffered_) {
} else {
auto it = inflight_color_buffers_.find(mailbox);
DCHECK(it != inflight_color_buffers_.end());
RecycleColorBuffer(std::move(it->second), sync_token, is_lost);
inflight_color_buffers_.erase(it);
}
} else {
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 (use_shared_images_swapchain_)
return DoPresent(sync_token, size);
if (taken_front_buffer_.IsZero()) {
DLOG(ERROR) << "TakeFrontBuffer should be called before DoSwapBuffers";
return PP_ERROR_FAILED;
}
if (bound_to_instance_) {
bool is_overlay_candidate = use_image_chromium_;
uint32_t target = GL_TEXTURE_2D;
#if BUILDFLAG(IS_MAC)
if (use_image_chromium_)
target = GL_TEXTURE_RECTANGLE_ARB;
#endif
viz::TransferableResource resource = viz::TransferableResource::MakeGpu(
taken_front_buffer_, target, sync_token, size,
viz::SinglePlaneFormat::kRGBA_8888, is_overlay_candidate);
HostGlobals::Get()
->GetInstance(pp_instance())
->CommitTransferableResource(resource);
commit_pending_ = true;
} else {
command_buffer_->SignalSyncToken(
sync_token, base::BindOnce(&PPB_Graphics3D_Impl::OnSwapBuffers,
weak_ptr_factory_.GetWeakPtr()));
}
return PP_OK_COMPLETIONPENDING;
}
bool PPB_Graphics3D_Impl::InitRaw(
PPB_Graphics3D_API* share_context,
const gpu::ContextCreationAttribs& requested_attribs,
gpu::Capabilities* 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();
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;
if (channel->gpu_feature_info()
.status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_WEBGL] ==
gpu::kGpuFeatureStatusBlocklisted) {
return false;
}
has_alpha_ = requested_attribs.alpha_size > 0;
if (use_shared_images_swapchain_) {
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;
}
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 = requested_attribs;
attrib_helper.should_use_native_gmb_for_backbuffer = use_image_chromium_;
if (use_shared_images_swapchain_) {
attrib_helper = gpu::ContextCreationAttribs();
}
attrib_helper.context_type = gpu::CONTEXT_TYPE_OPENGLES2;
gpu::CommandBufferProxyImpl* share_buffer = nullptr;
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();
}
if (use_shared_images_swapchain_)
shared_image_interface_ = channel->CreateClientSharedImageInterface();
command_buffer_ = std::make_unique<gpu::CommandBufferProxyImpl>(
std::move(channel), render_thread->GetGpuMemoryBufferManager(),
kGpuStreamIdDefault, base::SingleThreadTaskRunner::GetCurrentDefault());
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_region)
*shared_state_region = &command_buffer_->GetSharedStateRegion();
if (capabilities) {
*capabilities = command_buffer_->GetCapabilities();
capabilities->use_shared_images_swapchain_for_ppapi =
use_shared_images_swapchain_;
}
if (command_buffer_id)
*command_buffer_id = command_buffer_->GetCommandBufferID();
if (use_shared_images_swapchain_) {
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()
DCHECK(!lost_context_);
lost_context_ = true;
#endif
if (bound_to_instance_) {
HostGlobals::Get()->GetInstance(pp_instance())->BindGraphics(pp_instance(),
0);
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PPB_Graphics3D_Impl::SendContextLost,
weak_ptr_factory_.GetWeakPtr()));
}
void PPB_Graphics3D_Impl::OnGpuControlLostContextMaybeReentrant() {
}
void PPB_Graphics3D_Impl::OnGpuControlReturnData(
base::span<const uint8_t> data) {
NOTIMPLEMENTED();
}
void PPB_Graphics3D_Impl::OnSwapBuffers() {
if (HasPendingSwap()) {
commit_pending_ = false;
SwapBuffersACK(PP_OK);
}
}
void PPB_Graphics3D_Impl::SendContextLost() {
PepperPluginInstanceImpl* instance =
HostGlobals::Get()->GetInstance(pp_instance());
if (!instance || !instance->container())
return;
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));
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::GenerateLegacyMailbox();
}
int32_t PPB_Graphics3D_Impl::DoPresent(const gpu::SyncToken& sync_token,
const gfx::Size& size) {
DCHECK(command_buffer_);
DCHECK(use_shared_images_swapchain_);
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_) {
constexpr bool is_overlay_candidate = false;
constexpr uint32_t target = GL_TEXTURE_2D;
auto mailbox = current_color_buffer_->Export();
viz::TransferableResource resource = viz::TransferableResource::MakeGpu(
mailbox, target, sync_token, current_color_buffer_->size(),
viz::SinglePlaneFormat::kRGBA_8888, is_overlay_candidate);
HostGlobals::Get()
->GetInstance(pp_instance())
->CommitTransferableResource(resource);
commit_pending_ = true;
if (!is_single_buffered_) {
inflight_color_buffers_.emplace(mailbox,
std::move(current_color_buffer_));
current_color_buffer_ = GetOrCreateColorBuffer();
}
} else {
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(use_shared_images_swapchain_);
DCHECK(current_color_buffer_);
current_color_buffer_->Detach(command_buffer_.get());
}
void PPB_Graphics3D_Impl::DoResize(gfx::Size size) {
DCHECK(use_shared_images_swapchain_);
if (swapchain_size_ == size)
return;
swapchain_size_ = size;
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() {
DCHECK(use_shared_images_swapchain_);
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) {
DCHECK(use_shared_images_swapchain_);
buffer->Recycle(sync_token);
if (is_lost || buffer->size() != swapchain_size_)
return;
available_color_buffers_.push_back(std::move(buffer));
}
}