From 13d8565cf2e49e4e04ecb2cb37ae1e5d6f9f3da4 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Wed, 14 Apr 2021 09:39:01 -0700 Subject: [PATCH] Support SKP captures in flutter_tester (#25566) --- shell/common/rasterizer.cc | 1 - shell/gpu/gpu_surface_software_delegate.h | 2 +- shell/testing/BUILD.gn | 1 + shell/testing/tester_main.cc | 51 ++++++++++++++++- testing/dart/observatory/README.md | 5 ++ testing/dart/observatory/skp_test.dart | 67 +++++++++++++++++++++++ testing/dart/pubspec.yaml | 7 ++- testing/run_tests.py | 19 ++++++- 8 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 testing/dart/observatory/README.md create mode 100644 testing/dart/observatory/skp_test.dart diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index d0f70b63a..acd33975f 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -454,7 +454,6 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { // Deleting a surface also clears the GL context. Therefore, acquire the // frame after calling `BeginFrame` as this operation resets the GL context. auto frame = surface_->AcquireFrame(layer_tree.frame_size()); - if (frame == nullptr) { return RasterStatus::kFailed; } diff --git a/shell/gpu/gpu_surface_software_delegate.h b/shell/gpu/gpu_surface_software_delegate.h index 81cf30a6b..e46f4ec0f 100644 --- a/shell/gpu/gpu_surface_software_delegate.h +++ b/shell/gpu/gpu_surface_software_delegate.h @@ -21,7 +21,7 @@ namespace flutter { /// rasterizer needs to allocate and present the software backing /// store. /// -/// @see |IOSurfaceSoftware|, |AndroidSurfaceSoftware|, +/// @see |IOSSurfaceSoftware|, |AndroidSurfaceSoftware|, /// |EmbedderSurfaceSoftware|. /// class GPUSurfaceSoftwareDelegate { diff --git a/shell/testing/BUILD.gn b/shell/testing/BUILD.gn index f82f48132..678f6207c 100644 --- a/shell/testing/BUILD.gn +++ b/shell/testing/BUILD.gn @@ -28,6 +28,7 @@ executable("testing") { "//flutter/fml", "//flutter/lib/snapshot", "//flutter/shell/common", + "//flutter/shell/gpu:gpu_surface_software", "//flutter/third_party/tonic", "//third_party/dart/runtime:libdart_jit", "//third_party/dart/runtime/bin:dart_io_api", diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 6095ffe14..cbdaabfe1 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -22,6 +22,7 @@ #include "flutter/shell/common/shell.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/common/thread_host.h" +#include "flutter/shell/gpu/gpu_surface_software.h" #include "third_party/dart/runtime/include/bin/dart_io_api.h" #include "third_party/dart/runtime/include/dart_api.h" @@ -31,6 +32,51 @@ namespace flutter { +class TesterPlatformView : public PlatformView, + public GPUSurfaceSoftwareDelegate { + public: + TesterPlatformView(Delegate& delegate, TaskRunners task_runners) + : PlatformView(delegate, std::move(task_runners)) {} + + // |PlatformView| + std::unique_ptr CreateRenderingSurface() override { + auto surface = std::make_unique( + this, true /* render to surface */); + FML_DCHECK(surface->IsValid()); + return surface; + } + + // |GPUSurfaceSoftwareDelegate| + sk_sp AcquireBackingStore(const SkISize& size) override { + if (sk_surface_ != nullptr && + SkISize::Make(sk_surface_->width(), sk_surface_->height()) == size) { + // The old and new surface sizes are the same. Nothing to do here. + return sk_surface_; + } + + SkImageInfo info = + SkImageInfo::MakeN32(size.fWidth, size.fHeight, kPremul_SkAlphaType, + SkColorSpace::MakeSRGB()); + sk_surface_ = SkSurface::MakeRaster(info, nullptr); + + if (sk_surface_ == nullptr) { + FML_LOG(ERROR) + << "Could not create backing store for software rendering."; + return nullptr; + } + + return sk_surface_; + } + + // |GPUSurfaceSoftwareDelegate| + bool PresentBackingStore(sk_sp backing_store) override { + return true; + } + + private: + sk_sp sk_surface_ = nullptr; +}; + // Checks whether the engine's main Dart isolate has no pending work. If so, // then exit the given message loop. class ScriptCompletionTaskObserver { @@ -138,7 +184,8 @@ int RunTester(const flutter::Settings& settings, Shell::CreateCallback on_create_platform_view = [](Shell& shell) { - return std::make_unique(shell, shell.GetTaskRunners()); + return std::make_unique(shell, + shell.GetTaskRunners()); }; Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { @@ -162,6 +209,8 @@ int RunTester(const flutter::Settings& settings, return EXIT_FAILURE; } + shell->GetPlatformView()->NotifyCreated(); + // Initialize default testing locales. There is no platform to // pass locales on the tester, so to retain expected locale behavior, // we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales. diff --git a/testing/dart/observatory/README.md b/testing/dart/observatory/README.md new file mode 100644 index 000000000..41b57d88d --- /dev/null +++ b/testing/dart/observatory/README.md @@ -0,0 +1,5 @@ +Tests in this folder need to be run with the observatory enabled, e.g. to make +VM service method calls. + +The `run_tests.py` script disables the observatory for other tests in the +parent directory. \ No newline at end of file diff --git a/testing/dart/observatory/skp_test.dart b/testing/dart/observatory/skp_test.dart new file mode 100644 index 000000000..2b2e3e813 --- /dev/null +++ b/testing/dart/observatory/skp_test.dart @@ -0,0 +1,67 @@ +// 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. + +// @dart=2.12 + +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer' as developer; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:test/test.dart'; +import 'package:vm_service/vm_service.dart' as vms; +import 'package:vm_service/vm_service_io.dart'; + +void main() { + late vms.VmService vmService; + + setUpAll(() async { + final developer.ServiceProtocolInfo info = + await developer.Service.getInfo(); + + if (info.serverUri == null) { + fail('This test must not be run with --disable-observatory.'); + } + + vmService = await vmServiceConnectUri( + 'ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws', + ); + }); + + tearDownAll(() async { + await vmService.dispose(); + }); + + test('Capture an SKP ', () async { + final Completer completer = Completer(); + window.onBeginFrame = (Duration timeStamp) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(const Rect.fromLTRB(10, 10, 20, 20), Paint()); + final Picture picture = recorder.endRecording(); + + final SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset.zero, picture); + final Scene scene = builder.build(); + + window.render(scene); + scene.dispose(); + // window.onBeginFrame = (Duration timeStamp) { + completer.complete(); + // }; + // window.scheduleFrame(); + }; + window.scheduleFrame(); + await completer.future; + + final vms.Response response = await vmService.callServiceExtension('_flutter.screenshotSkp'); + + final String base64data = response.json!['skp'] as String; + expect(base64data, isNotNull); + expect(base64data, isNotEmpty); + final Uint8List decoded = base64Decode(base64data); + expect(decoded.sublist(0, 8), 'skiapict'.codeUnits); + }); +} diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml index a605e5bd2..903346810 100644 --- a/testing/dart/pubspec.yaml +++ b/testing/dart/pubspec.yaml @@ -4,9 +4,10 @@ environment: sdk: '>=2.8.0 <3.0.0' dependencies: - test: 1.3.0 - path: 1.6.2 - image: ^2.1.4 + test: 1.16.8 + path: 1.8.0 + image: 3.0.2 + vm_service: 6.2.0 dependency_overrides: sky_engine: diff --git a/testing/run_tests.py b/testing/run_tests.py index d5dd747ff..4a21171e2 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -225,14 +225,17 @@ def SnapshotTest(build_dir, dart_file, kernel_file_output, verbose_dart_snapshot assert os.path.exists(kernel_file_output) -def RunDartTest(build_dir, dart_file, verbose_dart_snapshot, multithreaded): +def RunDartTest(build_dir, dart_file, verbose_dart_snapshot, multithreaded, enable_observatory=False): kernel_file_name = os.path.basename(dart_file) + '.kernel.dill' kernel_file_output = os.path.join(out_dir, kernel_file_name) SnapshotTest(build_dir, dart_file, kernel_file_output, verbose_dart_snapshot) - command_args = [ - '--disable-observatory', + command_args = [] + if not enable_observatory: + command_args.append('--disable-observatory') + + command_args += [ '--use-test-fonts', kernel_file_output ] @@ -415,8 +418,18 @@ def RunDartTests(build_dir, filter, verbose_dart_snapshot): # Now that we have the Sky packages at the hardcoded location, run `pub get`. RunEngineExecutable(build_dir, os.path.join('dart-sdk', 'bin', 'pub'), None, flags=['get'], cwd=dart_tests_dir) + dart_observatory_tests = glob.glob('%s/observatory/*_test.dart' % dart_tests_dir) dart_tests = glob.glob('%s/*_test.dart' % dart_tests_dir) + if 'release' not in build_dir: + for dart_test_file in dart_observatory_tests: + if filter is not None and os.path.basename(dart_test_file) not in filter: + print("Skipping %s due to filter." % dart_test_file) + else: + print("Testing dart file %s with observatory enabled" % dart_test_file) + RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, True, True) + RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, False, True) + for dart_test_file in dart_tests: if filter is not None and os.path.basename(dart_test_file) not in filter: print("Skipping %s due to filter." % dart_test_file) -- GitLab