Skip to content

Commit

Permalink
Add Linux platform support for fetching refresh rate on startup. (#52934
Browse files Browse the repository at this point in the history
)

This patch addresses the missing implementation of
`platformDispatcher.views` on Linux. It checks the refresh rate of the
renderer's window and returns the value. Without this implementation,
`WidgetsBinding.instance.platformDispatcher.views.first.display.size`
would throw an exception on Linux, preventing safe usage.

Related: flutter/flutter#144230
  • Loading branch information
bc-lee committed May 28, 2024
1 parent cf201b1 commit 416c619
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 3 deletions.
20 changes: 20 additions & 0 deletions shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 573,26 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
g_warning("Failed to enable accessibility features on Flutter engine");
}

gdouble refresh_rate = fl_renderer_get_refresh_rate(self->renderer);
// FlutterEngineDisplay::refresh_rate expects 0 if the refresh rate is
// unknown.
if (refresh_rate <= 0.0) {
refresh_rate = 0.0;
}
FlutterEngineDisplay display = {};
display.struct_size = sizeof(FlutterEngineDisplay);
display.display_id = 0;
display.single_display = true;
display.refresh_rate = refresh_rate;

std::vector displays = {display};
result = self->embedder_api.NotifyDisplayUpdate(
self->engine, kFlutterEngineDisplaysUpdateTypeStartup, displays.data(),
displays.size());
if (result != kSuccess) {
g_warning("Failed to notify display update to Flutter engine: %d", result);
}

return TRUE;
}

Expand Down
5 changes: 5 additions & 0 deletions shell/platform/linux/fl_renderer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 153,11 @@ void fl_renderer_clear_current(FlRenderer* self) {
FL_RENDERER_GET_CLASS(self)->clear_current(self);
}

gdouble fl_renderer_get_refresh_rate(FlRenderer* self) {
g_return_val_if_fail(FL_IS_RENDERER(self), -1.0);
return FL_RENDERER_GET_CLASS(self)->get_refresh_rate(self);
}

guint32 fl_renderer_get_fbo(FlRenderer* self) {
g_return_val_if_fail(FL_IS_RENDERER(self), 0);

Expand Down
19 changes: 19 additions & 0 deletions shell/platform/linux/fl_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 80,16 @@ struct _FlRendererClass {
*/
gboolean (*collect_backing_store)(FlRenderer* renderer,
const FlutterBackingStore* backing_store);

/**
* Virtual method called when Flutter wants to get the refresh rate of the
* renderer.
* @renderer: an #FlRenderer.
*
* Returns: The refresh rate of the display in Hz. If the refresh rate is
* not available, returns -1.0.
*/
gdouble (*get_refresh_rate)(FlRenderer* renderer);
};

/**
Expand Down Expand Up @@ -223,6 233,15 @@ void fl_renderer_render(FlRenderer* renderer, int width, int height);
*/
void fl_renderer_cleanup(FlRenderer* renderer);

/**
* fl_renderer_get_refresh_rate:
* @renderer: an #FlRenderer.
*
* Returns: The refresh rate of the display in Hz. If the refresh rate is
* not available, returns -1.0.
*/
gdouble fl_renderer_get_refresh_rate(FlRenderer* renderer);

G_END_DECLS

#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_H_
18 changes: 18 additions & 0 deletions shell/platform/linux/fl_renderer_gdk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 36,23 @@ static void fl_renderer_gdk_clear_current(FlRenderer* renderer) {
gdk_gl_context_clear_current();
}

static gdouble fl_renderer_gdk_get_refresh_rate(FlRenderer* renderer) {
FlRendererGdk* self = FL_RENDERER_GDK(renderer);
GdkDisplay* display = gdk_window_get_display(self->window);
GdkMonitor* monitor =
gdk_display_get_monitor_at_window(display, self->window);
if (monitor == nullptr) {
return -1.0;
}

int refresh_rate = gdk_monitor_get_refresh_rate(monitor);
if (refresh_rate <= 0) {
return -1.0;
}
// the return value is in milli-hertz, convert to hertz
return static_cast<gdouble>(refresh_rate) / 1000.0;
}

static void fl_renderer_gdk_dispose(GObject* object) {
FlRendererGdk* self = FL_RENDERER_GDK(object);

Expand All @@ -52,6 69,7 @@ static void fl_renderer_gdk_class_init(FlRendererGdkClass* klass) {
FL_RENDERER_CLASS(klass)->make_resource_current =
fl_renderer_gdk_make_resource_current;
FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_gdk_clear_current;
FL_RENDERER_CLASS(klass)->get_refresh_rate = fl_renderer_gdk_get_refresh_rate;
}

static void fl_renderer_gdk_init(FlRendererGdk* self) {}
Expand Down
7 changes: 7 additions & 0 deletions shell/platform/linux/fl_renderer_headless.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 19,18 @@ static void fl_renderer_headless_make_resource_current(FlRenderer* renderer) {}
// Implements FlRenderer::clear_current.
static void fl_renderer_headless_clear_current(FlRenderer* renderer) {}

// Implements FlRenderer::get_refresh_rate.
static gdouble fl_renderer_headless_get_refresh_rate(FlRenderer* renderer) {
return -1.0;
}

static void fl_renderer_headless_class_init(FlRendererHeadlessClass* klass) {
FL_RENDERER_CLASS(klass)->make_current = fl_renderer_headless_make_current;
FL_RENDERER_CLASS(klass)->make_resource_current =
fl_renderer_headless_make_resource_current;
FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_headless_clear_current;
FL_RENDERER_CLASS(klass)->get_refresh_rate =
fl_renderer_headless_get_refresh_rate;
}

static void fl_renderer_headless_init(FlRendererHeadless* self) {}
Expand Down
16 changes: 16 additions & 0 deletions shell/platform/linux/fl_renderer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 51,19 @@ TEST(FlRendererTest, RestoresGLState) {

g_object_ref_sink(view);
}

static constexpr double kExpectedRefreshRate = 120.0;
static gdouble renderer_get_refresh_rate(FlRenderer* renderer) {
return kExpectedRefreshRate;
}

TEST(FlRendererTest, RefreshRate) {
flutter::testing::fl_ensure_gtk_init();
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlMockRenderer) renderer =
fl_mock_renderer_new(&renderer_get_refresh_rate);

gdouble result_refresh_rate =
fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
}
9 changes: 9 additions & 0 deletions shell/platform/linux/testing/mock_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 513,14 @@ FlutterEngineResult FlutterEngineUnregisterExternalTexture(
return kSuccess;
}

FlutterEngineResult FlutterEngineNotifyDisplayUpdate(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
FlutterEngineDisplaysUpdateType update_type,
const FlutterEngineDisplay* displays,
size_t display_count) {
return kSuccess;
}

} // namespace

