From f4d2f31be831c3a2c98f5fbdb3bf6cf9fa56e85f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 15 Jun 2026 13:12:52 +0300 Subject: [PATCH] feat(motion): enable high refresh rate via refresh_rate Add DisplayRefreshService with RefreshRate.enable() at startup, debug Hz logging, and optional Hz overlay via QUERYA_REFRESH_OVERLAY. Closes #174 --- docs/motion-and-high-refresh.md | 1 + lib/core/motion/display_refresh_service.dart | 49 +++++++++++++++++++ lib/main.dart | 2 + pubspec.yaml | 1 + .../motion/display_refresh_service_test.dart | 33 +++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 lib/core/motion/display_refresh_service.dart create mode 100644 test/core/motion/display_refresh_service_test.dart diff --git a/docs/motion-and-high-refresh.md b/docs/motion-and-high-refresh.md index 4897f02a..e3d7922f 100644 --- a/docs/motion-and-high-refresh.md +++ b/docs/motion-and-high-refresh.md @@ -67,6 +67,7 @@ Root cause (engine): per `flutter/flutter#160952`, the engine "can render at 120 - **Windows / Linux:** most likely already render at monitor Hz; the job is to **measure and verify**, then ensure no app-side code caps frames (e.g. heavy `setState`, unbounded rebuilds during animation). - **macOS:** the real high-Hz work — confirm ProMotion behavior; unlock via `refresh_rate` if capped at 60. - Use `refresh_rate` (or a thin wrapper) primarily for **diagnostics**: a debug-only FPS/Hz overlay and a benchmark to prove smoothness on each machine, plus the macOS unlock call in `main()`. +- **Implementation:** `lib/core/motion/display_refresh_service.dart` calls `RefreshRate.enable()` in `main()`. Debug Hz badge: `flutter run --dart-define=QUERYA_REFRESH_OVERLAY=true`. --- diff --git a/lib/core/motion/display_refresh_service.dart b/lib/core/motion/display_refresh_service.dart new file mode 100644 index 00000000..55b4e76e --- /dev/null +++ b/lib/core/motion/display_refresh_service.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; +import 'package:refresh_rate/refresh_rate.dart'; + +/// Desktop display refresh-rate unlock and diagnostics (UI-A4 / #174). +/// +/// Calls [RefreshRate.enable] once at startup (macOS 14+ ProMotion unlock; +/// query on Windows/Linux). Debug overlay: run with +/// `--dart-define=QUERYA_REFRESH_OVERLAY=true`. +abstract final class DisplayRefreshService { + static const bool _overlayFromEnvironment = bool.fromEnvironment( + 'QUERYA_REFRESH_OVERLAY', + ); + + /// Unlock peak refresh where the platform supports it; log Hz in debug builds. + static void initialize() { + RefreshRate.enable(); + + if (!kDebugMode) return; + + _logRefreshInfo(); + if (displayRefreshOverlayEnabled( + debugMode: kDebugMode, + overlayFlag: _overlayFromEnvironment, + )) { + RefreshRate.showHz(); + } + } + + static void _logRefreshInfo() { + final info = RefreshRate.info; + debugPrint( + 'Display refresh: ${info.currentRate} Hz ' + '(max ${info.maxRate}, variable=${info.isVariableRefreshRate})', + ); + } + + /// Cached current refresh rate from the platform (Hz), when available. + static double get currentHz => RefreshRate.info.currentRate; + + /// Peak supported refresh rate (Hz). + static double get maxHz => RefreshRate.info.maxRate; + + @visibleForTesting + static bool displayRefreshOverlayEnabled({ + required bool debugMode, + required bool overlayFlag, + }) => + debugMode && overlayFlag; +} diff --git a/lib/main.dart b/lib/main.dart index 887d9a07..d3ee6c6f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,11 +4,13 @@ import 'package:flutter/material.dart'; import 'app/app.dart'; import 'core/editor/syntax_highlight_service.dart'; import 'core/layout/ui_scale_controller.dart'; +import 'core/motion/display_refresh_service.dart'; import 'core/storage/local_db.dart'; import 'core/theme/theme_controller.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + DisplayRefreshService.initialize(); await LocalDb.initFfi(); await SyntaxHighlightService.ensureInitialized(); await ThemeController.instance.load(); diff --git a/pubspec.yaml b/pubspec.yaml index 5cb98878..2006b743 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: sdk: flutter http: ^1.2.2 crypto: ^3.0.6 + refresh_rate: ^1.0.2 shadcn_flutter: ^0.0.52 bitsdojo_window: ^0.1.6 path: ^1.9.0 diff --git a/test/core/motion/display_refresh_service_test.dart b/test/core/motion/display_refresh_service_test.dart new file mode 100644 index 00000000..f0f97422 --- /dev/null +++ b/test/core/motion/display_refresh_service_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/motion/display_refresh_service.dart'; + +void main() { + group('displayRefreshOverlayEnabled', () { + test('false in release or when flag unset', () { + expect( + DisplayRefreshService.displayRefreshOverlayEnabled( + debugMode: false, + overlayFlag: true, + ), + isFalse, + ); + expect( + DisplayRefreshService.displayRefreshOverlayEnabled( + debugMode: true, + overlayFlag: false, + ), + isFalse, + ); + }); + + test('true only in debug with QUERYA_REFRESH_OVERLAY', () { + expect( + DisplayRefreshService.displayRefreshOverlayEnabled( + debugMode: true, + overlayFlag: true, + ), + isTrue, + ); + }); + }); +}