Skip to content

Commit ed30d77

Browse files
committed
Setup a Metal test surface and add a new unit-test target that tests the testing utilities.
`//flutter/testing` now contains a lot of utilities used by other test targets. This includes stuff like working with render targets that use either OpenGL or Metal, fixtures for interacting with the Dart VM, test assertion predicates, etc.. However, these utilities themselves are not tested as part of a standalone test suite. Instead, only the test targets that include it exercise these utilities. Since these are no longer trivial, a new test target has been added that tests the testing utilities directly.
1 parent 591144d commit ed30d77

9 files changed

+343
-4
lines changed

BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ group("flutter") {
7373
"$flutter_root/shell/platform/common/cpp/client_wrapper:client_wrapper_unittests",
7474
"$flutter_root/shell/platform/embedder:embedder_unittests",
7575
"$flutter_root/shell/platform/glfw/client_wrapper:client_wrapper_glfw_unittests",
76+
"$flutter_root/testing:testing_unittests",
7677
"$flutter_root/third_party/txt:txt_unittests",
7778
]
7879

testing/BUILD.gn

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
# Use of this source code is governed by a BSD-style license that can be
33
# found in the LICENSE file.
44

5+
import("//flutter/shell/config.gni")
6+
import("//flutter/testing/testing.gni")
7+
58
source_set("testing_lib") {
69
testonly = true
710

@@ -77,4 +80,55 @@ if (current_toolchain == host_toolchain) {
7780
"//third_party/swiftshader_flutter:swiftshader",
7881
]
7982
}
83+
84+
# All targets on all platforms should be able to the Metal utilities. On
85+
# platforms where Metal in not available, the tests must be skipped or
86+
# implemented to use another available client rendering API. This is usually
87+
# either OpenGL which is portably implemented via SwiftShader or the software
88+
# backend. This way, all tests compile on all platforms but the Metal backend
89+
# exercised on platforms where Metal itself is available.
90+
source_set("metal") {
91+
testonly = true
92+
93+
sources = [
94+
"$flutter_root/testing/test_metal_surface.cc",
95+
"$flutter_root/testing/test_metal_surface.h",
96+
]
97+
98+
defines = []
99+
100+
if (shell_enable_metal) {
101+
sources += [ "$flutter_root/testing/test_metal_surface_impl.mm" ]
102+
defines += [ "TESTING_ENABLE_METAL" ]
103+
}
104+
105+
deps = [
106+
":skia",
107+
"$flutter_root/fml",
108+
]
109+
}
110+
}
111+
112+
test_fixtures("testing_fixtures") {
113+
fixtures = []
114+
}
115+
116+
# The //flutter/testing library provides utility methods to other test targets.
117+
# This test target tests the testing utilities.
118+
executable("testing_unittests") {
119+
testonly = true
120+
121+
sources = [
122+
"$flutter_root/testing/test_metal_surface_unittests.cc",
123+
]
124+
125+
deps = [
126+
":dart",
127+
":metal",
128+
":opengl",
129+
":skia",
130+
":testing",
131+
":testing_fixtures",
132+
":testing_lib",
133+
]
80134
}

testing/run_tests.py

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def RunCCTests(build_dir, filter):
108108

109109
RunEngineExecutable(build_dir, 'ui_unittests', filter, shuffle_flags)
110110

111+
RunEngineExecutable(build_dir, 'testing_unittests', filter, shuffle_flags)
112+
111113
# These unit-tests are Objective-C and can only run on Darwin.
112114
if IsMac():
113115
RunEngineExecutable(build_dir, 'flutter_channels_unittests', filter, shuffle_flags)

testing/test_metal_surface.cc

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/testing/test_metal_surface.h"
6+
7+
#if TESTING_ENABLE_METAL
8+
#include "flutter/testing/test_metal_surface_impl.h"
9+
#endif // TESTING_ENABLE_METAL
10+
11+
namespace flutter {
12+
13+
bool TestMetalSurface::PlatformSupportsMetal() {
14+
#if TESTING_ENABLE_METAL
15+
return true;
16+
#else // TESTING_ENABLE_METAL
17+
return false;
18+
#endif // TESTING_ENABLE_METAL
19+
}
20+
21+
std::unique_ptr<TestMetalSurface> TestMetalSurface::Create(
22+
SkISize surface_size) {
23+
#if TESTING_ENABLE_METAL
24+
return std::make_unique<TestMetalSurfaceImpl>(surface_size);
25+
#else // TESTING_ENABLE_METAL
26+
return nullptr;
27+
#endif // TESTING_ENABLE_METAL
28+
}
29+
30+
TestMetalSurface::TestMetalSurface() = default;
31+
32+
TestMetalSurface::~TestMetalSurface() = default;
33+
34+
bool TestMetalSurface::IsValid() const {
35+
return impl_ ? impl_->IsValid() : false;
36+
}
37+
38+
sk_sp<GrContext> TestMetalSurface::GetGrContext() const {
39+
return impl_ ? impl_->GetGrContext() : nullptr;
40+
}
41+
42+
sk_sp<SkSurface> TestMetalSurface::GetSurface() const {
43+
return impl_ ? impl_->GetSurface() : nullptr;
44+
}
45+
46+
} // namespace flutter

