Skip to content

Commit

Permalink
Multi-view View Metrics (#46174)
Browse files Browse the repository at this point in the history
This PR adds `FlutterWindowMetricsEvent.viewId` to the embedder API.

This PR only tests the ability to send metrics event for the implicit
view. Once multiple views can be added via embedder API, we should test
the ability to send different IDs.

Part of flutter/flutter#144806
Part of flutter/flutter#142845

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I signed the [CLA].
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
dkwingsmt authored Mar 14, 2024
1 parent 6710d10 commit 72ca2e1
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 13 deletions.
3 changes: 3 additions & 0 deletions examples/glfw/FlutterEmbedderGLFW.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) {
event.width = width * g_pixelRatio;
event.height = height * g_pixelRatio;
event.pixel_ratio = g_pixelRatio;
// This example only supports a single window, therefore we assume the event
// occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendWindowMetricsEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)),
&event);
Expand Down
3 changes: 3 additions & 0 deletions examples/glfw_drm/FlutterEmbedderGLFW.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) {
event.width = width * g_pixelRatio;
event.height = height * g_pixelRatio;
event.pixel_ratio = g_pixelRatio;
// This example only supports a single window, therefore we assume the event
// occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendWindowMetricsEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)),
&event);
Expand Down
7 changes: 7 additions & 0 deletions examples/vulkan_glfw/src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static const size_t kInitialWindowHeight = 600;
// `VK_PRESENT_MODE_MAILBOX_KHR` for continual swap without horizontal tearing,
// or `VK_PRESENT_MODE_IMMEDIATE_KHR` for no vsync.
static const VkPresentModeKHR kPreferredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
static constexpr FlutterViewId kImplicitViewId = 0;

static_assert(FLUTTER_ENGINE_VERSION == 1,
"This Flutter Embedder was authored against the stable Flutter "
Expand Down Expand Up @@ -86,6 +87,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// This example only supports a single window, therefore we assume the event
// occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendPointerEvent(g_state.engine, &event, 1);
}

Expand Down Expand Up @@ -130,6 +134,9 @@ void GLFWframebufferSizeCallback(GLFWwindow* window, int width, int height) {
event.width = width;
event.height = height;
event.pixel_ratio = g_pixelRatio;
// This example only supports a single window, therefore we assume the event
// occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendWindowMetricsEvent(g_state.engine, &event);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,12 +962,6 @@ - (nonnull NSString*)executableName {
}

- (void)updateWindowMetricsForViewController:(FlutterViewController*)viewController {
if (viewController.viewId != kFlutterImplicitViewId) {
// TODO(dkwingsmt): The embedder API only supports single-view for now. As
// embedder APIs are converted to multi-view, this method should support any
// views.
return;
}
if (!_engine || !viewController || !viewController.viewLoaded) {
return;
}
Expand All @@ -986,6 +980,7 @@ - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewControl
.left = static_cast<size_t>(scaledBounds.origin.x),
.top = static_cast<size_t>(scaledBounds.origin.y),
.display_id = static_cast<uint64_t>(displayId),
.view_id = viewController.viewId,
};
_embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
}
Expand Down
4 changes: 2 additions & 2 deletions shell/platform/embedder/embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2141,8 +2141,8 @@ FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
if (engine == nullptr || flutter_metrics == nullptr) {
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid.");
}
// TODO(dkwingsmt): Use a real view ID when multiview is supported.
int64_t view_id = kFlutterImplicitViewId;
FlutterViewId view_id =
SAFE_ACCESS(flutter_metrics, view_id, kFlutterImplicitViewId);

flutter::ViewportMetrics metrics;

Expand Down
2 changes: 2 additions & 0 deletions shell/platform/embedder/embedder.h
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,8 @@ typedef struct {
double physical_view_inset_left;
/// The identifier of the display the view is rendering on.
FlutterEngineDisplayId display_id;
/// The view that this event is describing.
int64_t view_id;
} FlutterWindowMetricsEvent;

/// The phase of the pointer event.
Expand Down
36 changes: 36 additions & 0 deletions shell/platform/embedder/fixtures/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,42 @@ void pointer_data_packet_view_id() {
signalNativeTest();
}

Map<int, Size> _getAllViewSizes() {
final Map<int, Size> result = <int, Size>{};
for (final FlutterView view in PlatformDispatcher.instance.views) {
result[view.viewId] = view.physicalSize;
}
return result;
}

List<int> _findDifferences(Map<int, Size> a, Map<int, Size> b) {
final Set<int> result = <int>{};
a.forEach((int viewId, Size sizeA) {
if (!b.containsKey(viewId) || b[viewId] != sizeA) {
result.add(viewId);
}
});
b.forEach((int viewId, Size sizeB) {
if (!a.containsKey(viewId)) {
result.add(viewId);
}
});
return result.toList()..sort();
}

@pragma('vm:entry-point')
void window_metrics_event_view_id() {
Map<int, Size> sizes = _getAllViewSizes();
PlatformDispatcher.instance.onMetricsChanged = () {
final Map<int, Size> newSizes = _getAllViewSizes();
final List<int> differences = _findDifferences(sizes, newSizes);
sizes = newSizes;
signalNativeMessage('Changed: $differences');
};

signalNativeTest();
}

