Add support for shared texture and external BeginFrame in OSR mode (issue #1006)

- Add CefWindowInfo::shared_texture_enabled and
  CefRenderHandler::OnAcceleratedPaint for shared texture support. Currently
  only supported on Windows (D3D11).
- Add CefWindowInfo::external_begin_frame_enabled and
  CefBrowserHost::SendExternalBeginFrame for external begin frame support.
This commit is contained in:
Greg Wessels
2018-07-02 19:46:03 -05:00
committed by Marshall Greenblatt
parent 09afa3a843
commit 713eebcafc
43 changed files with 2339 additions and 156 deletions

View File

@@ -45,6 +45,10 @@
namespace {
// The maximum number of damage rects to cache for outstanding frame requests
// (for OnAcceleratedPaint).
const size_t kMaxDamageRects = 10;
const float kDefaultScaleFactor = 1.0;
// The maximum number of retry counts if frame capture fails.
@@ -65,6 +69,45 @@ static content::ScreenInfo ScreenInfoFrom(const CefScreenInfo& src) {
return screenInfo;
}
class CefCompositorFrameSinkClient
: public viz::mojom::CompositorFrameSinkClient {
public:
CefCompositorFrameSinkClient(viz::mojom::CompositorFrameSinkClient* forward,
CefRenderWidgetHostViewOSR* rwhv)
: forward_(forward), render_widget_host_view_(rwhv) {}
void DidReceiveCompositorFrameAck(
const std::vector<viz::ReturnedResource>& resources) override {
forward_->DidReceiveCompositorFrameAck(resources);
}
void DidPresentCompositorFrame(
uint32_t presentation_token,
const gfx::PresentationFeedback& feedback) override {
forward_->DidPresentCompositorFrame(presentation_token, feedback);
if (render_widget_host_view_) {
render_widget_host_view_->OnPresentCompositorFrame(presentation_token);
}
}
void OnBeginFrame(const viz::BeginFrameArgs& args) override {
forward_->OnBeginFrame(args);
}
void OnBeginFramePausedChanged(bool paused) override {
forward_->OnBeginFramePausedChanged(paused);
}
void ReclaimResources(
const std::vector<viz::ReturnedResource>& resources) override {
forward_->ReclaimResources(resources);
}
private:
viz::mojom::CompositorFrameSinkClient* const forward_;
CefRenderWidgetHostViewOSR* const render_widget_host_view_;
};
} // namespace
// Used for managing copy requests when GPU compositing is enabled. Based on
@@ -204,6 +247,8 @@ class CefBeginFrameTimer : public viz::DelayBasedTimeSourceClient {
CefRenderWidgetHostViewOSR::CefRenderWidgetHostViewOSR(
SkColor background_color,
bool use_shared_texture,
bool use_external_begin_frame,
content::RenderWidgetHost* widget,
CefRenderWidgetHostViewOSR* parent_host_view,
bool is_guest_view_hack)
@@ -257,6 +302,8 @@ CefRenderWidgetHostViewOSR::CefRenderWidgetHostViewOSR(
GetRootLayer()->SetFillsBoundsOpaquely(opaque);
GetRootLayer()->SetColor(background_color_);
external_begin_frame_enabled_ = use_external_begin_frame;
#if !defined(OS_MACOSX)
// On macOS the ui::Compositor is created/owned by the platform view.
content::ImageTransportFactory* factory =
@@ -264,13 +311,22 @@ CefRenderWidgetHostViewOSR::CefRenderWidgetHostViewOSR(
ui::ContextFactoryPrivate* context_factory_private =
factory->GetContextFactoryPrivate();
// Matching the attributes from RecyclableCompositorMac.
compositor_.reset(
new ui::Compositor(context_factory_private->AllocateFrameSinkId(),
content::GetContextFactory(), context_factory_private,
base::ThreadTaskRunnerHandle::Get(),
features::IsSurfaceSynchronizationEnabled(),
false /* enable_pixel_canvas */));
compositor_.reset(new ui::Compositor(
context_factory_private->AllocateFrameSinkId(),
content::GetContextFactory(), context_factory_private,
base::ThreadTaskRunnerHandle::Get(),
features::IsSurfaceSynchronizationEnabled(),
false /* enable_pixel_canvas */, use_external_begin_frame));
compositor_->SetAcceleratedWidget(compositor_widget_);
// Tell the compositor to use shared textures if the client can handle
// OnAcceleratedPaint.
compositor_->EnableSharedTexture(use_shared_texture);
if (use_external_begin_frame) {
compositor_->SetExternalBeginFrameClient(this);
}
compositor_->SetDelegate(this);
compositor_->SetRootLayer(root_layer_.get());
#endif
@@ -289,6 +345,13 @@ CefRenderWidgetHostViewOSR::~CefRenderWidgetHostViewOSR() {
if (is_showing_)
browser_compositor_->SetRenderWidgetHostIsHidden(true);
#else
if (external_begin_frame_enabled_) {
ui::Compositor* compositor = GetCompositor();
if (compositor) {
compositor->SetExternalBeginFrameClient(nullptr);
}
}
// Marking the DelegatedFrameHost as removed from the window hierarchy is
// necessary to remove all connections to its old ui::Compositor.
if (is_showing_)
@@ -468,19 +531,85 @@ void CefRenderWidgetHostViewOSR::TakeFallbackContentFrom(
void CefRenderWidgetHostViewOSR::DidCreateNewRendererCompositorFrameSink(
viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink) {
renderer_compositor_frame_sink_ = renderer_compositor_frame_sink;
renderer_compositor_frame_sink_.reset(
new CefCompositorFrameSinkClient(renderer_compositor_frame_sink, this));
if (GetDelegatedFrameHost()) {
GetDelegatedFrameHost()->DidCreateNewRendererCompositorFrameSink(
renderer_compositor_frame_sink_);
renderer_compositor_frame_sink_.get());
}
}
void CefRenderWidgetHostViewOSR::OnPresentCompositorFrame(
uint32_t presentation_token) {
// Is Chromium rendering to a shared texture?
void* shared_texture = nullptr;
ui::Compositor* compositor = GetCompositor();
if (compositor) {
shared_texture = compositor->GetSharedTexture();
}
if (shared_texture) {
CefRefPtr<CefRenderHandler> handler =
browser_impl_->GetClient()->GetRenderHandler();
CHECK(handler);
CefRenderHandler::RectList rcList;
{
// Find the corresponding damage rect. If there isn't one pass the entire
// view size for a full redraw.
base::AutoLock lock_scope(damage_rect_lock_);
gfx::Rect damage;
auto const i = damage_rects_.find(presentation_token);
if (i != damage_rects_.end()) {
damage = i->second;
} else {
damage = GetViewBounds();
}
rcList.push_back(
CefRect(damage.x(), damage.y(), damage.width(), damage.height()));
}
handler->OnAcceleratedPaint(browser_impl_.get(),
IsPopupWidget() ? PET_POPUP : PET_VIEW, rcList,
shared_texture);
}
}
void CefRenderWidgetHostViewOSR::AddDamageRect(uint32_t presentation_token,
const gfx::Rect& rect) {
// Associate the given damage rect with the presentation token.
// For OnAcceleratedPaint we'll lookup the corresponding damage area based on
// the frame token which is passed back to OnPresentCompositorFrame.
base::AutoLock lock_scope(damage_rect_lock_);
// We assume our presentation_token is a counter. Since we're using an ordered
// map we can enforce a max size and remove oldest from the front. Worst case,
// if a damage rect isn't associated, we can simply pass the entire view size.
while (damage_rects_.size() >= kMaxDamageRects) {
damage_rects_.erase(damage_rects_.begin());
}
damage_rects_[presentation_token] = rect;
}
void CefRenderWidgetHostViewOSR::SubmitCompositorFrame(
const viz::LocalSurfaceId& local_surface_id,
viz::CompositorFrame frame,
base::Optional<viz::HitTestRegionList> hit_test_region_list) {
TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::OnSwapCompositorFrame");
// Update the frame rate. At this point we should have a valid connection back
// to the Synthetic Frame Source, which is important so we can actually modify
// the frame rate to something other than the default of 60Hz.
if (sync_frame_rate_) {
if (frame_rate_threshold_us_ != 0) {
GetCompositor()->SetAuthoritativeVSyncInterval(
base::TimeDelta::FromMicroseconds(frame_rate_threshold_us_));
}
sync_frame_rate_ = false;
}
if (frame.metadata.root_scroll_offset != last_scroll_offset_) {
last_scroll_offset_ = frame.metadata.root_scroll_offset;
@@ -510,31 +639,50 @@ void CefRenderWidgetHostViewOSR::SubmitCompositorFrame(
GetDelegatedFrameHost()->SubmitCompositorFrame(
local_surface_id, std::move(frame), std::move(hit_test_region_list));
} else {
if (!copy_frame_generator_.get()) {
copy_frame_generator_.reset(
new CefCopyFrameGenerator(frame_rate_threshold_us_, this));
}
ui::Compositor* compositor = GetCompositor();
if (!compositor)
return;
// Determine the damage rectangle for the current frame. This is the same
// calculation that SwapDelegatedFrame uses.
// Will be nullptr if we're not using shared textures.
const void* shared_texture = compositor->GetSharedTexture();
// Determine the damage rectangle for the current frame. This is the
// same calculation that SwapDelegatedFrame uses.
viz::RenderPass* root_pass = frame.render_pass_list.back().get();
gfx::Size frame_size = root_pass->output_rect.size();
gfx::Rect damage_rect =
gfx::ToEnclosingRect(gfx::RectF(root_pass->damage_rect));
damage_rect.Intersect(gfx::Rect(frame_size));
if (shared_texture) {
// Indicate that we want feedback every frame.
if (!++presentation_token_)
++presentation_token_;
AddDamageRect(presentation_token_, damage_rect);
frame.metadata.frame_token = presentation_token_;
frame.metadata.request_presentation_feedback = true;
}
// We would normally call BrowserCompositorMac::SubmitCompositorFrame on
// macOS, however it contains compositor resize logic that we don't want.
// Consequently we instead call the SwapDelegatedFrame method directly.
// macOS, however it contains compositor resize logic that we don't
// want. Consequently we instead call the SwapDelegatedFrame method
// directly.
GetDelegatedFrameHost()->SubmitCompositorFrame(
local_surface_id, std::move(frame), std::move(hit_test_region_list));
// Request a copy of the last compositor frame which will eventually call
// OnPaint asynchronously.
copy_frame_generator_->GenerateCopyFrame(damage_rect);
}
if (!shared_texture) {
if (!copy_frame_generator_.get()) {
copy_frame_generator_.reset(
new CefCopyFrameGenerator(frame_rate_threshold_us_, this));
}
return;
// Request a copy of the last compositor frame which will eventually
// call OnPaint asynchronously.
copy_frame_generator_->GenerateCopyFrame(damage_rect);
}
}
}
}
@@ -727,7 +875,8 @@ void CefRenderWidgetHostViewOSR::GetScreenInfo(
screen_info.available_rect.width == 0 ||
screen_info.available_rect.height == 0) {
// If a screen rectangle was not provided, try using the view rectangle
// instead. Otherwise, popup views may be drawn incorrectly, or not at all.
// instead. Otherwise, popup views may be drawn incorrectly, or not at
// all.
CefRect screenRect;
handler->GetViewRect(browser_impl_.get(), screenRect);
CHECK_GT(screenRect.width, 0);
@@ -879,8 +1028,11 @@ const viz::FrameSinkId& CefRenderWidgetHostViewOSR::GetFrameSinkId() const {
void CefRenderWidgetHostViewOSR::SetNeedsBeginFrames(bool enabled) {
SetFrameRate();
// Start/stop the timer that sends BeginFrame requests.
begin_frame_timer_->SetActive(enabled);
if (!external_begin_frame_enabled_) {
// Start/stop the timer that sends BeginFrame requests.
begin_frame_timer_->SetActive(enabled);
}
if (software_output_device_) {
// When the SoftwareOutputDevice is active it will call OnPaint for each
// frame. If the SoftwareOutputDevice is deactivated while an invalidation
@@ -938,6 +1090,16 @@ void CefRenderWidgetHostViewOSR::DidNavigate() {
#endif
}
void CefRenderWidgetHostViewOSR::OnDisplayDidFinishFrame(
const viz::BeginFrameAck& /*ack*/) {
// TODO(cef): is there something we need to track with this notification?
}
void CefRenderWidgetHostViewOSR::OnNeedsExternalBeginFrames(
bool needs_begin_frames) {
needs_external_begin_frames_ = needs_begin_frames;
}
std::unique_ptr<viz::SoftwareOutputDevice>
CefRenderWidgetHostViewOSR::CreateSoftwareOutputDevice(
ui::Compositor* compositor) {
@@ -1035,8 +1197,8 @@ void CefRenderWidgetHostViewOSR::OnScreenInfoChanged() {
#endif
// We might want to change the cursor scale factor here as well - see the
// cache for the current_cursor_, as passed by UpdateCursor from the renderer
// in the rwhv_aura (current_cursor_.SetScaleFactor)
// cache for the current_cursor_, as passed by UpdateCursor from the
// renderer in the rwhv_aura (current_cursor_.SetScaleFactor)
// Notify the guest hosts if any.
for (auto guest_host_view : guest_host_views_)
@@ -1056,6 +1218,31 @@ void CefRenderWidgetHostViewOSR::Invalidate(
InvalidateInternal(gfx::Rect(GetCompositorViewportPixelSize()));
}
void CefRenderWidgetHostViewOSR::SendExternalBeginFrame() {
DCHECK(external_begin_frame_enabled_);
base::TimeTicks frame_time = base::TimeTicks::Now();
base::TimeTicks deadline = base::TimeTicks();
base::TimeDelta interval = viz::BeginFrameArgs::DefaultInterval();
viz::BeginFrameArgs begin_frame_args = viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, begin_frame_source_.source_id(),
begin_frame_number_, frame_time, deadline, interval,
viz::BeginFrameArgs::NORMAL);
DCHECK(begin_frame_args.IsValid());
begin_frame_number_++;
if (renderer_compositor_frame_sink_) {
GetCompositor()->IssueExternalBeginFrame(begin_frame_args);
renderer_compositor_frame_sink_->OnBeginFrame(begin_frame_args);
}
if (!IsPopupWidget() && popup_host_view_) {
popup_host_view_->SendExternalBeginFrame();
}
}
void CefRenderWidgetHostViewOSR::SendKeyEvent(
const content::NativeWebKeyboardEvent& event) {
TRACE_EVENT0("libcef", "CefRenderWidgetHostViewOSR::SendKeyEvent");
@@ -1155,8 +1342,8 @@ void CefRenderWidgetHostViewOSR::SendMouseWheelEvent(
return;
} else {
// Scrolling outside of the popup widget so destroy it.
// Execute asynchronously to avoid deleting the widget from inside some
// other callback.
// Execute asynchronously to avoid deleting the widget from inside
// some other callback.
CEF_POST_TASK(
CEF_UIT,
base::Bind(&CefRenderWidgetHostViewOSR::CancelWidget,
@@ -1298,13 +1485,26 @@ void CefRenderWidgetHostViewOSR::SetFrameRate() {
if (frame_rate_threshold_us_ != 0)
return;
const int frame_rate =
osr_util::ClampFrameRate(browser->settings().windowless_frame_rate);
ui::Compositor* compositor = GetCompositor();
int frame_rate;
if (compositor && compositor->shared_texture_enabled()) {
// No upper-bound when using OnAcceleratedPaint.
frame_rate = browser->settings().windowless_frame_rate;
if (frame_rate <= 0) {
frame_rate = 1;
}
sync_frame_rate_ = true;
} else {
frame_rate =
osr_util::ClampFrameRate(browser->settings().windowless_frame_rate);
}
frame_rate_threshold_us_ = 1000000 / frame_rate;
if (GetCompositor()) {
if (compositor) {
// Configure the VSync interval for the browser process.
GetCompositor()->vsync_manager()->SetAuthoritativeVSyncInterval(
compositor->vsync_manager()->SetAuthoritativeVSyncInterval(
base::TimeDelta::FromMicroseconds(frame_rate_threshold_us_));
}
@@ -1313,13 +1513,15 @@ void CefRenderWidgetHostViewOSR::SetFrameRate() {
frame_rate_threshold_us_);
}
if (begin_frame_timer_.get()) {
begin_frame_timer_->SetFrameRateThresholdUs(frame_rate_threshold_us_);
} else {
begin_frame_timer_.reset(new CefBeginFrameTimer(
frame_rate_threshold_us_,
base::Bind(&CefRenderWidgetHostViewOSR::OnBeginFrameTimerTick,
weak_ptr_factory_.GetWeakPtr())));
if (!external_begin_frame_enabled_) {
if (begin_frame_timer_.get()) {
begin_frame_timer_->SetFrameRateThresholdUs(frame_rate_threshold_us_);
} else {
begin_frame_timer_.reset(new CefBeginFrameTimer(
frame_rate_threshold_us_,
base::Bind(&CefRenderWidgetHostViewOSR::OnBeginFrameTimerTick,
weak_ptr_factory_.GetWeakPtr())));
}
}
}
@@ -1459,8 +1661,8 @@ void CefRenderWidgetHostViewOSR::CancelWidget() {
if (render_widget_host_ && !is_destroyed_) {
is_destroyed_ = true;
// Don't delete the RWHI manually while owned by a scoped_ptr in RVHI. This
// matches a CHECK() in RenderWidgetHostImpl::Destroy().
// Don't delete the RWHI manually while owned by a scoped_ptr in RVHI.
// This matches a CHECK() in RenderWidgetHostImpl::Destroy().
const bool also_delete = !render_widget_host_->owner_delegate();
// Results in a call to Destroy().