Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qq_34031325
engine
提交
07e2520d
E
engine
项目概览
qq_34031325
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
07e2520d
编写于
8月 28, 2020
作者:
D
David Worsham
提交者:
GitHub
8月 28, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
fuchsia: create new flutter_runner render path (#19584)
上级
80a1cac4
变更
12
隐藏空白更改
内联
并排
Showing
12 changed file
with
872 addition
and
245 deletion
+872
-245
ci/licenses_golden/licenses_flutter
ci/licenses_golden/licenses_flutter
+2
-0
shell/platform/fuchsia/flutter/BUILD.gn
shell/platform/fuchsia/flutter/BUILD.gn
+8
-2
shell/platform/fuchsia/flutter/component.cc
shell/platform/fuchsia/flutter/component.cc
+6
-3
shell/platform/fuchsia/flutter/engine.cc
shell/platform/fuchsia/flutter/engine.cc
+124
-65
shell/platform/fuchsia/flutter/engine.h
shell/platform/fuchsia/flutter/engine.h
+15
-6
shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
...m/fuchsia/flutter/flutter_runner_product_configuration.cc
+5
-0
shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
...rm/fuchsia/flutter/flutter_runner_product_configuration.h
+7
-0
shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc
...latform/fuchsia/flutter/fuchsia_external_view_embedder.cc
+442
-0
shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h
...platform/fuchsia/flutter/fuchsia_external_view_embedder.h
+158
-0
shell/platform/fuchsia/flutter/platform_view.cc
shell/platform/fuchsia/flutter/platform_view.cc
+16
-21
shell/platform/fuchsia/flutter/platform_view.h
shell/platform/fuchsia/flutter/platform_view.h
+17
-15
shell/platform/fuchsia/flutter/platform_view_unittest.cc
shell/platform/fuchsia/flutter/platform_view_unittest.cc
+72
-133
未找到文件。
ci/licenses_golden/licenses_flutter
浏览文件 @
07e2520d
...
...
@@ -1162,6 +1162,8 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/engine.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl.cc
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl.h
FILE: ../../../flutter/shell/platform/fuchsia/flutter/fuchsia_intl_unittest.cc
...
...
shell/platform/fuchsia/flutter/BUILD.gn
浏览文件 @
07e2520d
...
...
@@ -53,12 +53,12 @@ template("runner_sources") {
"accessibility_bridge.h",
"component.cc",
"component.h",
"compositor_context.cc",
"compositor_context.h",
"engine.cc",
"engine.h",
"flutter_runner_product_configuration.cc",
"flutter_runner_product_configuration.h",
"fuchsia_external_view_embedder.cc",
"fuchsia_external_view_embedder.h",
"fuchsia_intl.cc",
"fuchsia_intl.h",
"isolate_configurator.cc",
...
...
@@ -92,6 +92,12 @@ template("runner_sources") {
"vulkan_surface_producer.cc",
"vulkan_surface_producer.h",
]
if (flutter_enable_legacy_fuchsia_embedder) {
sources += [
"compositor_context.cc",
"compositor_context.h",
]
}
public_configs = runner_configs
...
...
shell/platform/fuchsia/flutter/component.cc
浏览文件 @
07e2520d
...
...
@@ -54,6 +54,7 @@ constexpr char kDataKey[] = "data";
constexpr
char
kAssetsKey
[]
=
"assets"
;
constexpr
char
kTmpPath
[]
=
"/tmp"
;
constexpr
char
kServiceRootPath
[]
=
"/svc"
;
constexpr
char
kRunnerConfigPath
[]
=
"/config/data/flutter_runner_config"
;
// static
void
Application
::
ParseProgramMetadata
(
...
...
@@ -346,11 +347,13 @@ Application::Application(
}
}
// Load and use
product
-specific configuration, if it exists.
// Load and use
runner
-specific configuration, if it exists.
std
::
string
json_string
;
if
(
dart_utils
::
ReadFileToString
(
"/config/data/frame_scheduling_performance_values"
,
&
json_string
))
{
if
(
dart_utils
::
ReadFileToString
(
kRunnerConfigPath
,
&
json_string
))
{
product_config_
=
FlutterRunnerProductConfiguration
(
json_string
);
}
else
{
FML_LOG
(
WARNING
)
<<
"Failed to load runner configuration from "
<<
kRunnerConfigPath
<<
"; using default config values."
;
}
#if defined(DART_PRODUCT)
...
...
shell/platform/fuchsia/flutter/engine.cc
浏览文件 @
07e2520d
...
...
@@ -8,7 +8,6 @@
#include <zircon/status.h>
#include "../runtime/dart/utils/files.h"
#include "compositor_context.h"
#include "flutter/common/task_runners.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/synchronization/waitable_event.h"
...
...
@@ -16,13 +15,20 @@
#include "flutter/runtime/dart_vm_lifecycle.h"
#include "flutter/shell/common/rasterizer.h"
#include "flutter/shell/common/run_configuration.h"
#include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
#include "flutter_runner_product_configuration.h"
#include "fuchsia_external_view_embedder.h"
#include "fuchsia_intl.h"
#include "platform_view.h"
#include "surface.h"
#include "task_runner_adapter.h"
#include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
#include "thread.h"
#if defined(LEGACY_FUCHSIA_EMBEDDER)
#include "compositor_context.h" // nogncheck
#endif
namespace
flutter_runner
{
namespace
{
...
...
@@ -65,6 +71,9 @@ Engine::Engine(Delegate& delegate,
FlutterRunnerProductConfiguration
product_config
)
:
delegate_
(
delegate
),
thread_label_
(
std
::
move
(
thread_label
)),
#if defined(LEGACY_FUCHSIA_EMBEDDER)
use_legacy_renderer_
(
product_config
.
use_legacy_renderer
()),
#endif
weak_factory_
(
this
)
{
if
(
zx
::
event
::
create
(
0
,
&
vsync_event_
)
!=
ZX_OK
)
{
FML_DLOG
(
ERROR
)
<<
"Could not create the vsync event."
;
...
...
@@ -124,9 +133,18 @@ Engine::Engine(Delegate& delegate,
thread_label_
,
std
::
move
(
session
),
std
::
move
(
session_error_callback
),
[](
auto
)
{},
vsync_handle
);
surface_producer_
.
emplace
(
session_connection_
->
get
());
scene_update_context_
.
emplace
(
thread_label_
,
std
::
move
(
view_token
),
std
::
move
(
view_ref_pair
),
session_connection_
.
value
());
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
legacy_external_view_embedder_
.
emplace
(
thread_label_
,
std
::
move
(
view_token
),
std
::
move
(
view_ref_pair
),
session_connection_
.
value
());
}
else
#endif
{
external_view_embedder_
.
emplace
(
thread_label_
,
std
::
move
(
view_token
),
std
::
move
(
view_ref_pair
),
session_connection_
.
value
(),
surface_producer_
.
value
());
}
}));
// Grab the parent environment services. The platform view may want to
...
...
@@ -152,11 +170,8 @@ Engine::Engine(Delegate& delegate,
OnDestroyView
on_destroy_view_callback
=
std
::
bind
(
&
Engine
::
DestroyView
,
this
,
std
::
placeholders
::
_1
);
OnGetViewEmbedder
on_get_view_embedder_callback
=
std
::
bind
(
&
Engine
::
GetViewEmbedder
,
this
);
OnGetGrContext
on_get_gr_context_callback
=
std
::
bind
(
&
Engine
::
GetGrContext
,
this
);
OnCreateSurface
on_create_surface_callback
=
std
::
bind
(
&
Engine
::
CreateSurface
,
this
);
// SessionListener has a OnScenicError method; invoke this callback on the
// platform thread when that happens. The Session itself should also be
...
...
@@ -187,11 +202,9 @@ Engine::Engine(Delegate& delegate,
on_create_view_callback
=
std
::
move
(
on_create_view_callback
),
on_update_view_callback
=
std
::
move
(
on_update_view_callback
),
on_destroy_view_callback
=
std
::
move
(
on_destroy_view_callback
),
on_get_view_embedder_callback
=
std
::
move
(
on_get_view_embedder_callback
),
on_get_gr_context_callback
=
std
::
move
(
on_get_gr_context_callback
),
vsync_handle
=
vsync_event_
.
get
(),
product_config
=
product_config
](
flutter
::
Shell
&
shell
)
mutable
{
on_create_surface_callback
=
std
::
move
(
on_create_surface_callback
),
vsync_offset
=
product_config
.
get_vsync_offset
(),
vsync_handle
=
vsync_event_
.
get
()](
flutter
::
Shell
&
shell
)
mutable
{
return
std
::
make_unique
<
flutter_runner
::
PlatformView
>
(
shell
,
// delegate
debug_label
,
// debug label
...
...
@@ -206,27 +219,35 @@ Engine::Engine(Delegate& delegate,
std
::
move
(
on_create_view_callback
),
std
::
move
(
on_update_view_callback
),
std
::
move
(
on_destroy_view_callback
),
std
::
move
(
on_get_view_embedder_callback
),
std
::
move
(
on_get_gr_context_callback
),
vsync_handle
,
// vsync handle
product_config
);
std
::
move
(
on_create_surface_callback
),
std
::
move
(
vsync_offset
),
// vsync offset
vsync_handle
);
});
// Setup the callback that will instantiate the rasterizer.
flutter
::
Shell
::
CreateCallback
<
flutter
::
Rasterizer
>
on_create_rasterizer
=
fml
::
MakeCopyable
([
this
](
flutter
::
Shell
&
shell
)
mutable
{
FML_DCHECK
(
session_connection_
);
FML_DCHECK
(
surface_producer_
);
FML_DCHECK
(
scene_update_context_
);
std
::
unique_ptr
<
flutter_runner
::
CompositorContext
>
compositor_context
=
std
::
make_unique
<
flutter_runner
::
CompositorContext
>
(
session_connection_
.
value
(),
surface_producer_
.
value
(),
scene_update_context_
.
value
());
return
std
::
make_unique
<
flutter
::
Rasterizer
>
(
shell
,
std
::
move
(
compositor_context
));
});
flutter
::
Shell
::
CreateCallback
<
flutter
::
Rasterizer
>
on_create_rasterizer
;
#if defined(LEGACY_FUCHSIA_EMBEDDER)
on_create_rasterizer
=
[
this
](
flutter
::
Shell
&
shell
)
{
if
(
use_legacy_renderer_
)
{
FML_DCHECK
(
session_connection_
);
FML_DCHECK
(
surface_producer_
);
FML_DCHECK
(
legacy_external_view_embedder_
);
auto
compositor_context
=
std
::
make_unique
<
flutter_runner
::
CompositorContext
>
(
session_connection_
.
value
(),
surface_producer_
.
value
(),
legacy_external_view_embedder_
.
value
());
return
std
::
make_unique
<
flutter
::
Rasterizer
>
(
shell
,
std
::
move
(
compositor_context
));
}
else
{
return
std
::
make_unique
<
flutter
::
Rasterizer
>
(
shell
);
}
};
#else
on_create_rasterizer
=
[](
flutter
::
Shell
&
shell
)
{
return
std
::
make_unique
<
flutter
::
Rasterizer
>
(
shell
);
};
#endif
settings
.
root_isolate_create_callback
=
std
::
bind
(
&
Engine
::
OnMainIsolateStart
,
this
);
...
...
@@ -479,59 +500,97 @@ void Engine::Terminate() {
}
void
Engine
::
DebugWireframeSettingsChanged
(
bool
enabled
)
{
if
(
!
shell_
||
!
scene_update_context_
)
{
return
;
}
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
(
[
this
,
enabled
]()
{
scene_update_context_
->
EnableWireframe
(
enabled
);
});
FML_CHECK
(
shell_
);
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
([
this
,
enabled
]()
{
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
FML_CHECK
(
legacy_external_view_embedder_
);
legacy_external_view_embedder_
->
EnableWireframe
(
enabled
);
}
else
#endif
{
FML_CHECK
(
external_view_embedder_
);
external_view_embedder_
->
EnableWireframe
(
enabled
);
}
});
}
void
Engine
::
CreateView
(
int64_t
view_id
,
bool
hit_testable
,
bool
focusable
)
{
if
(
!
shell_
||
!
scene_update_context_
)
{
return
;
}
FML_CHECK
(
shell_
);
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
(
[
this
,
view_id
,
hit_testable
,
focusable
]()
{
scene_update_context_
->
CreateView
(
view_id
,
hit_testable
,
focusable
);
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
FML_CHECK
(
legacy_external_view_embedder_
);
legacy_external_view_embedder_
->
CreateView
(
view_id
,
hit_testable
,
focusable
);
}
else
#endif
{
FML_CHECK
(
external_view_embedder_
);
external_view_embedder_
->
CreateView
(
view_id
);
external_view_embedder_
->
SetViewProperties
(
view_id
,
hit_testable
,
focusable
);
}
});
}
void
Engine
::
UpdateView
(
int64_t
view_id
,
bool
hit_testable
,
bool
focusable
)
{
if
(
!
shell_
||
!
scene_update_context_
)
{
return
;
}
FML_CHECK
(
shell_
);
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
(
[
this
,
view_id
,
hit_testable
,
focusable
]()
{
scene_update_context_
->
UpdateView
(
view_id
,
hit_testable
,
focusable
);
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
FML_CHECK
(
legacy_external_view_embedder_
);
legacy_external_view_embedder_
->
UpdateView
(
view_id
,
hit_testable
,
focusable
);
}
else
#endif
{
FML_CHECK
(
external_view_embedder_
);
external_view_embedder_
->
SetViewProperties
(
view_id
,
hit_testable
,
focusable
);
}
});
}
void
Engine
::
DestroyView
(
int64_t
view_id
)
{
if
(
!
shell_
||
!
scene_update_context_
)
{
return
;
}
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
(
[
this
,
view_id
]()
{
scene_update_context_
->
DestroyView
(
view_id
);
});
FML_CHECK
(
shell_
);
shell_
->
GetTaskRunners
().
GetRasterTaskRunner
()
->
PostTask
([
this
,
view_id
]()
{
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
FML_CHECK
(
legacy_external_view_embedder_
);
legacy_external_view_embedder_
->
DestroyView
(
view_id
);
}
else
#endif
{
FML_CHECK
(
external_view_embedder_
);
external_view_embedder_
->
DestroyView
(
view_id
);
}
});
}
flutter
::
ExternalViewEmbedder
*
Engine
::
GetViewEmbedder
()
{
if
(
!
scene_update_context_
)
{
return
nullptr
;
}
return
&
scene_update_context_
.
value
();
}
std
::
unique_ptr
<
flutter
::
Surface
>
Engine
::
CreateSurface
()
{
flutter
::
ExternalViewEmbedder
*
external_view_embedder
=
nullptr
;
GrDirectContext
*
Engine
::
GetGrContext
()
{
// GetGrContext should be called only after rasterizer is created.
FML_DCHECK
(
shell_
);
FML_DCHECK
(
shell_
->
GetRasterizer
());
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
use_legacy_renderer_
)
{
FML_CHECK
(
legacy_external_view_embedder_
);
external_view_embedder
=
&
legacy_external_view_embedder_
.
value
();
}
else
#endif
{
FML_CHECK
(
external_view_embedder_
);
external_view_embedder
=
&
external_view_embedder_
.
value
();
}
FML_CHECK
(
external_view_embedder
);
return
surface_producer_
->
gr_context
();
return
std
::
make_unique
<
Surface
>
(
thread_label_
,
external_view_embedder
,
surface_producer_
->
gr_context
());
}
#if !defined(DART_PRODUCT)
...
...
shell/platform/fuchsia/flutter/engine.h
浏览文件 @
07e2520d
...
...
@@ -14,17 +14,21 @@
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/zx/event.h>
#include "flutter/flow/embedded_views.h"
#include "flutter/flow/scene_update_context.h"
#include "flutter/flow/surface.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/common/shell.h"
#include "flutter_runner_product_configuration.h"
#include "fuchsia_external_view_embedder.h"
#include "isolate_configurator.h"
#include "session_connection.h"
#include "thread.h"
#include "vulkan_surface_producer.h"
#if defined(LEGACY_FUCHSIA_EMBEDDER)
#include "flutter/flow/scene_update_context.h" // nogncheck
#endif
namespace
flutter_runner
{
// Represents an instance of running Flutter engine along with the threads
...
...
@@ -65,7 +69,10 @@ class Engine final {
std
::
optional
<
SessionConnection
>
session_connection_
;
std
::
optional
<
VulkanSurfaceProducer
>
surface_producer_
;
std
::
optional
<
flutter
::
SceneUpdateContext
>
scene_update_context_
;
std
::
optional
<
FuchsiaExternalViewEmbedder
>
external_view_embedder_
;
#if defined(LEGACY_FUCHSIA_EMBEDDER)
std
::
optional
<
flutter
::
SceneUpdateContext
>
legacy_external_view_embedder_
;
#endif
std
::
unique_ptr
<
IsolateConfigurator
>
isolate_configurator_
;
std
::
unique_ptr
<
flutter
::
Shell
>
shell_
;
...
...
@@ -74,6 +81,10 @@ class Engine final {
zx
::
event
vsync_event_
;
#if defined(LEGACY_FUCHSIA_EMBEDDER)
bool
use_legacy_renderer_
=
true
;
#endif
fml
::
WeakPtrFactory
<
Engine
>
weak_factory_
;
void
OnMainIsolateStart
();
...
...
@@ -87,9 +98,7 @@ class Engine final {
void
UpdateView
(
int64_t
view_id
,
bool
hit_testable
,
bool
focusable
);
void
DestroyView
(
int64_t
view_id
);
flutter
::
ExternalViewEmbedder
*
GetViewEmbedder
();
GrDirectContext
*
GetGrContext
();
std
::
unique_ptr
<
flutter
::
Surface
>
CreateSurface
();
FML_DISALLOW_COPY_AND_ASSIGN
(
Engine
);
};
...
...
shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc
浏览文件 @
07e2520d
...
...
@@ -20,6 +20,11 @@ FlutterRunnerProductConfiguration::FlutterRunnerProductConfiguration(
if
(
auto
&
val
=
document
[
"vsync_offset_in_us"
];
val
.
IsInt
())
{
vsync_offset_
=
fml
::
TimeDelta
::
FromMicroseconds
(
val
.
GetInt
());
}
#if defined(LEGACY_FUCHSIA_EMBEDDER)
if
(
auto
&
val
=
document
[
"use_legacy_renderer"
];
val
.
IsBool
())
{
use_legacy_renderer_
=
val
.
GetBool
();
}
#endif
}
}
// namespace flutter_runner
shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h
浏览文件 @
07e2520d
...
...
@@ -4,6 +4,7 @@
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_RUNNER_PRODUCT_CONFIGURATION_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_RUNNER_PRODUCT_CONFIGURATION_H_
#include "flutter/fml/time/time_delta.h"
namespace
flutter_runner
{
...
...
@@ -14,9 +15,15 @@ class FlutterRunnerProductConfiguration {
FlutterRunnerProductConfiguration
(
std
::
string
path
);
fml
::
TimeDelta
get_vsync_offset
()
{
return
vsync_offset_
;
}
#if defined(LEGACY_FUCHSIA_EMBEDDER)
bool
use_legacy_renderer
()
{
return
use_legacy_renderer_
;
}
#endif
private:
fml
::
TimeDelta
vsync_offset_
=
fml
::
TimeDelta
::
Zero
();
#if defined(LEGACY_FUCHSIA_EMBEDDER)
bool
use_legacy_renderer_
=
true
;
#endif
};
}
// namespace flutter_runner
...
...
shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc
0 → 100644
浏览文件 @
07e2520d
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fuchsia_external_view_embedder.h"
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/types.h>
#include <algorithm> // For std::clamp
#include "flutter/fml/trace_event.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace
flutter_runner
{
namespace
{
// Layer separation is as infinitesimal as possible without introducing
// Z-fighting.
constexpr
float
kScenicZElevationBetweenLayers
=
0.0001
f
;
constexpr
float
kScenicZElevationForPlatformView
=
100.
f
;
}
// namespace
FuchsiaExternalViewEmbedder
::
FuchsiaExternalViewEmbedder
(
std
::
string
debug_label
,
fuchsia
::
ui
::
views
::
ViewToken
view_token
,
scenic
::
ViewRefPair
view_ref_pair
,
SessionConnection
&
session
,
VulkanSurfaceProducer
&
surface_producer
)
:
session_
(
session
),
surface_producer_
(
surface_producer
),
root_view_
(
session_
.
get
(),
std
::
move
(
view_token
),
std
::
move
(
view_ref_pair
.
control_ref
),
std
::
move
(
view_ref_pair
.
view_ref
),
debug_label
),
metrics_node_
(
session_
.
get
()),
root_node_
(
session_
.
get
())
{
root_view_
.
AddChild
(
metrics_node_
);
metrics_node_
.
SetEventMask
(
fuchsia
::
ui
::
gfx
::
kMetricsEventMask
);
metrics_node_
.
SetLabel
(
"Flutter::MetricsWatcher"
);
metrics_node_
.
AddChild
(
root_node_
);
root_node_
.
SetLabel
(
"Flutter::LayerTree"
);
session_
.
Present
();
}
FuchsiaExternalViewEmbedder
::~
FuchsiaExternalViewEmbedder
()
=
default
;
SkCanvas
*
FuchsiaExternalViewEmbedder
::
GetRootCanvas
()
{
auto
found
=
frame_layers_
.
find
(
kRootLayerId
);
if
(
found
==
frame_layers_
.
end
())
{
FML_DLOG
(
WARNING
)
<<
"No root canvas could be found. This is extremely unlikely and "
"indicates that the external view embedder did not receive the "
"notification to begin the frame."
;
return
nullptr
;
}
return
found
->
second
.
canvas_spy
->
GetSpyingCanvas
();
}
std
::
vector
<
SkCanvas
*>
FuchsiaExternalViewEmbedder
::
GetCurrentCanvases
()
{
std
::
vector
<
SkCanvas
*>
canvases
;
for
(
const
auto
&
layer
:
frame_layers_
)
{
// This method (for legacy reasons) expects non-root current canvases.
if
(
layer
.
first
.
has_value
())
{
canvases
.
push_back
(
layer
.
second
.
canvas_spy
->
GetSpyingCanvas
());
}
}
return
canvases
;
}
void
FuchsiaExternalViewEmbedder
::
PrerollCompositeEmbeddedView
(
int
view_id
,
std
::
unique_ptr
<
flutter
::
EmbeddedViewParams
>
params
)
{
zx_handle_t
handle
=
static_cast
<
zx_handle_t
>
(
view_id
);
FML_DCHECK
(
frame_layers_
.
count
(
handle
)
==
0
);
frame_layers_
.
emplace
(
std
::
make_pair
(
EmbedderLayerId
{
handle
},
EmbedderLayer
(
frame_size_
,
*
params
)));
frame_composition_order_
.
push_back
(
handle
);
}
SkCanvas
*
FuchsiaExternalViewEmbedder
::
CompositeEmbeddedView
(
int
view_id
)
{
zx_handle_t
handle
=
static_cast
<
zx_handle_t
>
(
view_id
);
auto
found
=
frame_layers_
.
find
(
handle
);
FML_DCHECK
(
found
!=
frame_layers_
.
end
());
return
found
->
second
.
canvas_spy
->
GetSpyingCanvas
();
}
flutter
::
PostPrerollResult
FuchsiaExternalViewEmbedder
::
PostPrerollAction
(
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
{
return
flutter
::
PostPrerollResult
::
kSuccess
;
}
void
FuchsiaExternalViewEmbedder
::
BeginFrame
(
SkISize
frame_size
,
GrDirectContext
*
context
,
double
device_pixel_ratio
,
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
{
TRACE_EVENT0
(
"flutter"
,
"FuchsiaExternalViewEmbedder::BeginFrame"
);
// Reset for new frame.
Reset
();
frame_size_
=
frame_size
;
frame_dpr_
=
device_pixel_ratio
;
// Create the root layer.
frame_layers_
.
emplace
(
std
::
make_pair
(
kRootLayerId
,
EmbedderLayer
(
frame_size
,
std
::
nullopt
)));
frame_composition_order_
.
push_back
(
kRootLayerId
);
}
void
FuchsiaExternalViewEmbedder
::
EndFrame
(
bool
should_resubmit_frame
,
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
{
TRACE_EVENT0
(
"flutter"
,
"FuchsiaExternalViewEmbedder::EndFrame"
);
}
void
FuchsiaExternalViewEmbedder
::
SubmitFrame
(
GrDirectContext
*
context
,
std
::
unique_ptr
<
flutter
::
SurfaceFrame
>
frame
)
{
TRACE_EVENT0
(
"flutter"
,
"FuchsiaExternalViewEmbedder::SubmitFrame"
);
std
::
vector
<
std
::
unique_ptr
<
SurfaceProducerSurface
>>
frame_surfaces
;
std
::
unordered_map
<
EmbedderLayerId
,
size_t
>
frame_surface_indices
;
// Create surfaces for the frame and associate them with layer IDs.
{
TRACE_EVENT0
(
"flutter"
,
"CreateSurfaces"
);
for
(
const
auto
&
layer
:
frame_layers_
)
{
if
(
!
layer
.
second
.
canvas_spy
->
DidDrawIntoCanvas
())
{
continue
;
}
auto
surface
=
surface_producer_
.
ProduceSurface
(
layer
.
second
.
surface_size
);
FML_DCHECK
(
surface
)
<<
"Embedder did not return a valid render target of size ("
<<
layer
.
second
.
surface_size
.
width
()
<<
", "
<<
layer
.
second
.
surface_size
.
height
()
<<
")"
;
frame_surface_indices
.
emplace
(
std
::
make_pair
(
layer
.
first
,
frame_surfaces
.
size
()));
frame_surfaces
.
emplace_back
(
std
::
move
(
surface
));
}
}
// Submit layers and platform views to Scenic in composition order.
{
TRACE_EVENT0
(
"flutter"
,
"SubmitLayers"
);
std
::
unordered_map
<
uint64_t
,
size_t
>
scenic_rect_indices
;
size_t
scenic_layer_index
=
0
;
float
embedded_views_height
=
0.0
f
;
// First re-scale everything according to the DPR.
const
float
inv_dpr
=
1.0
f
/
frame_dpr_
;
root_node_
.
SetScale
(
inv_dpr
,
inv_dpr
,
1.0
f
);
for
(
const
auto
&
layer_id
:
frame_composition_order_
)
{
const
auto
&
layer
=
frame_layers_
.
find
(
layer_id
);
FML_DCHECK
(
layer
!=
frame_layers_
.
end
());
// Draw the PlatformView associated with each layer first.
if
(
layer_id
.
has_value
())
{
FML_DCHECK
(
layer
->
second
.
embedded_view_params
.
has_value
());
auto
&
view_params
=
layer
->
second
.
embedded_view_params
.
value
();
// Compute offset and size for the platform view.
SkPoint
view_offset
=
SkPoint
::
Make
(
view_params
.
finalBoundingRect
().
left
(),
view_params
.
finalBoundingRect
().
top
());
SkSize
view_size
=
SkSize
::
Make
(
view_params
.
finalBoundingRect
().
width
(),
view_params
.
finalBoundingRect
().
height
());
// Compute opacity for the platform view.
float
view_opacity
=
1.0
f
;
for
(
auto
i
=
view_params
.
mutatorsStack
().
Bottom
();
i
!=
view_params
.
mutatorsStack
().
Top
();
++
i
)
{
const
auto
&
mutator
=
*
i
;
switch
(
mutator
->
GetType
())
{
case
flutter
::
MutatorType
::
opacity
:
{
view_opacity
*=
std
::
clamp
(
mutator
->
GetAlphaFloat
(),
0.0
f
,
1.0
f
);
}
break
;
default:
{
break
;
}
}
}
auto
found
=
scenic_views_
.
find
(
layer_id
.
value
());
FML_DCHECK
(
found
!=
scenic_views_
.
end
());
auto
&
view_holder
=
found
->
second
;
// Set opacity.
if
(
view_opacity
!=
view_holder
.
opacity
)
{
view_holder
.
opacity_node
.
SetOpacity
(
view_opacity
);
view_holder
.
opacity
=
view_opacity
;
}
// Set offset and elevation.
const
float
view_elevation
=
kScenicZElevationBetweenLayers
*
scenic_layer_index
+
embedded_views_height
;
if
(
view_offset
!=
view_holder
.
offset
||
view_elevation
!=
view_holder
.
elevation
)
{
view_holder
.
entity_node
.
SetTranslation
(
view_offset
.
fX
,
view_offset
.
fY
,
-
view_elevation
);
view_holder
.
elevation
=
view_elevation
;
}
// Set HitTestBehavior.
if
(
view_holder
.
pending_hit_testable
!=
view_holder
.
hit_testable
)
{
view_holder
.
entity_node
.
SetHitTestBehavior
(
view_holder
.
pending_hit_testable
?
fuchsia
::
ui
::
gfx
::
HitTestBehavior
::
kDefault
:
fuchsia
::
ui
::
gfx
::
HitTestBehavior
::
kSuppress
);
view_holder
.
hit_testable
=
view_holder
.
pending_hit_testable
;
}
// Set size and focusable.
//
// Scenic rejects `SetViewProperties` calls with a zero size.
if
(
!
view_size
.
isEmpty
()
&&
(
view_size
!=
view_holder
.
size
||
view_holder
.
pending_focusable
!=
view_holder
.
focusable
))
{
view_holder
.
view_holder
.
SetViewProperties
({
.
bounding_box
=
{
.
min
=
{.
x
=
0.
f
,
.
y
=
0.
f
,
.
z
=
-
1000.
f
},
.
max
=
{.
x
=
view_size
.
width
(),
.
y
=
view_size
.
height
(),
.
z
=
0.
f
},
},
.
inset_from_min
=
{.
x
=
0.
f
,
.
y
=
0.
f
,
.
z
=
0.
f
},
.
inset_from_max
=
{.
x
=
0.
f
,
.
y
=
0.
f
,
.
z
=
0.
f
},
.
focus_change
=
view_holder
.
pending_focusable
,
});
view_holder
.
size
=
view_size
;
view_holder
.
focusable
=
view_holder
.
pending_focusable
;
}
// Attach the ScenicView to the main scene graph.
root_node_
.
AddChild
(
view_holder
.
opacity_node
);
// Account for the ScenicView's height when positioning the next layer.
embedded_views_height
+=
kScenicZElevationForPlatformView
;
}
if
(
layer
->
second
.
canvas_spy
->
DidDrawIntoCanvas
())
{
const
auto
&
surface_index
=
frame_surface_indices
.
find
(
layer_id
);
FML_DCHECK
(
surface_index
!=
frame_surface_indices
.
end
());
scenic
::
Image
*
surface_image
=
frame_surfaces
[
surface_index
->
second
]
->
GetImage
();
// Create a new layer if needed for the surface.
FML_DCHECK
(
scenic_layer_index
<=
scenic_layers_
.
size
());
if
(
scenic_layer_index
==
scenic_layers_
.
size
())
{
ScenicLayer
new_layer
{
.
shape_node
=
scenic
::
ShapeNode
(
session_
.
get
()),
.
material
=
scenic
::
Material
(
session_
.
get
()),
};
new_layer
.
shape_node
.
SetMaterial
(
new_layer
.
material
);
scenic_layers_
.
emplace_back
(
std
::
move
(
new_layer
));
}
// Compute a hash and index for the rect.
const
uint64_t
rect_hash
=
(
static_cast
<
uint64_t
>
(
layer
->
second
.
surface_size
.
width
())
<<
32
)
+
layer
->
second
.
surface_size
.
height
();
size_t
rect_index
=
0
;
auto
found_index
=
scenic_rect_indices
.
find
(
rect_hash
);
if
(
found_index
==
scenic_rect_indices
.
end
())
{
scenic_rect_indices
.
emplace
(
std
::
make_pair
(
rect_hash
,
0
));
}
else
{
rect_index
=
found_index
->
second
+
1
;
scenic_rect_indices
[
rect_hash
]
=
rect_index
;
}
// Create a new rect if needed for the surface.
auto
found_rects
=
scenic_rects_
.
find
(
rect_hash
);
if
(
found_rects
==
scenic_rects_
.
end
())
{
auto
[
emplaced_rects
,
success
]
=
scenic_rects_
.
emplace
(
std
::
make_pair
(
rect_hash
,
std
::
vector
<
scenic
::
Rectangle
>
()));
FML_DCHECK
(
success
);
found_rects
=
std
::
move
(
emplaced_rects
);
}
FML_DCHECK
(
rect_index
<=
found_rects
->
second
.
size
());
if
(
rect_index
==
found_rects
->
second
.
size
())
{
found_rects
->
second
.
emplace_back
(
scenic
::
Rectangle
(
session_
.
get
(),
layer
->
second
.
surface_size
.
width
(),
layer
->
second
.
surface_size
.
height
()));
}
// Set layer shape and texture.
// Scenic currently lacks an API to enable rendering of alpha channel;
// Flutter Embedder also lacks an API to detect if a layer has alpha or
// not. Alpha channels are only rendered if there is a OpacityNode
// higher in the tree with opacity != 1. For now, always assume t he
// layer has alpha and clamp to a infinitesimally smaller value than 1.
//
// This does not cause visual problems in practice, but probably has
// performance implications.
auto
&
scenic_layer
=
scenic_layers_
[
scenic_layer_index
];
auto
&
scenic_rect
=
found_rects
->
second
[
rect_index
];
const
float
layer_elevation
=
kScenicZElevationBetweenLayers
*
scenic_layer_index
+
embedded_views_height
;
scenic_layer
.
shape_node
.
SetLabel
(
"Flutter::Layer"
);
scenic_layer
.
shape_node
.
SetShape
(
scenic_rect
);
scenic_layer
.
shape_node
.
SetTranslation
(
layer
->
second
.
surface_size
.
width
()
*
0.5
f
,
layer
->
second
.
surface_size
.
height
()
*
0.5
f
,
-
layer_elevation
);
scenic_layer
.
material
.
SetColor
(
SK_AlphaOPAQUE
,
SK_AlphaOPAQUE
,
SK_AlphaOPAQUE
,
SK_AlphaOPAQUE
-
1
);
scenic_layer
.
material
.
SetTexture
(
*
surface_image
);
// Attach the ScenicLayer to the main scene graph.
root_node_
.
AddChild
(
scenic_layer
.
shape_node
);
// Account for the ScenicLayer's height when positioning the next layer.
scenic_layer_index
++
;
}
}
}
// Present the session to Scenic, along with surface acquire/release fencess.
{
TRACE_EVENT0
(
"flutter"
,
"SessionPresent"
);
session_
.
Present
();
}
// Render the recorded SkPictures into the surfaces.
{
TRACE_EVENT0
(
"flutter"
,
"RasterizeSurfaces"
);
for
(
const
auto
&
surface_index
:
frame_surface_indices
)
{
TRACE_EVENT0
(
"flutter"
,
"RasterizeSurface"
);
const
auto
&
layer
=
frame_layers_
.
find
(
surface_index
.
first
);
FML_DCHECK
(
layer
!=
frame_layers_
.
end
());
sk_sp
<
SkPicture
>
picture
=
layer
->
second
.
recorder
->
finishRecordingAsPicture
();
FML_DCHECK
(
picture
);
sk_sp
<
SkSurface
>
sk_surface
=
frame_surfaces
[
surface_index
.
second
]
->
GetSkiaSurface
();
FML_DCHECK
(
sk_surface
);
FML_DCHECK
(
SkISize
::
Make
(
sk_surface
->
width
(),
sk_surface
->
height
())
==
frame_size_
);
SkCanvas
*
canvas
=
sk_surface
->
getCanvas
();
FML_DCHECK
(
canvas
);
canvas
->
setMatrix
(
SkMatrix
::
I
());
canvas
->
clear
(
SK_ColorTRANSPARENT
);
canvas
->
drawPicture
(
picture
);
canvas
->
flush
();
}
}
// Flush deferred Skia work and inform Scenic that render targets are ready.
{
TRACE_EVENT0
(
"flutter"
,
"PresentSurfaces"
);
surface_producer_
.
OnSurfacesPresented
(
std
::
move
(
frame_surfaces
));
}
// Submit the underlying render-backend-specific frame for processing.
frame
->
Submit
();
}
void
FuchsiaExternalViewEmbedder
::
EnableWireframe
(
bool
enable
)
{
session_
.
get
()
->
Enqueue
(
scenic
::
NewSetEnableDebugViewBoundsCmd
(
root_view_
.
id
(),
enable
));
session_
.
Present
();
}
void
FuchsiaExternalViewEmbedder
::
CreateView
(
int64_t
view_id
)
{
FML_DCHECK
(
scenic_views_
.
find
(
view_id
)
==
scenic_views_
.
end
());
ScenicView
new_view
=
{
.
opacity_node
=
scenic
::
OpacityNodeHACK
(
session_
.
get
()),
.
entity_node
=
scenic
::
EntityNode
(
session_
.
get
()),
.
view_holder
=
scenic
::
ViewHolder
(
session_
.
get
(),
scenic
::
ToViewHolderToken
(
zx
::
eventpair
((
zx_handle_t
)
view_id
)),
"Flutter::PlatformView"
),
};
new_view
.
opacity_node
.
SetLabel
(
"flutter::PlatformView::OpacityMutator"
);
new_view
.
entity_node
.
SetLabel
(
"flutter::PlatformView::TransformMutator"
);
new_view
.
opacity_node
.
AddChild
(
new_view
.
entity_node
);
new_view
.
entity_node
.
Attach
(
new_view
.
view_holder
);
new_view
.
entity_node
.
SetTranslation
(
0.
f
,
0.
f
,
-
kScenicZElevationBetweenLayers
);
scenic_views_
.
emplace
(
std
::
make_pair
(
view_id
,
std
::
move
(
new_view
)));
}
void
FuchsiaExternalViewEmbedder
::
DestroyView
(
int64_t
view_id
)
{
size_t
erased
=
scenic_views_
.
erase
(
view_id
);
FML_DCHECK
(
erased
==
1
);
}
void
FuchsiaExternalViewEmbedder
::
SetViewProperties
(
int64_t
view_id
,
bool
hit_testable
,
bool
focusable
)
{
auto
found
=
scenic_views_
.
find
(
view_id
);
FML_DCHECK
(
found
!=
scenic_views_
.
end
());
auto
&
view_holder
=
found
->
second
;
view_holder
.
pending_hit_testable
=
hit_testable
;
view_holder
.
pending_focusable
=
focusable
;
}
void
FuchsiaExternalViewEmbedder
::
Reset
()
{
frame_layers_
.
clear
();
frame_composition_order_
.
clear
();
frame_size_
=
SkISize
::
Make
(
0
,
0
);
frame_dpr_
=
1.
f
;
// Detach the root node to prepare for the next frame.
session_
.
get
()
->
Enqueue
(
scenic
::
NewDetachChildrenCmd
(
root_node_
.
id
()));
// Clear images on all layers so they aren't cached unnecesarily.
for
(
auto
&
layer
:
scenic_layers_
)
{
layer
.
material
.
SetTexture
(
0
);
}
}
}
// namespace flutter_runner
shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h
0 → 100644
浏览文件 @
07e2520d
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_FUCHSIA_EXTERNAL_VIEW_EMBEDDER_H_
#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_FUCHSIA_EXTERNAL_VIEW_EMBEDDER_H_
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <cstdint> // For uint32_t & uint64_t
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "flutter/flow/embedded_views.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/shell/common/canvas_spy.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkSize.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "session_connection.h"
#include "vulkan_surface_producer.h"
namespace
flutter_runner
{
// This class orchestrates interaction with the Scenic compositor on Fuchsia. It
// ensures that flutter content and platform view content are both rendered
// correctly in a unified scene.
class
FuchsiaExternalViewEmbedder
final
:
public
flutter
::
ExternalViewEmbedder
{
public:
FuchsiaExternalViewEmbedder
(
std
::
string
debug_label
,
fuchsia
::
ui
::
views
::
ViewToken
view_token
,
scenic
::
ViewRefPair
view_ref_pair
,
SessionConnection
&
session
,
VulkanSurfaceProducer
&
surface_producer
);
~
FuchsiaExternalViewEmbedder
();
// |ExternalViewEmbedder|
SkCanvas
*
GetRootCanvas
()
override
;
// |ExternalViewEmbedder|
std
::
vector
<
SkCanvas
*>
GetCurrentCanvases
()
override
;
// |ExternalViewEmbedder|
void
PrerollCompositeEmbeddedView
(
int
view_id
,
std
::
unique_ptr
<
flutter
::
EmbeddedViewParams
>
params
)
override
;
// |ExternalViewEmbedder|
SkCanvas
*
CompositeEmbeddedView
(
int
view_id
)
override
;
// |ExternalViewEmbedder|
flutter
::
PostPrerollResult
PostPrerollAction
(
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
override
;
// |ExternalViewEmbedder|
void
BeginFrame
(
SkISize
frame_size
,
GrDirectContext
*
context
,
double
device_pixel_ratio
,
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
override
;
// |ExternalViewEmbedder|
void
EndFrame
(
bool
should_resubmit_frame
,
fml
::
RefPtr
<
fml
::
RasterThreadMerger
>
raster_thread_merger
)
override
;
// |ExternalViewEmbedder|
void
SubmitFrame
(
GrDirectContext
*
context
,
std
::
unique_ptr
<
flutter
::
SurfaceFrame
>
frame
)
override
;
// |ExternalViewEmbedder|
void
CancelFrame
()
override
{
Reset
();
}
// |ExternalViewEmbedder|
bool
SupportsDynamicThreadMerging
()
override
{
return
false
;
}
// View manipulation.
// |SetViewProperties| doesn't manipulate the view directly -- it sets
// prending properties for the next |UpdateView| call.
void
EnableWireframe
(
bool
enable
);
void
CreateView
(
int64_t
view_id
);
void
DestroyView
(
int64_t
view_id
);
void
SetViewProperties
(
int64_t
view_id
,
bool
hit_testable
,
bool
focusable
);
private:
// Reset state for a new frame.
void
Reset
();
struct
EmbedderLayer
{
EmbedderLayer
(
const
SkISize
&
frame_size
,
std
::
optional
<
flutter
::
EmbeddedViewParams
>
view_params
)
:
embedded_view_params
(
std
::
move
(
view_params
)),
recorder
(
std
::
make_unique
<
SkPictureRecorder
>
()),
canvas_spy
(
std
::
make_unique
<
flutter
::
CanvasSpy
>
(
recorder
->
beginRecording
(
frame_size
.
width
(),
frame_size
.
height
()))),
surface_size
(
frame_size
)
{}
std
::
optional
<
flutter
::
EmbeddedViewParams
>
embedded_view_params
;
std
::
unique_ptr
<
SkPictureRecorder
>
recorder
;
std
::
unique_ptr
<
flutter
::
CanvasSpy
>
canvas_spy
;
SkISize
surface_size
;
};
struct
ScenicView
{
scenic
::
OpacityNodeHACK
opacity_node
;
scenic
::
EntityNode
entity_node
;
scenic
::
ViewHolder
view_holder
;
SkPoint
offset
=
SkPoint
::
Make
(
0.
f
,
0.
f
);
SkSize
size
=
SkSize
::
MakeEmpty
();
float
elevation
=
0.
f
;
float
opacity
=
1.
f
;
bool
hit_testable
=
false
;
bool
focusable
=
false
;
bool
pending_hit_testable
=
false
;
bool
pending_focusable
=
false
;
};
struct
ScenicLayer
{
scenic
::
ShapeNode
shape_node
;
scenic
::
Material
material
;
};
using
EmbedderLayerId
=
std
::
optional
<
uint32_t
>
;
constexpr
static
EmbedderLayerId
kRootLayerId
=
EmbedderLayerId
{};
SessionConnection
&
session_
;
VulkanSurfaceProducer
&
surface_producer_
;
scenic
::
View
root_view_
;
scenic
::
EntityNode
metrics_node_
;
scenic
::
EntityNode
root_node_
;
std
::
unordered_map
<
uint64_t
,
std
::
vector
<
scenic
::
Rectangle
>>
scenic_rects_
;
std
::
unordered_map
<
int64_t
,
ScenicView
>
scenic_views_
;
std
::
vector
<
ScenicLayer
>
scenic_layers_
;
std
::
unordered_map
<
EmbedderLayerId
,
EmbedderLayer
>
frame_layers_
;
std
::
vector
<
EmbedderLayerId
>
frame_composition_order_
;
SkISize
frame_size_
=
SkISize
::
Make
(
0
,
0
);
float
frame_dpr_
=
1.
f
;
FML_DISALLOW_COPY_AND_ASSIGN
(
FuchsiaExternalViewEmbedder
);
};
}
// namespace flutter_runner
#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_FUCHSIA_EXTERNAL_VIEW_EMBEDDER_H_
shell/platform/fuchsia/flutter/platform_view.cc
浏览文件 @
07e2520d
...
...
@@ -10,10 +10,8 @@
#include <sstream>
#include "flutter/fml/logging.h"
#include "flutter/lib/ui/compositing/scene_host.h"
#include "flutter/lib/ui/window/pointer_data.h"
#include "flutter/lib/ui/window/window.h"
#include "flutter_runner_product_configuration.h"
#include "logging.h"
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
...
...
@@ -21,6 +19,10 @@
#include "runtime/dart/utils/inlines.h"
#include "vsync_waiter.h"
#if defined(LEGACY_FUCHSIA_EMBEDDER)
#include "flutter/lib/ui/compositing/scene_host.h"
#endif
namespace
flutter_runner
{
static
constexpr
char
kFlutterPlatformChannel
[]
=
"flutter/platform"
;
...
...
@@ -60,10 +62,9 @@ PlatformView::PlatformView(
OnCreateView
on_create_view_callback
,
OnUpdateView
on_update_view_callback
,
OnDestroyView
on_destroy_view_callback
,
OnGetViewEmbedder
on_get_view_embedder_callback
,
OnGetGrContext
on_get_gr_context_callback
,
zx_handle_t
vsync_event_handle
,
FlutterRunnerProductConfiguration
product_config
)
OnCreateSurface
on_create_surface_callback
,
fml
::
TimeDelta
vsync_offset
,
zx_handle_t
vsync_event_handle
)
:
flutter
::
PlatformView
(
delegate
,
std
::
move
(
task_runners
)),
debug_label_
(
std
::
move
(
debug_label
)),
view_ref_
(
std
::
move
(
view_ref
)),
...
...
@@ -75,11 +76,10 @@ PlatformView::PlatformView(
on_create_view_callback_
(
std
::
move
(
on_create_view_callback
)),
on_update_view_callback_
(
std
::
move
(
on_update_view_callback
)),
on_destroy_view_callback_
(
std
::
move
(
on_destroy_view_callback
)),
on_get_view_embedder_callback_
(
std
::
move
(
on_get_view_embedder_callback
)),
on_get_gr_context_callback_
(
std
::
move
(
on_get_gr_context_callback
)),
on_create_surface_callback_
(
std
::
move
(
on_create_surface_callback
)),
ime_client_
(
this
),
vsync_
event_handle_
(
vsync_event_handle
),
product_config_
(
product_config
)
{
vsync_
offset_
(
std
::
move
(
vsync_offset
)
),
vsync_event_handle_
(
vsync_event_handle
)
{
// Register all error handlers.
SetInterfaceErrorHandler
(
session_listener_binding_
,
"SessionListener"
);
SetInterfaceErrorHandler
(
ime_
,
"Input Method Editor"
);
...
...
@@ -248,6 +248,7 @@ void PlatformView::OnScenicEvent(
}
break
;
}
#if defined(LEGACY_FUCHSIA_EMBEDDER)
case
fuchsia
::
ui
::
gfx
::
Event
::
Tag
::
kViewConnected
:
OnChildViewConnected
(
event
.
gfx
().
view_connected
().
view_holder_id
);
break
;
...
...
@@ -260,6 +261,7 @@ void PlatformView::OnScenicEvent(
event
.
gfx
().
view_state_changed
().
view_holder_id
,
event
.
gfx
().
view_state_changed
().
state
.
is_rendering
);
break
;
#endif
case
fuchsia
::
ui
::
gfx
::
Event
::
Tag
::
Invalid
:
FML_DCHECK
(
false
)
<<
"Flutter PlatformView::OnScenicEvent: Got "
"an invalid GFX event."
;
...
...
@@ -317,6 +319,7 @@ void PlatformView::OnScenicEvent(
}
}
#if defined(LEGACY_FUCHSIA_EMBEDDER)
void
PlatformView
::
OnChildViewConnected
(
scenic
::
ResourceId
view_holder_id
)
{
task_runners_
.
GetUITaskRunner
()
->
PostTask
([
view_holder_id
]()
{
flutter
::
SceneHost
::
OnViewConnected
(
view_holder_id
);
...
...
@@ -335,6 +338,7 @@ void PlatformView::OnChildViewStateChanged(scenic::ResourceId view_holder_id,
flutter
::
SceneHost
::
OnViewStateChanged
(
view_holder_id
,
state
);
});
}
#endif
static
flutter
::
PointerData
::
Change
GetChangeFromPointerEventPhase
(
fuchsia
::
ui
::
input
::
PointerEventPhase
phase
)
{
...
...
@@ -513,21 +517,12 @@ void PlatformView::DeactivateIme() {
// |flutter::PlatformView|
std
::
unique_ptr
<
flutter
::
VsyncWaiter
>
PlatformView
::
CreateVSyncWaiter
()
{
return
std
::
make_unique
<
flutter_runner
::
VsyncWaiter
>
(
debug_label_
,
vsync_event_handle_
,
task_runners_
,
product_config_
.
get_vsync_offset
());
debug_label_
,
vsync_event_handle_
,
task_runners_
,
vsync_offset_
);
}
// |flutter::PlatformView|
std
::
unique_ptr
<
flutter
::
Surface
>
PlatformView
::
CreateRenderingSurface
()
{
// This platform does not repeatly lose and gain a surface connection. So the
// surface is setup once during platform view setup and returned to the
// shell on the initial (and only) |NotifyCreated| call.
auto
view_embedder
=
on_get_view_embedder_callback_
?
on_get_view_embedder_callback_
()
:
nullptr
;
auto
gr_context
=
on_get_gr_context_callback_
?
on_get_gr_context_callback_
()
:
nullptr
;
return
std
::
make_unique
<
Surface
>
(
debug_label_
,
view_embedder
,
gr_context
);
return
on_create_surface_callback_
?
on_create_surface_callback_
()
:
nullptr
;
}
// |flutter::PlatformView|
...
...
shell/platform/fuchsia/flutter/platform_view.h
浏览文件 @
07e2520d
...
...
@@ -7,18 +7,21 @@
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fit/function.h>
#include <map>
#include <set>
#include "flutter/fml/macros.h"
#include "flutter/fml/time/time_delta.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
#include "flutter_runner_product_configuration.h"
#include "lib/fidl/cpp/binding.h"
#include "lib/ui/scenic/cpp/id.h"
#include "surface.h"
#include "accessibility_bridge.h"
#if defined(LEGACY_FUCHSIA_EMBEDDER)
#include <lib/ui/scenic/cpp/id.h> // nogncheck
#endif
namespace
flutter_runner
{
...
...
@@ -26,8 +29,7 @@ using OnEnableWireframe = fit::function<void(bool)>;
using
OnCreateView
=
fit
::
function
<
void
(
int64_t
,
bool
,
bool
)
>
;
using
OnUpdateView
=
fit
::
function
<
void
(
int64_t
,
bool
,
bool
)
>
;
using
OnDestroyView
=
fit
::
function
<
void
(
int64_t
)
>
;
using
OnGetViewEmbedder
=
fit
::
function
<
flutter
::
ExternalViewEmbedder
*
()
>
;
using
OnGetGrContext
=
fit
::
function
<
GrDirectContext
*
()
>
;
using
OnCreateSurface
=
fit
::
function
<
std
::
unique_ptr
<
flutter
::
Surface
>
()
>
;
// The per engine component residing on the platform thread is responsible for
// all platform specific integrations.
...
...
@@ -55,10 +57,9 @@ class PlatformView final : public flutter::PlatformView,
OnCreateView
on_create_view_callback
,
OnUpdateView
on_update_view_callback
,
OnDestroyView
on_destroy_view_callback
,
OnGetViewEmbedder
on_get_view_embedder_callback
,
OnGetGrContext
on_get_gr_context_callback
,
zx_handle_t
vsync_event_handle
,
FlutterRunnerProductConfiguration
product_config
);
OnCreateSurface
on_create_surface_callback
,
fml
::
TimeDelta
vsync_offset
,
zx_handle_t
vsync_event_handle
);
~
PlatformView
();
...
...
@@ -87,8 +88,7 @@ class PlatformView final : public flutter::PlatformView,
OnCreateView
on_create_view_callback_
;
OnUpdateView
on_update_view_callback_
;
OnDestroyView
on_destroy_view_callback_
;
OnGetViewEmbedder
on_get_view_embedder_callback_
;
OnGetGrContext
on_get_gr_context_callback_
;
OnCreateSurface
on_create_surface_callback_
;
int
current_text_input_client_
=
0
;
fidl
::
Binding
<
fuchsia
::
ui
::
input
::
InputMethodEditorClient
>
ime_client_
;
...
...
@@ -112,14 +112,14 @@ class PlatformView final : public flutter::PlatformView,
// such. Notifying via logs multiple times results in log-spam. See:
// https://github.com/flutter/flutter/issues/55966
std
::
set
<
std
::
string
/* channel */
>
unregistered_channels_
;
fml
::
TimeDelta
vsync_offset_
;
zx_handle_t
vsync_event_handle_
=
0
;
float
view_width_
=
0.0
f
;
// Width in logical pixels.
float
view_height_
=
0.0
f
;
// Height in logical pixels.
float
view_pixel_ratio_
=
0.0
f
;
// Logical / physical pixel ratio.
FlutterRunnerProductConfiguration
product_config_
;
void
RegisterPlatformMessageHandlers
();
// |fuchsia::ui::input::InputMethodEditorClient|
...
...
@@ -134,9 +134,11 @@ class PlatformView final : public flutter::PlatformView,
void
OnScenicError
(
std
::
string
error
)
override
;
void
OnScenicEvent
(
std
::
vector
<
fuchsia
::
ui
::
scenic
::
Event
>
events
)
override
;
#if defined(LEGACY_FUCHSIA_EMBEDDER)
void
OnChildViewConnected
(
scenic
::
ResourceId
view_holder_id
);
void
OnChildViewDisconnected
(
scenic
::
ResourceId
view_holder_id
);
void
OnChildViewStateChanged
(
scenic
::
ResourceId
view_holder_id
,
bool
state
);
#endif
bool
OnHandlePointerEvent
(
const
fuchsia
::
ui
::
input
::
PointerEvent
&
pointer
);
...
...
shell/platform/fuchsia/flutter/platform_view_unittest.cc
浏览文件 @
07e2520d
...
...
@@ -20,6 +20,7 @@
#include "flutter/lib/ui/window/window.h"
#include "gtest/gtest.h"
#include "surface.h"
#include "task_runner_adapter.h"
namespace
flutter_runner_test
::
flutter_runner_a11y_test
{
...
...
@@ -116,10 +117,7 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
bool
SemanticsEnabled
()
const
{
return
semantics_enabled_
;
}
int32_t
SemanticsFeatures
()
const
{
return
semantics_features_
;
}
flutter
::
ExternalViewEmbedder
*
get_view_embedder
()
{
return
surface_
->
GetExternalViewEmbedder
();
}
GrDirectContext
*
get_gr_context
()
{
return
surface_
->
GetContext
();
}
flutter
::
Surface
*
surface
()
const
{
return
surface_
.
get
();
}
private:
std
::
unique_ptr
<
flutter
::
Surface
>
surface_
;
...
...
@@ -162,18 +160,17 @@ TEST_F(PlatformViewTests, ChangesAccessibilitySettings) {
std
::
move
(
view_ref
),
// view_ref
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
RunLoopUntilIdle
();
...
...
@@ -227,10 +224,9 @@ TEST_F(PlatformViewTests, EnableWireframeTest) {
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
// Cast platform_view to its base view so we can have access to the public
...
...
@@ -287,18 +283,17 @@ TEST_F(PlatformViewTests, CreateViewTest) {
std
::
move
(
view_ref
),
// view_refs
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
CreateViewCallback
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
CreateViewCallback
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
// Cast platform_view to its base view so we can have access to the public
...
...
@@ -357,18 +352,17 @@ TEST_F(PlatformViewTests, UpdateViewTest) {
std
::
move
(
view_ref
),
// view_refs
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
UpdateViewCallback
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
UpdateViewCallback
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
// Cast platform_view to its base view so we can have access to the public
...
...
@@ -427,18 +421,17 @@ TEST_F(PlatformViewTests, DestroyViewTest) {
std
::
move
(
view_ref
),
// view_refs
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
DestroyViewCallback
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
DestroyViewCallback
,
// on_destroy_view_callback,
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
// Cast platform_view to its base view so we can have access to the public
...
...
@@ -499,10 +492,9 @@ TEST_F(PlatformViewTests, RequestFocusTest) {
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_gr_context_callback,
nullptr
,
// on_get_view_embedder_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
// Cast platform_view to its base view so we can have access to the public
...
...
@@ -534,8 +526,8 @@ TEST_F(PlatformViewTests, RequestFocusTest) {
}
// Test to make sure that PlatformView correctly returns a Surface instance
// that can surface the
view_embedder provided from GetViewEmbedderCallback
.
TEST_F
(
PlatformViewTests
,
GetViewEmbedder
Test
)
{
// that can surface the
provided gr_context and view_embedder
.
TEST_F
(
PlatformViewTests
,
CreateSurface
Test
)
{
sys
::
testing
::
ServiceDirectoryProvider
services_provider
(
dispatcher
());
MockPlatformViewDelegate
delegate
;
zx
::
eventpair
a
,
b
;
...
...
@@ -552,63 +544,13 @@ TEST_F(PlatformViewTests, GetViewEmbedderTest) {
nullptr
// io
);
// Test get view embedder callback function.
MockExternalViewEmbedder
view_embedder
;
auto
GetViewEmbedderCallback
=
[
&
view_embedder
]()
{
return
&
view_embedder
;
};
auto
platform_view
=
flutter_runner
::
PlatformView
(
delegate
,
// delegate
"test_platform_view"
,
// label
std
::
move
(
view_ref
),
// view_refs
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
GetViewEmbedderCallback
,
// on_get_view_embedder_callback,
nullptr
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
);
RunLoopUntilIdle
();
platform_view
.
NotifyCreated
();
RunLoopUntilIdle
();
EXPECT_EQ
(
&
view_embedder
,
delegate
.
get_view_embedder
());
}
// Test to make sure that PlatformView correctly returns a Surface instance
// that can surface the GrContext provided from GetGrContextCallback.
TEST_F
(
PlatformViewTests
,
GetGrContextTest
)
{
sys
::
testing
::
ServiceDirectoryProvider
services_provider
(
dispatcher
());
MockPlatformViewDelegate
delegate
;
zx
::
eventpair
a
,
b
;
zx
::
eventpair
::
create
(
/* flags */
0u
,
&
a
,
&
b
);
auto
view_ref
=
fuchsia
::
ui
::
views
::
ViewRef
({
.
reference
=
std
::
move
(
a
),
});
flutter
::
TaskRunners
task_runners
=
flutter
::
TaskRunners
(
"test_runners"
,
// label
nullptr
,
// platform
flutter_runner
::
CreateFMLTaskRunner
(
async_get_default_dispatcher
()),
// raster
nullptr
,
// ui
nullptr
// io
);
// Test get GrContext callback function.
// Test create surface callback function.
sk_sp
<
GrDirectContext
>
gr_context
=
GrDirectContext
::
MakeMock
(
nullptr
,
GrContextOptions
());
auto
GetGrContextCallback
=
[
gr_context
=
gr_context
.
get
()]()
{
return
gr_context
;
MockExternalViewEmbedder
view_embedder
;
auto
CreateSurfaceCallback
=
[
&
view_embedder
,
gr_context
]()
{
return
std
::
make_unique
<
flutter_runner
::
Surface
>
(
"PlatformViewTest"
,
&
view_embedder
,
gr_context
.
get
());
};
auto
platform_view
=
flutter_runner
::
PlatformView
(
...
...
@@ -617,27 +559,24 @@ TEST_F(PlatformViewTests, GetGrContextTest) {
std
::
move
(
view_ref
),
// view_refs
std
::
move
(
task_runners
),
// task_runners
services_provider
.
service_directory
(),
// runner_services
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
nullptr
,
// on_get_view_embedder_callback,
GetGrContextCallback
,
// on_get_gr_context_callback,
0u
,
// vsync_event_handle
{}
// product_config
nullptr
,
// parent_environment_service_provider_handle
nullptr
,
// session_listener_request
nullptr
,
// focuser,
nullptr
,
// on_session_listener_error_callback
nullptr
,
// on_enable_wireframe_callback,
nullptr
,
// on_create_view_callback,
nullptr
,
// on_update_view_callback,
nullptr
,
// on_destroy_view_callback,
CreateSurfaceCallback
,
// on_create_surface_callback,
fml
::
TimeDelta
::
Zero
(),
// vsync_offset
ZX_HANDLE_INVALID
// vsync_event_handle
);
RunLoopUntilIdle
();
platform_view
.
NotifyCreated
();
RunLoopUntilIdle
();
EXPECT_EQ
(
gr_context
.
get
(),
delegate
.
get_gr_context
());
EXPECT_EQ
(
gr_context
.
get
(),
delegate
.
surface
()
->
GetContext
());
EXPECT_EQ
(
&
view_embedder
,
delegate
.
surface
()
->
GetExternalViewEmbedder
());
}
}
// namespace flutter_runner_test::flutter_runner_a11y_test
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录