Files
cromite/build/patches/Set-the-screen-frame-rate-to-60-Hz.patch

417 lines
20 KiB
Diff

From: uazo <uazo@users.noreply.github.com>
Date: Wed, 10 Sep 2025 07:44:05 +0000
Subject: Set the screen frame rate to 60 Hz
Set the screen refresh rate to 60Hz by default, increasing it if necessary
in cases where the system only supports lower frequencies (such as in RDP
or virtual systems).
The feature can be disabled using the throttle-main-thread-to-60hz flag (enabled by default).
License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
---
cc/base/features.cc | 9 ++-------
cc/scheduler/scheduler_state_machine.cc | 19 ++++++++++++++++++-
.../java/res/xml/privacy_preferences.xml | 6 ++++++
chrome/browser/about_flags.cc | 6 +++---
.../browser/chrome_content_browser_client.cc | 11 +++++++++++
.../Set-the-screen-frame-rate-to-60-Hz.grdp | 9 +++++++++
.../common/frame_sinks/begin_frame_source.cc | 14 +++++++++++++-
.../common/frame_sinks/begin_frame_source.h | 1 +
.../frame_sinks/delay_based_time_source.cc | 2 +-
components/viz/service/display/display.cc | 18 ++++++++++++++++--
.../external_begin_frame_source_android.cc | 13 +++++++++++++
.../external_begin_frame_source_android.h | 2 ++
.../root_compositor_frame_sink_impl.cc | 10 ++++++++--
.../Set-the-screen-frame-rate-to-60-Hz.inc | 7 +++++++
.../core/animation/animation_clock.cc | 11 ++++++++---
.../core/view_transition/view_transition.cc | 2 ++
16 files changed, 120 insertions(+), 20 deletions(-)
create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Set-the-screen-frame-rate-to-60-Hz.grdp
create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Set-the-screen-frame-rate-to-60-Hz.inc
diff --git a/cc/base/features.cc b/cc/base/features.cc
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -131,13 +131,7 @@ BASE_FEATURE(kPreventDuplicateImageDecodes, base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kInitImageDecodeLastUseTime, base::FEATURE_ENABLED_BY_DEFAULT);
// Enabled on Android, after a field trial showed improvements.
-BASE_FEATURE(kThrottleMainFrameTo60Hz,
-#if BUILDFLAG(IS_ANDROID)
- base::FEATURE_ENABLED_BY_DEFAULT
-#else
- base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-);
+CROMITE_FEATURE(kThrottleMainFrameTo60Hz, "ThrottleMainFrameTo60Hz", base::FEATURE_ENABLED_BY_DEFAULT);
#if BUILDFLAG(IS_ANDROID)
BASE_FEATURE(kThrottleMainFrameTo60HzWebView,
@@ -170,6 +164,7 @@ BASE_FEATURE(kViewTransitionFloorTransform, base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kRenderThrottleFrameRate, base::FEATURE_ENABLED_BY_DEFAULT);
const base::FeatureParam<int> kRenderThrottledFrameIntervalHz{
&kRenderThrottleFrameRate, "render-throttled-frame-interval-hz", 30};
+SET_CROMITE_FEATURE_DISABLED(kRenderThrottleFrameRate);
BASE_FEATURE(kInternalBeginFrameSourceOnManyDidNotProduceFrame,
base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -35,7 +35,7 @@ const int kMaxPendingSubmitFrames = 1;
bool IsEligibleToThrottleMainFrameRate() {
#if BUILDFLAG(IS_ANDROID)
// Still requires balancing tradeoffs for desktop Android, not enabled yet.
- return !base::android::device_info::is_desktop();
+ return true;
#else
return true;
#endif
@@ -1432,6 +1432,15 @@ void SchedulerStateMachine::FrameIntervalUpdated(
//
// Apply some slack, so that if for some reason the interval is a bit larger
// than 8.33333333333333ms, then we catch it still.
+#if !BUILDFLAG(IS_ANDROID)
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ features::SetIsEligibleForThrottleMainFrameTo60Hz(true);
+ main_frame_throttled_interval_ = base::Hertz(60);
+ } else {
+ main_frame_throttled_interval_ = base::TimeDelta();
+ }
+ if ((true)) return;
+#endif
constexpr float kSlackFactor = .9;
bool fast_vsync_interval =
frame_interval < base::Hertz(120) * (1 / kSlackFactor);
@@ -1758,6 +1767,14 @@ void SchedulerStateMachine::SetRequestHighFramerate(bool flag) {
}
base::TimeDelta SchedulerStateMachine::MainFrameThrottledInterval() const {
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+#if BUILDFLAG(IS_ANDROID)
+ return base::TimeDelta();
+#else
+ return main_frame_throttled_interval_;
+#endif
+ }
+ if ((true)) return base::TimeDelta();
if (!throttle_frame_rate_) {
if (high_framerate_requests_count_ &&
base::FeatureList::IsEnabled(
diff --git a/chrome/android/java/res/xml/privacy_preferences.xml b/chrome/android/java/res/xml/privacy_preferences.xml
--- a/chrome/android/java/res/xml/privacy_preferences.xml
+++ b/chrome/android/java/res/xml/privacy_preferences.xml
@@ -92,6 +92,12 @@ found in the LICENSE file.
android:summary="@string/tab_switcher_list_mode_summary"
app:featureName="TabSwitcherListMode"
app:needRestart="true" />
+ <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+ android:key="throttle_main_thread_to_60hz"
+ android:title="@string/throttle_main_thread_to_60hz_title"
+ android:summary="@string/throttle_main_thread_to_60hz_summary"
+ app:featureName="throttle-main-thread-to-60hz"
+ app:needRestart="true" />
<PreferenceCategory
android:key="security_section"
android:title="@string/security_section_title" />
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -10896,9 +10896,9 @@ const FeatureEntry kFeatureEntries[] = {
FEATURE_VALUE_TYPE(::features::kBlockRootWindowAccessibleNameChangeEvent)},
#endif // BUILDFLAG(IS_MAC)
- {"throttle-main-thread-to-60hz", flag_descriptions::kThrottleMainTo60HzName,
- flag_descriptions::kThrottleMainTo60HzDescription, kOsAll,
- FEATURE_VALUE_TYPE(features::kThrottleMainFrameTo60Hz)},
+ // {"throttle-main-thread-to-60hz", flag_descriptions::kThrottleMainTo60HzName,
+ // flag_descriptions::kThrottleMainTo60HzDescription, kOsAll,
+ // FEATURE_VALUE_TYPE(features::kThrottleMainFrameTo60Hz)},
#if BUILDFLAG(IS_ANDROID)
{"client-side-detection-send-intelligent-scan-info-android",
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -53,6 +53,7 @@
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/config/chromebox_for_meetings/buildflags.h" // PLATFORM_CFM
+#include "cc/base/features.h"
#include "chrome/browser/after_startup_task_utils.h"
#include "chrome/browser/ai/ai_manager.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
@@ -320,6 +321,7 @@
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_switches.h"
#include "components/version_info/version_info.h"
+#include "components/viz/common/switches.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webui/chrome_urls/pref_names.h"
#include "content/public/browser/attribution_data_model.h"
@@ -2844,6 +2846,15 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
command_line->AppendSwitchASCII(ash::switches::kHomedir, homedir.value());
#endif
+#if !BUILDFLAG(IS_ANDROID)
+ if (child_process_id != -1
+ && base::FeatureList::IsEnabled(::features::kThrottleMainFrameTo60Hz)
+ && !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableFrameRateLimit)) {
+ base::CommandLine::ForCurrentProcess()->
+ AppendSwitch(switches::kDisableFrameRateLimit);
+ }
+#endif
+
if (process_type == switches::kRendererProcess) {
content::RenderProcessHost* process =
content::RenderProcessHost::FromID(child_process_id);
diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Set-the-screen-frame-rate-to-60-Hz.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Set-the-screen-frame-rate-to-60-Hz.grdp
new file mode 100644
--- /dev/null
+++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Set-the-screen-frame-rate-to-60-Hz.grdp
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<grit-part>
+ <message name="IDS_THROTTLE_MAIN_THREAD_TO_60HZ_TITLE" desc="Text for 'Allow custom tab intents' settings-privacy option.">
+ Throttle frame rate to 60hz
+ </message>
+ <message name="IDS_THROTTLE_MAIN_THREAD_TO_60HZ_SUMMARY" desc="Summary text for 'Allow custom tab intents' settings-privacy option.">
+ Throttle compositor frame rate to 60fps, even when VSync rate is higher.
+ </message>
+</grit-part>
diff --git a/components/viz/common/frame_sinks/begin_frame_source.cc b/components/viz/common/frame_sinks/begin_frame_source.cc
--- a/components/viz/common/frame_sinks/begin_frame_source.cc
+++ b/components/viz/common/frame_sinks/begin_frame_source.cc
@@ -24,6 +24,7 @@
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.h"
+#include "cc/base/features.h"
namespace viz {
@@ -268,7 +269,10 @@ BackToBackBeginFrameSource::BackToBackBeginFrameSource(
time_source_->SetClient(this);
// The time_source_ ticks immediately, so we SetActive(true) for a single
// tick when we need it, and keep it as SetActive(false) otherwise.
- time_source_->SetTimebaseAndInterval(base::TimeTicks(), base::TimeDelta());
+ auto interval = base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)
+ ? base::Hertz(60)
+ : base::TimeDelta();
+ time_source_->SetTimebaseAndInterval(base::TimeTicks(), interval);
}
BackToBackBeginFrameSource::~BackToBackBeginFrameSource() = default;
@@ -326,6 +330,14 @@ void BackToBackBeginFrameSource::OnTimerTick() {
}
base::TimeTicks frame_time = time_source_->LastTickTime();
base::TimeDelta interval = max_vrr_interval_.value_or(vsync_interval_);
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ interval = base::Hertz(60);
+ frame_time = frame_time.SnappedToNextTick(base::TimeTicks(), interval);
+ if (last_frame_time_ == frame_time) {
+ return;
+ }
+ last_frame_time_ = frame_time;
+ }
BeginFrameArgs args = BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, source_id(), next_sequence_number_, frame_time,
frame_time + interval, interval, BeginFrameArgs::NORMAL, vsync_interval_);
diff --git a/components/viz/common/frame_sinks/begin_frame_source.h b/components/viz/common/frame_sinks/begin_frame_source.h
--- a/components/viz/common/frame_sinks/begin_frame_source.h
+++ b/components/viz/common/frame_sinks/begin_frame_source.h
@@ -363,6 +363,7 @@ class VIZ_COMMON_EXPORT BackToBackBeginFrameSource
pending_begin_frame_observers_;
uint64_t next_sequence_number_;
base::TimeDelta vsync_interval_ = BeginFrameArgs::DefaultInterval();
+ base::TimeTicks last_frame_time_;
std::optional<base::TimeDelta> max_vrr_interval_ = std::nullopt;
base::WeakPtrFactory<BackToBackBeginFrameSource> weak_factory_{this};
};
diff --git a/components/viz/common/frame_sinks/delay_based_time_source.cc b/components/viz/common/frame_sinks/delay_based_time_source.cc
--- a/components/viz/common/frame_sinks/delay_based_time_source.cc
+++ b/components/viz/common/frame_sinks/delay_based_time_source.cc
@@ -165,7 +165,7 @@ void DelayBasedTimeSource::PostNextTickTask(base::TimeTicks now) {
// double tick. See crbug.com/398090404 for details.
const auto kMaxJudderAllowed = base::Microseconds(500);
if (now + kMaxJudderAllowed >= next_tick_time_) {
- next_tick_time_ += interval_;
+ next_tick_time_ += interval_; // kAvoidDuplicateDelayBeginFrame
}
DCHECK_GT(next_tick_time_, now);
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -33,6 +33,7 @@
#include "base/trace_event/traced_value.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
+#include "cc/base/features.h"
#include "cc/base/math_util.h"
#include "cc/base/region.h"
#include "cc/base/simple_enclosed_region.h"
@@ -269,8 +270,21 @@ void Display::PresentationGroupTiming::OnSwap(gfx::SwapTimings timings,
auto frame_latency = timings.swap_start - frame_time_;
if (frame_latency < base::Seconds(0)) {
- LOG(ERROR) << "Frame latency is negative: "
- << frame_latency.InMillisecondsF() << " ms";
+ bool log_error = true;
+#if BUILDFLAG(IS_ANDROID)
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ // Android < 11 do not support dynamic frame rate, so if
+ // Android PerformanceHint (ADPF) is active, the frame time is
+ // deliberately shifted forward at 60Hz intervals to avoid glich,
+ // so do not log.
+ log_error = false;
+ // ADPF would not be informed of frame misses, which, in fact, do not exist.
+ }
+#endif
+ if (log_error) {
+ LOG(ERROR) << "Frame latency is negative: "
+ << frame_latency.InMillisecondsF() << " ms";
+ }
return;
}
// Can be nullptr in unittests.
diff --git a/components/viz/service/frame_sinks/external_begin_frame_source_android.cc b/components/viz/service/frame_sinks/external_begin_frame_source_android.cc
--- a/components/viz/service/frame_sinks/external_begin_frame_source_android.cc
+++ b/components/viz/service/frame_sinks/external_begin_frame_source_android.cc
@@ -17,6 +17,7 @@
#include "base/trace_event/typed_macros.h"
#include "ui/gfx/android/achoreographer_compat.h"
#include "ui/gl/gl_features.h"
+#include "cc/base/features.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/viz/service/service_jni_headers/ExternalBeginFrameSourceAndroid_jni.h"
@@ -261,6 +262,10 @@ ExternalBeginFrameSourceAndroid::ExternalBeginFrameSourceAndroid(
float refresh_rate,
bool requires_align_with_java)
: ExternalBeginFrameSource(this, restart_id) {
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz))
+ fixed_vsync_period_ = base::Hertz(60);
+ else
+ fixed_vsync_period_ = base::TimeDelta();
// Android WebView requires begin frame to be inside the "animate" stage of
// input-animate-draw stages of the java Choreographer, which requires using
// java Choreographer.
@@ -294,6 +299,14 @@ void ExternalBeginFrameSourceAndroid::OnVSyncImpl(
DCHECK_EQ(base::TimeTicks::GetClock(),
base::TimeTicks::Clock::LINUX_CLOCK_MONOTONIC);
base::TimeTicks frame_time = ToTimeTicks(time_nanos);
+ if (!fixed_vsync_period_.is_zero()) {
+ auto next_tick = frame_time.SnappedToNextTick(base::TimeTicks(), fixed_vsync_period_);
+ if (last_tick_ == next_tick) return;
+
+ last_tick_ = next_tick;
+ vsync_period = fixed_vsync_period_;
+ frame_time = next_tick;
+ }
// TODO(crbug.com/40829076): If `possible_deadlines` is present, should
// really pick a deadline from `possible_deadlines`. However some code
// still assume the deadline is a multiple of interval from frame time.
diff --git a/components/viz/service/frame_sinks/external_begin_frame_source_android.h b/components/viz/service/frame_sinks/external_begin_frame_source_android.h
--- a/components/viz/service/frame_sinks/external_begin_frame_source_android.h
+++ b/components/viz/service/frame_sinks/external_begin_frame_source_android.h
@@ -52,6 +52,8 @@ class VIZ_SERVICE_EXPORT ExternalBeginFrameSourceAndroid
std::unique_ptr<AChoreographerImpl> achoreographer_;
base::android::ScopedJavaGlobalRef<jobject> j_object_;
BeginFrameArgsGenerator begin_frame_args_generator_;
+ base::TimeDelta fixed_vsync_period_;
+ base::TimeTicks last_tick_;
};
} // namespace viz
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -21,6 +21,7 @@
#include "base/time/time.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "build/build_config.h"
+#include "cc/base/features.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/service/display/display.h"
@@ -772,7 +773,10 @@ void RootCompositorFrameSinkImpl::FrameIntervalDeciderResultCallback(
result);
interval = interval_and_compat.first;
gfx::SurfaceControlFrameRateCompatibility compat = interval_and_compat.second;
-
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ interval = base::Hertz(60);
+ compat = gfx::SurfaceControlFrameRateCompatibility::kFixedSource;
+ }
if (decided_display_interval_ == interval &&
decided_display_frame_rate_compat_ == compat) {
return;
@@ -794,7 +798,9 @@ void RootCompositorFrameSinkImpl::FrameIntervalDeciderResultCallback(
return interval.interval;
}),
result);
-
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ interval = base::Hertz(60);
+ }
if (decided_display_interval_ == interval) {
return;
}
diff --git a/cromite_flags/chrome/browser/about_flags_cc/Set-the-screen-frame-rate-to-60-Hz.inc b/cromite_flags/chrome/browser/about_flags_cc/Set-the-screen-frame-rate-to-60-Hz.inc
new file mode 100644
--- /dev/null
+++ b/cromite_flags/chrome/browser/about_flags_cc/Set-the-screen-frame-rate-to-60-Hz.inc
@@ -0,0 +1,7 @@
+#ifdef FLAG_SECTION
+
+ {"throttle-main-thread-to-60hz", "Throttle frame rate to 60hz",
+ "Throttle compositor frame rate to 60fps, even when VSync rate is higher.", kOsAll,
+ FEATURE_VALUE_TYPE(features::kThrottleMainFrameTo60Hz)},
+
+#endif
diff --git a/third_party/blink/renderer/core/animation/animation_clock.cc b/third_party/blink/renderer/core/animation/animation_clock.cc
--- a/third_party/blink/renderer/core/animation/animation_clock.cc
+++ b/third_party/blink/renderer/core/animation/animation_clock.cc
@@ -29,6 +29,7 @@
*/
#include "third_party/blink/renderer/core/animation/animation_clock.h"
+#include "cc/base/features.h"
#include <math.h>
@@ -77,9 +78,13 @@ base::TimeTicks AnimationClock::CurrentTime() {
// Attempt to predict what the most recent timestamp would have been. This
// may not produce a result greater than |time_|, but it greatly reduces the
// chance of conflicting with any future frame timestamp that does come in.
- const base::TimeDelta frame_shift =
- (current_time - time_) % kApproximateFrameTime;
- new_time = current_time - frame_shift;
+ if (base::FeatureList::IsEnabled(features::kThrottleMainFrameTo60Hz)) {
+ new_time = new_time.SnappedToNextTick(base::TimeTicks(), base::Hertz(60));
+ } else {
+ const base::TimeDelta frame_shift =
+ (current_time - time_) % kApproximateFrameTime;
+ new_time = current_time - frame_shift;
+ }
DCHECK_GE(new_time, time_);
}
UpdateTime(new_time);
diff --git a/third_party/blink/renderer/core/view_transition/view_transition.cc b/third_party/blink/renderer/core/view_transition/view_transition.cc
--- a/third_party/blink/renderer/core/view_transition/view_transition.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition.cc
@@ -1067,9 +1067,11 @@ CSSStyleSheet* ViewTransition::UAStyleSheet() const {
void ViewTransition::WillCommitCompositorFrame() {
// There should only be 1 commit when we're in the capturing phase and
// rendering is paused immediately after it finishes.
+#if !BUILDFLAG(IS_WIN)
if (state_ == State::kCapturing) {
PauseRendering();
}
+#endif
}
gfx::Size ViewTransition::GetSnapshotRootSize() const {
--