testing/test_metal_surface.h

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_TESTING_TEST_METAL_SURFACE_H_
6+
#define FLUTTER_TESTING_TEST_METAL_SURFACE_H_
7+
8+
#include "flutter/fml/macros.h"
9+
#include "third_party/skia/include/core/SkSize.h"
10+
#include "third_party/skia/include/core/SkSurface.h"
11+
#include "third_party/skia/include/gpu/GrContext.h"
12+
13+
namespace flutter {
14+
15+
//------------------------------------------------------------------------------
16+
/// @brief Creates a MTLTexture backed SkSurface and context that can be
17+
/// used to render to in unit-tests.
18+
///
19+
class TestMetalSurface {
20+
public:
21+
static bool PlatformSupportsMetal();
22+
23+
static std::unique_ptr<TestMetalSurface> Create(
24+
SkISize surface_size = SkISize::MakeEmpty());
25+
26+
virtual ~TestMetalSurface();
27+
28+
virtual bool IsValid() const;
29+
30+
virtual sk_sp<GrContext> GetGrContext() const;
31+
32+
virtual sk_sp<SkSurface> GetSurface() const;
33+
34+
protected:
35+
TestMetalSurface();
36+
37+
private:
38+
std::unique_ptr<TestMetalSurface> impl_;
39+
40+
TestMetalSurface(std::unique_ptr<TestMetalSurface> impl);
41+
42+
FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurface);
43+
};
44+
45+
} // namespace flutter
46+
47+
#endif // FLUTTER_TESTING_TEST_METAL_SURFACE_H_

testing/test_metal_surface_impl.h

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_TESTING_TEST_METAL_SURFACE_IMPL_H_
6+
#define FLUTTER_TESTING_TEST_METAL_SURFACE_IMPL_H_
7+
8+
#include "flutter/fml/macros.h"
9+
#include "flutter/testing/test_metal_surface.h"
10+
11+
namespace flutter {
12+
13+
class TestMetalSurfaceImpl : public TestMetalSurface {
14+
public:
15+
TestMetalSurfaceImpl(SkISize surface_size);
16+
17+
// |TestMetalSurface|
18+
~TestMetalSurfaceImpl() override;
19+
20+
private:
21+
bool is_valid_ = false;
22+
sk_sp<GrContext> context_;
23+
sk_sp<SkSurface> surface_;
24+
25+
// |TestMetalSurface|
26+
bool IsValid() const override;
27+
28+
// |TestMetalSurface|
29+
sk_sp<GrContext> GetGrContext() const override;
30+
31+
// |TestMetalSurface|
32+
sk_sp<SkSurface> GetSurface() const override;
33+
34+
FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurfaceImpl);
35+
};
36+
37+
} // namespace flutter
38+
39+
#endif // FLUTTER_TESTING_TEST_METAL_SURFACE_IMPL_H_

