Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qq_34031325
engine
提交
5526884e
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,发现更多精彩内容 >>
未验证
提交
5526884e
编写于
5月 11, 2019
作者:
C
Chinmay Garde
提交者:
GitHub
5月 11, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Wire up the Skia Metal backend on iOS. (#8936)
上级
5ad98642
变更
15
隐藏空白更改
内联
并排
Showing
15 changed file
with
352 addition
and
6 deletion
+352
-6
ci/licenses_golden/licenses_flutter
ci/licenses_golden/licenses_flutter
+4
-0
shell/common/BUILD.gn
shell/common/BUILD.gn
+1
-0
shell/config.gni
shell/config.gni
+1
-0
shell/gpu/BUILD.gn
shell/gpu/BUILD.gn
+9
-0
shell/gpu/gpu.gni
shell/gpu/gpu.gni
+6
-0
shell/gpu/gpu_surface_metal.h
shell/gpu/gpu_surface_metal.h
+50
-0
shell/gpu/gpu_surface_metal.mm
shell/gpu/gpu_surface_metal.mm
+145
-0
shell/platform/android/BUILD.gn
shell/platform/android/BUILD.gn
+1
-0
shell/platform/darwin/ios/BUILD.gn
shell/platform/darwin/ios/BUILD.gn
+14
-1
shell/platform/darwin/ios/framework/Source/FlutterView.mm
shell/platform/darwin/ios/framework/Source/FlutterView.mm
+27
-2
shell/platform/darwin/ios/ios_surface_gl.h
shell/platform/darwin/ios/ios_surface_gl.h
+3
-3
shell/platform/darwin/ios/ios_surface_metal.h
shell/platform/darwin/ios/ios_surface_metal.h
+44
-0
shell/platform/darwin/ios/ios_surface_metal.mm
shell/platform/darwin/ios/ios_surface_metal.mm
+34
-0
shell/platform/embedder/BUILD.gn
shell/platform/embedder/BUILD.gn
+1
-0
tools/gn
tools/gn
+12
-0
未找到文件。
ci/licenses_golden/licenses_flutter
浏览文件 @
5526884e
...
...
@@ -498,6 +498,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_gl.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_gl.h
FILE: ../../../flutter/shell/gpu/gpu_surface_gl_delegate.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_gl_delegate.h
FILE: ../../../flutter/shell/gpu/gpu_surface_metal.h
FILE: ../../../flutter/shell/gpu/gpu_surface_metal.mm
FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc
FILE: ../../../flutter/shell/gpu/gpu_surface_software.h
FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.cc
...
...
@@ -719,6 +721,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.h
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.mm
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.h
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.mm
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.h
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.mm
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.h
FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm
FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h
...
...
shell/common/BUILD.gn
浏览文件 @
5526884e
...
...
@@ -145,6 +145,7 @@ shell_gpu_configuration("shell_unittests_gpu_configuration") {
enable_software = true
enable_vulkan = false
enable_gl = false
enable_metal = false
}
test_fixtures("shell_unittests_fixtures") {
...
...
shell/config.gni
浏览文件 @
5526884e
...
...
@@ -4,4 +4,5 @@
declare_args() {
shell_enable_vulkan = false
shell_enable_metal = false
}
shell/gpu/BUILD.gn
浏览文件 @
5526884e
...
...
@@ -45,3 +45,12 @@ source_set("gpu_surface_vulkan") {
"$flutter_root/vulkan",
]
}
source_set("gpu_surface_metal") {
sources = [
"$gpu_dir/gpu_surface_metal.h",
"$gpu_dir/gpu_surface_metal.mm",
]
deps = gpu_common_deps + [ "//third_party/skia" ]
}
shell/gpu/gpu.gni
浏览文件 @
5526884e
...
...
@@ -11,6 +11,8 @@ template("shell_gpu_configuration") {
"Caller must declare if the Vulkan backend must be enabled.")
assert(defined(invoker.enable_gl),
"Caller must declare if the Open GL backend must be enabled.")
assert(defined(invoker.enable_metal),
"Caller must declare if the Metal backend must be enabled.")
group(target_name) {
public_deps = []
...
...
@@ -26,5 +28,9 @@ template("shell_gpu_configuration") {
if (invoker.enable_vulkan) {
public_deps += [ "$flutter_root/shell/gpu:gpu_surface_vulkan" ]
}
if (invoker.enable_metal) {
public_deps += [ "$flutter_root/shell/gpu:gpu_surface_metal" ]
}
}
}
shell/gpu/gpu_surface_metal.h
0 → 100644
浏览文件 @
5526884e
// 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_GPU_GPU_SURFACE_METAL_H_
#define FLUTTER_SHELL_GPU_GPU_SURFACE_METAL_H_
#include <Metal/Metal.h>
#include "flutter/fml/macros.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
#include "flutter/shell/common/surface.h"
#include "third_party/skia/include/gpu/GrContext.h"
@
class
CAMetalLayer
;
namespace
flutter
{
class
GPUSurfaceMetal
:
public
Surface
{
public:
GPUSurfaceMetal
(
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer
);
~
GPUSurfaceMetal
()
override
;
private:
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer_
;
sk_sp
<
GrContext
>
context_
;
fml
::
scoped_nsprotocol
<
id
<
MTLCommandQueue
>>
command_queue_
;
// |Surface|
bool
IsValid
()
override
;
// |Surface|
std
::
unique_ptr
<
SurfaceFrame
>
AcquireFrame
(
const
SkISize
&
size
)
override
;
// |Surface|
SkMatrix
GetRootTransformation
()
const
override
;
// |Surface|
GrContext
*
GetContext
()
override
;
// |Surface|
bool
MakeRenderContextCurrent
()
override
;
FML_DISALLOW_COPY_AND_ASSIGN
(
GPUSurfaceMetal
);
};
}
// namespace flutter
#endif // FLUTTER_SHELL_GPU_GPU_SURFACE_METAL_H_
shell/gpu/gpu_surface_metal.mm
0 → 100644
浏览文件 @
5526884e
// 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 "flutter/shell/gpu/gpu_surface_metal.h"
#include <QuartzCore/CAMetalLayer.h>
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
namespace
flutter
{
GPUSurfaceMetal
::
GPUSurfaceMetal
(
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer
)
:
layer_
(
std
::
move
(
layer
))
{
if
(
!
layer_
)
{
FML_LOG
(
ERROR
)
<<
"Could not create metal surface because of invalid layer."
;
return
;
}
layer
.
get
().
pixelFormat
=
MTLPixelFormatBGRA8Unorm
;
auto
metal_device
=
fml
::
scoped_nsprotocol
<
id
<
MTLDevice
>>
([
layer_
.
get
().
device
retain
]);
auto
metal_queue
=
fml
::
scoped_nsprotocol
<
id
<
MTLCommandQueue
>>
([
metal_device
newCommandQueue
]);
if
(
!
metal_device
||
!
metal_queue
)
{
FML_LOG
(
ERROR
)
<<
"Could not create metal device or queue."
;
return
;
}
command_queue_
=
metal_queue
;
// The context creation routine accepts arguments using transfer semantics.
auto
context
=
GrContext
::
MakeMetal
(
metal_device
.
release
(),
metal_queue
.
release
());
if
(
!
context
)
{
FML_LOG
(
ERROR
)
<<
"Could not create Skia metal context."
;
return
;
}
context_
=
context
;
}
GPUSurfaceMetal
::~
GPUSurfaceMetal
()
=
default
;
// |Surface|
bool
GPUSurfaceMetal
::
IsValid
()
{
return
layer_
&&
context_
&&
command_queue_
;
}
// |Surface|
std
::
unique_ptr
<
SurfaceFrame
>
GPUSurfaceMetal
::
AcquireFrame
(
const
SkISize
&
size
)
{
if
(
!
IsValid
())
{
FML_LOG
(
ERROR
)
<<
"Metal surface was invalid."
;
return
nullptr
;
}
if
(
size
.
isEmpty
())
{
FML_LOG
(
ERROR
)
<<
"Metal surface was asked for an empty frame."
;
return
nullptr
;
}
const
auto
bounds
=
layer_
.
get
().
bounds
.
size
;
if
(
bounds
.
width
<=
0.0
||
bounds
.
height
<=
0.0
)
{
FML_LOG
(
ERROR
)
<<
"Metal layer bounds were invalid."
;
return
nullptr
;
}
const
auto
scale
=
layer_
.
get
().
contentsScale
;
auto
next_drawable
=
fml
::
scoped_nsprotocol
<
id
<
CAMetalDrawable
>>
([[
layer_
nextDrawable
]
retain
]);
if
(
!
next_drawable
)
{
FML_LOG
(
ERROR
)
<<
"Could not acquire next metal drawable."
;
return
nullptr
;
}
auto
metal_texture
=
fml
::
scoped_nsprotocol
<
id
<
MTLTexture
>>
([
next_drawable
.
get
().
texture
retain
]);
if
(
!
metal_texture
)
{
FML_LOG
(
ERROR
)
<<
"Could not acquire metal texture from drawable."
;
return
nullptr
;
}
GrMtlTextureInfo
metal_texture_info
;
metal_texture_info
.
fTexture
=
metal_texture
.
get
();
GrBackendRenderTarget
metal_render_target
(
bounds
.
width
*
scale
,
// width
bounds
.
height
*
scale
,
// height
1
,
// sample count
metal_texture_info
// metal texture info
);
auto
command_buffer
=
fml
::
scoped_nsprotocol
<
id
<
MTLCommandBuffer
>>
([[
command_queue_
.
get
()
commandBuffer
]
retain
]);
SkSurface
::
RenderTargetReleaseProc
release_proc
=
[](
SkSurface
::
ReleaseContext
context
)
{
[
reinterpret_cast
<
id
>
(
context
)
release
];
};
auto
surface
=
SkSurface
::
MakeFromBackendRenderTarget
(
context_
.
get
(),
// context
metal_render_target
,
// backend render target
kTopLeft_GrSurfaceOrigin
,
// origin
kBGRA_8888_SkColorType
,
// color type
nullptr
,
// colorspace
nullptr
,
// surface properties
release_proc
,
// release proc
metal_texture
.
release
()
// release context (texture)
);
if
(
!
surface
)
{
FML_LOG
(
ERROR
)
<<
"Could not create the SkSurface from the metal texture."
;
return
nullptr
;
}
auto
submit_callback
=
[
drawable
=
next_drawable
,
command_buffer
](
const
SurfaceFrame
&
surface_frame
,
SkCanvas
*
canvas
)
->
bool
{
canvas
->
flush
();
[
command_buffer
.
get
()
presentDrawable
:
drawable
.
get
()];
[
command_buffer
.
get
()
commit
];
return
true
;
};
return
std
::
make_unique
<
SurfaceFrame
>
(
std
::
move
(
surface
),
submit_callback
);
}
// |Surface|
SkMatrix
GPUSurfaceMetal
::
GetRootTransformation
()
const
{
// This backend does not currently support root surface transformations. Just
// return identity.
SkMatrix
matrix
;
matrix
.
reset
();
return
matrix
;
}
// |Surface|
GrContext
*
GPUSurfaceMetal
::
GetContext
()
{
return
context_
.
get
();
}
// |Surface|
bool
GPUSurfaceMetal
::
MakeRenderContextCurrent
()
{
// This backend has no such concept.
return
true
;
}
}
// namespace flutter
shell/platform/android/BUILD.gn
浏览文件 @
5526884e
...
...
@@ -11,6 +11,7 @@ shell_gpu_configuration("android_gpu_configuration") {
enable_software = true
enable_vulkan = shell_enable_vulkan
enable_gl = true
enable_metal = false
}
shared_library("flutter_shell_native") {
...
...
shell/platform/darwin/ios/BUILD.gn
浏览文件 @
5526884e
...
...
@@ -14,6 +14,7 @@ shell_gpu_configuration("ios_gpu_configuration") {
enable_software = true
enable_gl = true
enable_vulkan = false
enable_metal = shell_enable_metal
}
# The headers that will be copied to the Flutter.framework and be accessed
...
...
@@ -100,6 +101,13 @@ shared_library("create_flutter_framework_dylib") {
"platform_view_ios.mm",
]
if (shell_enable_metal) {
sources += [
"ios_surface_metal.h",
"ios_surface_metal.mm",
]
}
sources += _flutter_framework_headers
deps = [
...
...
@@ -117,7 +125,11 @@ shared_library("create_flutter_framework_dylib") {
public_configs = [ "$flutter_root:config" ]
defines = [ "FLUTTER_FRAMEWORK" ]
defines = [ "FLUTTER_FRAMEWORK=1" ]
if (shell_enable_metal) {
defines += [ "FLUTTER_SHELL_ENABLE_METAL=1" ]
}
libs = [
"CoreMedia.framework",
...
...
@@ -241,6 +253,7 @@ copy("copy_license") {
shared_library("copy_and_verify_framework_headers") {
visibility = [ ":*" ]
include_dirs = [ "$_flutter_framework_headers_copy_dir" ]
sources = [
"framework/Source/FlutterUmbrellaImport.m",
]
...
...
shell/platform/darwin/ios/framework/Source/FlutterView.mm
浏览文件 @
5526884e
...
...
@@ -17,6 +17,10 @@
#include "flutter/shell/platform/darwin/ios/ios_surface_software.h"
#include "third_party/skia/include/utils/mac/SkCGUtils.h"
#if FLUTTER_SHELL_ENABLE_METAL
#include "flutter/shell/platform/darwin/ios/ios_surface_metal.h"
#endif // FLUTTER_SHELL_ENABLE_METAL
@implementation
FlutterView
id
<
FlutterViewEngineDelegate
>
_delegate
;
...
...
@@ -60,14 +64,26 @@ id<FlutterViewEngineDelegate> _delegate;
layer
.
rasterizationScale
=
screenScale
;
}
#if FLUTTER_SHELL_ENABLE_METAL
if
([
self
.
layer
isKindOfClass
:[
CAMetalLayer
class
]])
{
CGFloat
screenScale
=
[
UIScreen
mainScreen
].
scale
;
self
.
layer
.
contentsScale
=
screenScale
;
self
.
layer
.
rasterizationScale
=
screenScale
;
}
#endif // FLUTTER_SHELL_ENABLE_METAL
[
super
layoutSubviews
];
}
+
(
Class
)
layerClass
{
#if TARGET_IPHONE_SIMULATOR
return
[
CALayer
class
];
#else // TARGET_IPHONE_SIMULATOR
#else // TARGET_IPHONE_SIMULATOR
#if FLUTTER_SHELL_ENABLE_METAL
return
[
CAMetalLayer
class
];
#else // FLUTTER_SHELL_ENABLE_METAL
return
[
CAEAGLLayer
class
];
#endif // FLUTTER_SHELL_ENABLE_METAL
#endif // TARGET_IPHONE_SIMULATOR
}
...
...
@@ -87,7 +103,16 @@ id<FlutterViewEngineDelegate> _delegate;
}
return
std
::
make_unique
<
flutter
::
IOSSurfaceGL
>
(
context
,
std
::
move
(
eagl_layer
),
[
_delegate
platformViewsController
]);
}
else
{
}
#if FLUTTER_SHELL_ENABLE_METAL
else
if
([
self
.
layer
isKindOfClass
:[
CAMetalLayer
class
]])
{
return
std
::
make_unique
<
flutter
::
IOSSurfaceMetal
>
(
fml
::
scoped_nsobject
<
CAMetalLayer
>
(
reinterpret_cast
<
CAMetalLayer
*>
([
self
.
layer
retain
])),
[
_delegate
platformViewsController
]);
}
#endif // FLUTTER_SHELL_ENABLE_METAL
else
{
fml
::
scoped_nsobject
<
CALayer
>
layer
(
reinterpret_cast
<
CALayer
*>
([
self
.
layer
retain
]));
return
std
::
make_unique
<
flutter
::
IOSSurfaceSoftware
>
(
std
::
move
(
layer
),
[
_delegate
platformViewsController
]);
...
...
shell/platform/darwin/ios/ios_surface_gl.h
浏览文件 @
5526884e
...
...
@@ -16,9 +16,9 @@
namespace
flutter
{
class
IOSSurfaceGL
:
public
IOSSurface
,
public
GPUSurfaceGLDelegate
,
public
flutter
::
ExternalViewEmbedder
{
class
IOSSurfaceGL
final
:
public
IOSSurface
,
public
GPUSurfaceGLDelegate
,
public
flutter
::
ExternalViewEmbedder
{
public:
IOSSurfaceGL
(
std
::
shared_ptr
<
IOSGLContext
>
context
,
fml
::
scoped_nsobject
<
CAEAGLLayer
>
layer
,
...
...
shell/platform/darwin/ios/ios_surface_metal.h
0 → 100644
浏览文件 @
5526884e
// 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_DARWIN_IOS_IOS_SURFACE_METAL_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_METAL_H_
#include "flutter/fml/macros.h"
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
#include "flutter/shell/gpu/gpu_surface_metal.h"
#include "flutter/shell/platform/darwin/ios/ios_surface.h"
@
class
CAMetalLayer
;
namespace
flutter
{
class
IOSSurfaceMetal
final
:
public
IOSSurface
{
public:
IOSSurfaceMetal
(
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer
,
FlutterPlatformViewsController
*
platform_views_controller
);
~
IOSSurfaceMetal
()
override
;
private:
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer_
;
// |IOSSurface|
bool
IsValid
()
const
override
;
// |IOSSurface|
bool
ResourceContextMakeCurrent
()
override
;
// |IOSSurface|
void
UpdateStorageSizeIfNecessary
()
override
;
// |IOSSurface|
std
::
unique_ptr
<
Surface
>
CreateGPUSurface
()
override
;
FML_DISALLOW_COPY_AND_ASSIGN
(
IOSSurfaceMetal
);
};
}
// namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_METAL_H_
shell/platform/darwin/ios/ios_surface_metal.mm
0 → 100644
浏览文件 @
5526884e
// 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 "flutter/shell/platform/darwin/ios/ios_surface_metal.h"
#include "flutter/shell/gpu/gpu_surface_metal.h"
namespace
flutter
{
IOSSurfaceMetal
::
IOSSurfaceMetal
(
fml
::
scoped_nsobject
<
CAMetalLayer
>
layer
,
FlutterPlatformViewsController
*
platform_views_controller
)
:
IOSSurface
(
platform_views_controller
),
layer_
(
std
::
move
(
layer
))
{}
IOSSurfaceMetal
::~
IOSSurfaceMetal
()
=
default
;
// |IOSSurface|
bool
IOSSurfaceMetal
::
IsValid
()
const
{
return
layer_
;
}
// |IOSSurface|
bool
IOSSurfaceMetal
::
ResourceContextMakeCurrent
()
{
return
false
;
}
// |IOSSurface|
void
IOSSurfaceMetal
::
UpdateStorageSizeIfNecessary
()
{}
// |IOSSurface|
std
::
unique_ptr
<
Surface
>
IOSSurfaceMetal
::
CreateGPUSurface
()
{
return
std
::
make_unique
<
GPUSurfaceMetal
>
(
layer_
);
}
}
// namespace flutter
shell/platform/embedder/BUILD.gn
浏览文件 @
5526884e
...
...
@@ -11,6 +11,7 @@ shell_gpu_configuration("embedder_gpu_configuration") {
enable_software = true
enable_vulkan = false
enable_gl = true
enable_metal = false
}
source_set("embedder") {
...
...
tools/gn
浏览文件 @
5526884e
...
...
@@ -43,6 +43,9 @@ def get_out_dir(args):
if
args
.
enable_vulkan
:
target_dir
.
append
(
'vulkan'
)
if
args
.
enable_metal
:
target_dir
.
append
(
'metal'
)
return
os
.
path
.
join
(
args
.
out_dir
,
'out'
,
'_'
.
join
(
target_dir
))
def
to_command_line
(
gn_args
):
...
...
@@ -71,6 +74,9 @@ def to_gn_args(args):
if
args
.
target_os
!=
'android'
and
args
.
enable_vulkan
:
raise
Exception
(
'--enable-vulkan is only supported on Android'
)
if
args
.
target_os
!=
'ios'
and
args
.
enable_metal
:
raise
Exception
(
'--enable-metal is only supported on iOS'
)
runtime_mode
=
args
.
runtime_mode
if
args
.
dynamic
and
runtime_mode
in
[
'profile'
,
'release'
]:
runtime_mode
=
'dynamic_'
+
runtime_mode
...
...
@@ -215,6 +221,11 @@ def to_gn_args(args):
gn_args
[
'use_goma'
]
=
False
gn_args
[
'goma_dir'
]
=
None
if
args
.
enable_metal
:
gn_args
[
'skia_use_metal'
]
=
True
gn_args
[
'shell_enable_metal'
]
=
True
gn_args
[
'ios_deployment_target'
]
=
'11'
if
args
.
enable_vulkan
:
# Enable vulkan in the Flutter shell.
gn_args
[
'shell_enable_vulkan'
]
=
True
...
...
@@ -285,6 +296,7 @@ def parse_args(args):
parser
.
add_argument
(
'--operator-new-alignment'
,
dest
=
'operator_new_alignment'
,
type
=
str
,
default
=
None
)
parser
.
add_argument
(
'--enable-vulkan'
,
action
=
'store_true'
,
default
=
False
)
parser
.
add_argument
(
'--enable-metal'
,
action
=
'store_true'
,
default
=
False
)
parser
.
add_argument
(
'--embedder-for-target'
,
dest
=
'embedder_for_target'
,
action
=
'store_true'
,
default
=
False
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录