FlutterEngineResult FlutterEngineGetProcAddresses(
Expand Down Expand Up @@ -552,5 560,6 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
table->UnregisterExternalTexture = &FlutterEngineUnregisterExternalTexture;
table->UpdateAccessibilityFeatures =
&FlutterEngineUpdateAccessibilityFeatures;
table->NotifyDisplayUpdate = &FlutterEngineNotifyDisplayUpdate;
return kSuccess;
}
20 changes: 18 additions & 2 deletions shell/platform/linux/testing/mock_renderer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 6,7 @@

struct _FlMockRenderer {
FlRenderer parent_instance;
FlMockRendererGetRefreshRate get_refresh_rate;
};

G_DEFINE_TYPE(FlMockRenderer, fl_mock_renderer, fl_renderer_get_type())
Expand All @@ -19,16 20,31 @@ static void fl_mock_renderer_make_resource_current(FlRenderer* renderer) {}
// Implements FlRenderer::clear_current.
static void fl_mock_renderer_clear_current(FlRenderer* renderer) {}

// Implements FlRenderer::get_refresh_rate.
static gdouble fl_mock_renderer_default_get_refresh_rate(FlRenderer* renderer) {
FlMockRenderer* self = FL_MOCK_RENDERER(renderer);
if (self->get_refresh_rate != nullptr) {
return self->get_refresh_rate(renderer);
}
return -1.0;
}

static void fl_mock_renderer_class_init(FlMockRendererClass* klass) {
FL_RENDERER_CLASS(klass)->make_current = fl_mock_renderer_make_current;
FL_RENDERER_CLASS(klass)->make_resource_current =
fl_mock_renderer_make_resource_current;
FL_RENDERER_CLASS(klass)->clear_current = fl_mock_renderer_clear_current;
FL_RENDERER_CLASS(klass)->get_refresh_rate =
fl_mock_renderer_default_get_refresh_rate;
}

static void fl_mock_renderer_init(FlMockRenderer* self) {}

// Creates a stub renderer
FlMockRenderer* fl_mock_renderer_new() {
return FL_MOCK_RENDERER(g_object_new(fl_mock_renderer_get_type(), nullptr));
FlMockRenderer* fl_mock_renderer_new(
FlMockRendererGetRefreshRate get_refresh_rate) {
FlMockRenderer* fl_mock_renderer = FL_MOCK_RENDERER(
g_object_new_valist(fl_mock_renderer_get_type(), nullptr, nullptr));
fl_mock_renderer->get_refresh_rate = get_refresh_rate;
return fl_mock_renderer;
}
5 changes: 4 additions & 1 deletion shell/platform/linux/testing/mock_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 15,10 @@ G_DECLARE_FINAL_TYPE(FlMockRenderer,
MOCK_RENDERER,
FlRenderer)

FlMockRenderer* fl_mock_renderer_new();
typedef gdouble (*FlMockRendererGetRefreshRate)(FlRenderer* renderer);

FlMockRenderer* fl_mock_renderer_new(
FlMockRendererGetRefreshRate get_refresh_rate = nullptr);

G_END_DECLS

Expand Down

0 comments on commit 416c619

Please sign in to comment.