testing/test_metal_surface_impl.mm

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/testing/test_metal_surface_impl.h"
6+
7+
#include <Metal/Metal.h>
8+
9+
#include "flutter/fml/logging.h"
10+
#include "flutter/fml/platform/darwin/scoped_nsobject.h"
11+
#include "third_party/skia/include/core/SkSurface.h"
12+
13+
namespace flutter {
14+
15+
TestMetalSurfaceImpl::TestMetalSurfaceImpl(SkISize surface_size) {
16+
if (surface_size.isEmpty()) {
17+
FML_LOG(ERROR) << "Size of test Metal surface was empty.";
18+
return;
19+
}
20+
21+
auto device = fml::scoped_nsobject{[MTLCreateSystemDefaultDevice() retain]};
22+
if (!device) {
23+
FML_LOG(ERROR) << "Could not acquire Metal device.";
24+
return;
25+
}
26+
27+
auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]};
28+
if (!command_queue) {
29+
FML_LOG(ERROR) << "Could not create the default command queue.";
30+
return;
31+
}
32+
33+
auto texture_descriptor = fml::scoped_nsobject{[[MTLTextureDescriptor
34+
texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
35+
width:surface_size.width()
36+
height:surface_size.height()
37+
mipmapped:NO] retain]};
38+
39+
// The most pessimistic option and disables all optimizations but allows tests
40+
// the most flexible access to the surface. They may read and wrote to the
41+
// surface from shaders or use as a pixel view.
42+
texture_descriptor.get().usage = MTLTextureUsageUnknown;
43+
44+
if (!texture_descriptor) {
45+
FML_LOG(ERROR) << "Invalid texture descriptor.";
46+
return;
47+
}
48+
49+
auto texture = fml::scoped_nsobject{
50+
[device.get() newTextureWithDescriptor:texture_descriptor.get()]};
51+
52+
if (!texture) {
53+
FML_LOG(ERROR) << "Could not create texture from texture descriptor.";
54+
return;
55+
}
56+
57+
auto skia_context = GrContext::MakeMetal(device.get(), command_queue.get());
58+
59+
if (skia_context) {
60+
// Skia wants ownership of the device and queue. If a context was created,
61+
// we now no longer own the argument. Release the arguments only on
62+
// successful creation of the context.
63+
FML_ALLOW_UNUSED_LOCAL(device.release());
64+
FML_ALLOW_UNUSED_LOCAL(command_queue.release());
65+
} else {
66+
FML_LOG(ERROR) << "Could not create the GrContext from the Metal Device "
67+
"and command queue.";
68+
return;
69+
}
70+
71+
GrMtlTextureInfo skia_texture_info;
72+
skia_texture_info.fTexture = sk_cf_obj<const void*>{[texture.get() retain]};
73+
74+
auto backend_render_target = GrBackendRenderTarget{
75+
surface_size.width(), // width
76+
surface_size.height(), // height
77+
1, // sample count
78+
skia_texture_info // texture info
79+
};
80+
81+
auto surface = SkSurface::MakeFromBackendRenderTarget(
82+
skia_context.get(), // context
83+
backend_render_target, // backend render target
84+
kTopLeft_GrSurfaceOrigin, // surface origin
85+
kBGRA_8888_SkColorType, // color type
86+
nullptr, // color space
87+
nullptr, // surface properties
88+
nullptr, // release proc (texture is already ref counted in sk_cf_obj)
89+
nullptr // release context
90+
);
91+
92+
if (!surface) {
93+
FML_LOG(ERROR) << "Could not create Skia surface from a Metal texture.";
94+
return;
95+
}
96+
97+
surface_ = std::move(surface);
98+
context_ = std::move(skia_context);
99+
100+
is_valid_ = true;
101+
}
102+
103+
// |TestMetalSurface|
104+
TestMetalSurfaceImpl::~TestMetalSurfaceImpl() = default;
105+
106+
// |TestMetalSurface|
107+
bool TestMetalSurfaceImpl::IsValid() const {
108+
return is_valid_;
109+
}
110+
// |TestMetalSurface|
111+
sk_sp<GrContext> TestMetalSurfaceImpl::GetGrContext() const {
112+
return IsValid() ? context_ : nullptr;
113+
}
114+
// |TestMetalSurface|
115+
sk_sp<SkSurface> TestMetalSurfaceImpl::GetSurface() const {
116+
return IsValid() ? surface_ : nullptr;
117+
}
118+
119+
} // namespace flutter
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/testing/test_metal_surface.h"
6+
#include "flutter/testing/testing.h"
7+
8+
namespace flutter {
9+
namespace testing {
10+
11+
TEST(TestMetalSurface, EmptySurfaceIsInvalid) {
12+
if (!TestMetalSurface::PlatformSupportsMetal()) {
13+
GTEST_SKIP();
14+
}
15+
16+
auto surface = TestMetalSurface::Create();
17+
ASSERT_NE(surface, nullptr);
18+
ASSERT_FALSE(surface->IsValid());
19+
}
20+
21+
TEST(TestMetalSurface, CanCreateValidTestMetalSurface) {
22+
if (!TestMetalSurface::PlatformSupportsMetal()) {
23+
GTEST_SKIP();
24+
}
25+
26+
auto surface = TestMetalSurface::Create(SkISize::Make(100, 100));
27+
ASSERT_NE(surface, nullptr);
28+
ASSERT_TRUE(surface->IsValid());
29+
ASSERT_NE(surface->GetSurface(), nullptr);
30+
ASSERT_NE(surface->GetGrContext(), nullptr);
31+
}
32+
33+
} // namespace testing
34+
} // namespace flutter

tools/gn

+1-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def get_out_dir(args):
4444
if args.enable_vulkan:
4545
target_dir.append('vulkan')
4646

47-
if args.enable_metal:
47+
if args.enable_metal and args.target_os == 'ios':
4848
target_dir.append('metal')
4949

5050
return os.path.join(args.out_dir, 'out', '_'.join(target_dir))
@@ -75,9 +75,6 @@ def to_gn_args(args):
7575
if args.target_os != 'android' and args.enable_vulkan:
7676
raise Exception('--enable-vulkan is only supported on Android')
7777

78-
if args.target_os != 'ios' and args.enable_metal:
79-
raise Exception('--enable-metal is only supported on iOS')
80-
8178
runtime_mode = args.runtime_mode
8279

8380
gn_args = {}

0 commit comments

Comments
 (0)