0

[ios blink] Simplify external begin frames

Fork ExternalBeginFrameSourceMojo to an iOS specific variant so that we
don't have to unnecessarily wait for previous frame's BeginFrameAck.
This uses a new IssueExternalBeginFrameNoAck mojo API.

With this new variant, Motionmark is able to see 60fps in its frame rate
detection phase.

Change-Id: I4a415f2388a3855db963ca8c6d8f825e20fff36b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6398086
Reviewed-by: Alex Gough <ajgo@chromium.org>
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Commit-Queue: Jonathan Ross <jonross@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1439376}
This commit is contained in:
Sunny Sachanandani
2025-03-28 07:06:45 -07:00
committed by Chromium LUCI CQ
parent e570133c2f
commit 6151353b2d
9 changed files with 196 additions and 51 deletions

@ -188,8 +188,6 @@ viz_component("service") {
"frame_sinks/compositor_frame_sink_impl.h",
"frame_sinks/compositor_frame_sink_support.cc",
"frame_sinks/compositor_frame_sink_support.h",
"frame_sinks/external_begin_frame_source_mojo.cc",
"frame_sinks/external_begin_frame_source_mojo.h",
"frame_sinks/frame_counter.cc",
"frame_sinks/frame_counter.h",
"frame_sinks/frame_sink_bundle_impl.cc",
@ -386,6 +384,18 @@ viz_component("service") {
}
}
if (is_ios) {
sources += [
"frame_sinks/external_begin_frame_source_mojo_ios.cc",
"frame_sinks/external_begin_frame_source_mojo_ios.h",
]
} else {
sources += [
"frame_sinks/external_begin_frame_source_mojo.cc",
"frame_sinks/external_begin_frame_source_mojo.h",
]
}
if (is_android || use_ozone) {
sources += [
"display/overlay_combination_cache.cc",

@ -0,0 +1,42 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/frame_sinks/external_begin_frame_source_mojo_ios.h"
#include <utility>
namespace viz {
ExternalBeginFrameSourceMojoIOS::ExternalBeginFrameSourceMojoIOS(
mojo::PendingAssociatedReceiver<mojom::ExternalBeginFrameController>
controller_receiver,
mojo::PendingAssociatedRemote<mojom::ExternalBeginFrameControllerClient>
controller_client_remote,
uint32_t restart_id)
: ExternalBeginFrameSource(this, restart_id),
receiver_(this, std::move(controller_receiver)),
remote_client_(std::move(controller_client_remote)) {}
ExternalBeginFrameSourceMojoIOS::~ExternalBeginFrameSourceMojoIOS() = default;
void ExternalBeginFrameSourceMojoIOS::IssueExternalBeginFrameNoAck(
const BeginFrameArgs& args) {
OnBeginFrame(args);
}
void ExternalBeginFrameSourceMojoIOS::OnNeedsBeginFrames(
bool needs_begin_frames) {
if (remote_client_) {
remote_client_->SetNeedsBeginFrame(needs_begin_frames);
}
}
void ExternalBeginFrameSourceMojoIOS::SetPreferredInterval(
base::TimeDelta interval) {
if (remote_client_) {
remote_client_->SetPreferredInterval(interval);
}
}
} // namespace viz

@ -0,0 +1,53 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_VIZ_SERVICE_FRAME_SINKS_EXTERNAL_BEGIN_FRAME_SOURCE_MOJO_IOS_H_
#define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_EXTERNAL_BEGIN_FRAME_SOURCE_MOJO_IOS_H_
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/service/viz_service_export.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom.h"
namespace viz {
// Implementation of ExternalBeginFrameSource that's controlled by IPCs over the
// mojom::ExternalBeginFrameController interface from UI/browser process on iOS.
class VIZ_SERVICE_EXPORT ExternalBeginFrameSourceMojoIOS
: public mojom::ExternalBeginFrameController,
public ExternalBeginFrameSource,
public ExternalBeginFrameSourceClient {
public:
// `controller_receiver` must be a valid mojo receiver.
// `controller_client_remote` is optional and can be an invalid remote.
ExternalBeginFrameSourceMojoIOS(
mojo::PendingAssociatedReceiver<mojom::ExternalBeginFrameController>
controller_receiver,
mojo::PendingAssociatedRemote<mojom::ExternalBeginFrameControllerClient>
controller_client_remote,
uint32_t restart_id);
~ExternalBeginFrameSourceMojoIOS() override;
// mojom::ExternalBeginFrameController implementation.
void IssueExternalBeginFrameNoAck(const BeginFrameArgs& args) override;
private:
// ExternalBeginFrameSourceClient implementation.
void OnNeedsBeginFrames(bool needs_begin_frames) override;
void SetPreferredInterval(base::TimeDelta interval) override;
mojo::AssociatedReceiver<mojom::ExternalBeginFrameController> receiver_;
mojo::AssociatedRemote<mojom::ExternalBeginFrameControllerClient>
remote_client_;
};
} // namespace viz
#endif // COMPONENTS_VIZ_SERVICE_FRAME_SINKS_EXTERNAL_BEGIN_FRAME_SOURCE_MOJO_IOS_H_

@ -26,7 +26,6 @@
#include "components/viz/service/display/output_surface.h"
#include "components/viz/service/display_embedder/output_surface_provider.h"
#include "components/viz/service/display_embedder/vsync_parameter_listener.h"
#include "components/viz/service/frame_sinks/external_begin_frame_source_mojo.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/hit_test/hit_test_aggregator.h"
#include "services/viz/public/mojom/compositing/layer_context.mojom.h"
@ -39,6 +38,9 @@
#if BUILDFLAG(IS_IOS)
#include "components/viz/common/frame_sinks/external_begin_frame_source_ios.h"
#include "components/viz/service/frame_sinks/external_begin_frame_source_mojo_ios.h"
#else
#include "components/viz/service/frame_sinks/external_begin_frame_source_mojo.h"
#endif
#if BUILDFLAG(IS_MAC)
@ -134,7 +136,9 @@ RootCompositorFrameSinkImpl::Create(
// |params|.
std::unique_ptr<ExternalBeginFrameSource> external_begin_frame_source;
std::unique_ptr<SyntheticBeginFrameSource> synthetic_begin_frame_source;
#if !BUILDFLAG(IS_IOS)
ExternalBeginFrameSourceMojo* external_begin_frame_source_mojo = nullptr;
#endif
bool hw_support_for_multiple_refresh_rates = false;
#if BUILDFLAG(IS_MAC)
bool created_external_begin_frame_source_mac = false;
@ -144,18 +148,23 @@ RootCompositorFrameSinkImpl::Create(
#endif
if (params->external_begin_frame_controller) {
auto owned_external_begin_frame_source_mojo =
#if BUILDFLAG(IS_IOS)
hw_support_for_multiple_refresh_rates = true;
external_begin_frame_source =
std::make_unique<ExternalBeginFrameSourceMojoIOS>(
std::move(params->external_begin_frame_controller),
std::move(params->external_begin_frame_controller_client),
restart_id);
#else
external_begin_frame_source =
std::make_unique<ExternalBeginFrameSourceMojo>(
frame_sink_manager,
std::move(params->external_begin_frame_controller),
std::move(params->external_begin_frame_controller_client),
restart_id);
external_begin_frame_source_mojo =
owned_external_begin_frame_source_mojo.get();
external_begin_frame_source =
std::move(owned_external_begin_frame_source_mojo);
#if BUILDFLAG(IS_IOS)
hw_support_for_multiple_refresh_rates = true;
static_cast<ExternalBeginFrameSourceMojo*>(
external_begin_frame_source.get());
#endif
} else {
#if BUILDFLAG(IS_ANDROID)
@ -238,8 +247,11 @@ RootCompositorFrameSinkImpl::Create(
std::move(output_surface), std::move(overlay_processor),
std::move(scheduler), std::move(task_runner));
if (external_begin_frame_source_mojo)
#if !BUILDFLAG(IS_IOS)
if (external_begin_frame_source_mojo) {
external_begin_frame_source_mojo->SetDisplay(display.get());
}
#endif
// base::WrapUnique instead of std::make_unique because the ctor is private.
auto impl = base::WrapUnique(new RootCompositorFrameSinkImpl(

@ -39,13 +39,13 @@ class BeginFrameSourceIOS
void SetPreferredInterval(base::TimeDelta interval) override;
private:
void BeginFrameAck(const viz::BeginFrameAck&);
const raw_ptr<ui::Compositor> compositor_;
raw_ptr<ui::Compositor> compositor_;
viz::ExternalBeginFrameSourceIOS begin_frame_source_;
viz::BeginFrameArgs last_used_begin_frame_args_;
bool send_begin_frame_ = true;
bool added_observer_ = false;
bool observing_begin_frame_source_ = false;
mojo::AssociatedReceiverSet<viz::mojom::ExternalBeginFrameControllerClient>
receivers_;

@ -4,6 +4,8 @@
#include "content/browser/renderer_host/begin_frame_source_ios.h"
#include "base/functional/callback_helpers.h"
namespace content {
////////////////////////////////////////////////////////////////////////////////
@ -12,6 +14,7 @@ namespace content {
BeginFrameSourceIOS::BeginFrameSourceIOS(ui::Compositor* compositor)
: compositor_(compositor),
begin_frame_source_(viz::BackToBackBeginFrameSource::kNotRestartableId) {
DCHECK(compositor_);
compositor_->SetExternalBeginFrameControllerClientFactory(this);
}
@ -20,19 +23,8 @@ BeginFrameSourceIOS::~BeginFrameSourceIOS() {
}
void BeginFrameSourceIOS::OnBeginFrame(const viz::BeginFrameArgs& args) {
if (!compositor_ || !send_begin_frame_) {
return;
}
last_used_begin_frame_args_ = args;
send_begin_frame_ = false;
compositor_->IssueExternalBeginFrame(
args, /*force=*/true,
base::BindOnce(&BeginFrameSourceIOS::BeginFrameAck,
weak_factory_.GetWeakPtr()));
}
void BeginFrameSourceIOS::BeginFrameAck(const viz::BeginFrameAck&) {
send_begin_frame_ = true;
compositor_->IssueExternalBeginFrameNoAck(args);
}
const viz::BeginFrameArgs& BeginFrameSourceIOS::LastUsedBeginFrameArgs() const {
@ -58,17 +50,13 @@ BeginFrameSourceIOS::CreateExternalBeginFrameControllerClient() {
}
void BeginFrameSourceIOS::SetNeedsBeginFrame(bool needs_begin_frames) {
if (needs_begin_frames == observing_begin_frame_source_) {
return;
}
observing_begin_frame_source_ = needs_begin_frames;
if (needs_begin_frames) {
if (added_observer_) {
return;
}
added_observer_ = true;
begin_frame_source_.AddObserver(this);
} else {
if (!added_observer_) {
return;
}
added_observer_ = false;
begin_frame_source_.RemoveObserver(this);
}
}

@ -14,8 +14,16 @@ interface ExternalBeginFrameController {
// If force is set to true, the frame will either begin or fail immediately,
// otherwise the frame will be deferred till one of the frame sinks indicates
// it's interested in a frame.
// This is only used for headless on desktop and Android XR compositor.
[EnableIfNot=is_ios]
IssueExternalBeginFrame(BeginFrameArgs args, bool force)
=> (BeginFrameAck ack);
// Issue a begin frame to the compositor. This is only used on iOS where begin
// frames originate in the browser process due to sandbox restrictions and we
// don't care about BeginFrameAcks for frame throttling unlike headless.
[EnableIf=is_ios]
IssueExternalBeginFrameNoAck(BeginFrameArgs args);
};
// Exposes a way to control when external BeginFrames are issued.
@ -25,4 +33,4 @@ interface ExternalBeginFrameControllerClient {
// Update the preferred interval.
SetPreferredInterval(mojo_base.mojom.TimeDelta interval);
};
};

@ -75,19 +75,15 @@
namespace ui {
// Used to hold on to IssueExternalBeginFrame arguments if
// |external_begin_frame_controller_| isn't ready yet.
struct PendingBeginFrameArgs {
PendingBeginFrameArgs(
const viz::BeginFrameArgs& args,
bool force,
base::OnceCallback<void(const viz::BeginFrameAck&)> callback)
: args(args), force(force), callback(std::move(callback)) {}
#if !BUILDFLAG(IS_IOS)
Compositor::PendingBeginFrameArgs::PendingBeginFrameArgs(
const viz::BeginFrameArgs& args,
bool force,
base::OnceCallback<void(const viz::BeginFrameAck&)> callback)
: args(args), force(force), callback(std::move(callback)) {}
viz::BeginFrameArgs args;
bool force;
base::OnceCallback<void(const viz::BeginFrameAck&)> callback;
};
Compositor::PendingBeginFrameArgs::~PendingBeginFrameArgs() = default;
#endif
Compositor::Compositor(const viz::FrameSinkId& frame_sink_id,
ui::ContextFactory* context_factory,
@ -388,9 +384,14 @@ void Compositor::SetExternalBeginFrameController(
DCHECK(use_external_begin_frame_control());
external_begin_frame_controller_ = std::move(external_begin_frame_controller);
if (pending_begin_frame_args_) {
#if BUILDFLAG(IS_IOS)
external_begin_frame_controller_->IssueExternalBeginFrameNoAck(
*pending_begin_frame_args_);
#else
external_begin_frame_controller_->IssueExternalBeginFrame(
pending_begin_frame_args_->args, pending_begin_frame_args_->force,
std::move(pending_begin_frame_args_->callback));
#endif
pending_begin_frame_args_.reset();
}
}
@ -726,6 +727,17 @@ bool Compositor::HasAnimationObserver(
return animation_observer_list_.HasObserver(observer);
}
#if BUILDFLAG(IS_IOS)
void Compositor::IssueExternalBeginFrameNoAck(const viz::BeginFrameArgs& args) {
if (!external_begin_frame_controller_) {
// It's ok to call this repeatedly until |external_begin_frame_controller_|
// is ready - we'll just update the |pending_begin_frame_args_|.
pending_begin_frame_args_.emplace(args);
return;
}
external_begin_frame_controller_->IssueExternalBeginFrameNoAck(args);
}
#else
void Compositor::IssueExternalBeginFrame(
const viz::BeginFrameArgs& args,
bool force,
@ -734,13 +746,13 @@ void Compositor::IssueExternalBeginFrame(
// IssueExternalBeginFrame() shouldn't be called again before the previous
// begin frame is acknowledged.
DCHECK(!pending_begin_frame_args_);
pending_begin_frame_args_ = std::make_unique<PendingBeginFrameArgs>(
args, force, std::move(callback));
pending_begin_frame_args_.emplace(args, force, std::move(callback));
return;
}
external_begin_frame_controller_->IssueExternalBeginFrame(
args, force, std::move(callback));
}
#endif
CompositorMetricsTracker Compositor::RequestNewCompositorMetricsTracker() {
return CompositorMetricsTracker(next_compositor_metrics_tracker_id_++,

@ -100,7 +100,6 @@ class ScopedAnimationDurationScaleMode;
class ScrollInputHandler;
class CompositorMetricsTracker;
class CompositorPropertyTreeDelegate;
struct PendingBeginFrameArgs;
constexpr int kCompositorLockTimeoutMs = 67;
@ -376,10 +375,14 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,
void RequestSuccessfulPresentationTimeForNextFrame(
SuccessfulPresentationTimeCallback callback);
#if BUILDFLAG(IS_IOS)
void IssueExternalBeginFrameNoAck(const viz::BeginFrameArgs& args);
#else
void IssueExternalBeginFrame(
const viz::BeginFrameArgs& args,
bool force,
base::OnceCallback<void(const viz::BeginFrameAck&)> callback);
#endif
// Creates a CompositorMetricsTracker for tracking this Compositor.
CompositorMetricsTracker RequestNewCompositorMetricsTracker();
@ -603,7 +606,24 @@ class COMPOSITOR_EXPORT Compositor : public base::PowerSuspendObserver,
raw_ptr<ExternalBeginFrameControllerClientFactory>
external_begin_frame_controler_client_factory_;
std::unique_ptr<PendingBeginFrameArgs> pending_begin_frame_args_;
// Used to hold on to IssueExternalBeginFrame(NoAck) arguments if
// |external_begin_frame_controller_| isn't ready yet.
#if BUILDFLAG(IS_IOS)
using PendingBeginFrameArgs = viz::BeginFrameArgs;
#else
struct PendingBeginFrameArgs {
PendingBeginFrameArgs(
const viz::BeginFrameArgs& args,
bool force,
base::OnceCallback<void(const viz::BeginFrameAck&)> callback);
~PendingBeginFrameArgs();
const viz::BeginFrameArgs args;
const bool force;
base::OnceCallback<void(const viz::BeginFrameAck&)> callback;
};
#endif
std::optional<PendingBeginFrameArgs> pending_begin_frame_args_;
ui::HostBeginFrameObserver::SimpleBeginFrameObserverList
simple_begin_frame_observers_;