@pragma('vm:entry-point')
Future<void> channel_listener_response() async {
channelBuffers.setListener('test/listen',
Expand Down
99 changes: 98 additions & 1 deletion shell/platform/embedder/tests/embedder_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2727,7 +2727,7 @@ TEST_F(EmbedderTest, CanSendPointer) {

/// Send a pointer event to Dart and wait until the Dart code echos with the
/// view ID.
TEST_F(EmbedderTest, CanSendPointerWithViewId) {
TEST_F(EmbedderTest, CanSendPointerEventWithViewId) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
EmbedderConfigBuilder builder(context);
builder.SetSoftwareRendererConfig();
Expand Down Expand Up @@ -2767,6 +2767,103 @@ TEST_F(EmbedderTest, CanSendPointerWithViewId) {
message_latch.Wait();
}

TEST_F(EmbedderTest, WindowMetricsEventDefaultsToImplicitView) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
EmbedderConfigBuilder builder(context);
builder.SetSoftwareRendererConfig();
builder.SetDartEntrypoint("window_metrics_event_view_id");

fml::AutoResetWaitableEvent ready_latch, message_latch;
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY(
[&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); }));
context.AddNativeCallback(
"SignalNativeMessage",
CREATE_NATIVE_ENTRY([&message_latch](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ("Changed: [0]", message);
message_latch.Signal();
}));

auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());

ready_latch.Wait();

FlutterWindowMetricsEvent event = {};
// Simulate an event that comes from an old version of embedder.h that doesn't
// have the view_id field.
event.struct_size = offsetof(FlutterWindowMetricsEvent, view_id);
event.width = 200;
event.height = 300;
event.pixel_ratio = 1.5;
// Skip assigning event.view_id here to test the default behavior.

FlutterEngineResult result =
FlutterEngineSendWindowMetricsEvent(engine.get(), &event);
ASSERT_EQ(result, kSuccess);

message_latch.Wait();
}

TEST_F(EmbedderTest, IgnoresWindowMetricsEventForUnknownView) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
EmbedderConfigBuilder builder(context);
builder.SetSoftwareRendererConfig();
builder.SetDartEntrypoint("window_metrics_event_view_id");

fml::AutoResetWaitableEvent ready_latch, message_latch;
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY(
[&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); }));

context.AddNativeCallback(
"SignalNativeMessage",
CREATE_NATIVE_ENTRY([&message_latch](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
// Message latch should only be signaled once as the bad
// view metric should be dropped by the engine.
ASSERT_FALSE(message_latch.IsSignaledForTest());
ASSERT_EQ("Changed: [0]", message);
message_latch.Signal();
}));

auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());

ready_latch.Wait();

// Send a window metric for a nonexistent view, which should be dropped by the
// engine.
FlutterWindowMetricsEvent bad_event = {};
bad_event.struct_size = sizeof(FlutterWindowMetricsEvent);
bad_event.width = 200;
bad_event.height = 300;
bad_event.pixel_ratio = 1.5;
bad_event.view_id = 100;

FlutterEngineResult result =
FlutterEngineSendWindowMetricsEvent(engine.get(), &bad_event);
ASSERT_EQ(result, kSuccess);

// Send a window metric for a valid view. The engine notifies the Dart app.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(FlutterWindowMetricsEvent);
event.width = 200;
event.height = 300;
event.pixel_ratio = 1.5;
event.view_id = 0;

result = FlutterEngineSendWindowMetricsEvent(engine.get(), &event);
ASSERT_EQ(result, kSuccess);

message_latch.Wait();
}

TEST_F(EmbedderTest, RegisterChannelListener) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);

Expand Down
3 changes: 3 additions & 0 deletions shell/platform/glfw/flutter_glfw.cc
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ static void SendWindowMetrics(FlutterDesktopWindowControllerState* controller,
} else {
event.pixel_ratio = controller->window_wrapper->pixel_ratio_override;
}
// The GLFW embedder doesn't support multiple views. We assume all pointer
// events come from the only view, the implicit view.
event.view_id = flutter::kFlutterImplicitViewId;
FlutterEngineSendWindowMetricsEvent(controller->engine->flutter_engine,
&event);
}
Expand Down
4 changes: 4 additions & 0 deletions shell/platform/linux/fl_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,10 @@ void fl_engine_send_window_metrics_event(FlEngine* self,
event.width = width;
event.height = height;
event.pixel_ratio = pixel_ratio;
// TODO(dkwingsmt): Assign the correct view ID once the Linux embedder
// supports multiple views.
// https://github.com/flutter/flutter/issues/138178
event.view_id = flutter::kFlutterImplicitViewId;
self->embedder_api.SendWindowMetricsEvent(self->engine, &event);
}

Expand Down
6 changes: 2 additions & 4 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ void FlutterWindowsView::SendWindowMetrics(size_t width,
event.width = width;
event.height = height;
event.pixel_ratio = dpiScale;
event.view_id = view_id_;
engine_->SendWindowMetricsEvent(event);
}

Expand Down Expand Up @@ -588,10 +589,7 @@ void FlutterWindowsView::SendPointerEventWithData(
event.device_kind = state->device_kind;
event.device = state->pointer_id;
event.buttons = state->buttons;
// TODO(dkwingsmt): Use the correct view ID for pointer events once the
// Windows embedder supports multiple views.
// https://github.com/flutter/flutter/issues/138179
event.view_id = flutter::kFlutterImplicitViewId;
event.view_id = view_id_;

// Set metadata that's always the same regardless of the event.
event.struct_size = sizeof(event);
Expand Down

0 comments on commit 72ca2e1

Please sign in to comment.