浏览代码

draft2

/siyaoH-1.17-PlatformMessage
Kevin Gu 4 年前
当前提交
eea80dd6
共有 136 个文件被更改,包括 6609 次插入14 次删除
  1. 16
      com.unity.uiwidgets/Runtime/ui/geometry.cs
  2. 131
      engine/Build.bee.cs
  3. 3
      com.unity.uiwidgets/Runtime/ui2.meta
  4. 6
      engine/.gitignore
  5. 3
      com.unity.uiwidgets/Runtime/ui2/painting.meta
  6. 3
      com.unity.uiwidgets/Runtime/ui2/painting/Isolate.cs.meta
  7. 49
      com.unity.uiwidgets/Runtime/ui2/painting/isolate.cs
  8. 203
      com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs
  9. 3
      com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs.meta
  10. 1001
      com.unity.uiwidgets/Runtime/ui2/painting/painting.cs
  11. 3
      com.unity.uiwidgets/Runtime/ui2/painting/painting.cs.meta
  12. 61
      engine/src/common/settings.cc
  13. 204
      engine/src/common/settings.h
  14. 46
      engine/src/common/task_runners.cc
  15. 42
      engine/src/common/task_runners.h
  16. 104
      engine/src/flow/compositor_context.cc
  17. 114
      engine/src/flow/compositor_context.h
  18. 49
      engine/src/flow/embedded_views.cc
  19. 253
      engine/src/flow/embedded_views.h
  20. 312
      engine/src/flow/instrumentation.cc
  21. 94
      engine/src/flow/instrumentation.h
  22. 25
      engine/src/flow/layers/backdrop_filter_layer.cc
  23. 20
      engine/src/flow/layers/backdrop_filter_layer.h
  24. 56
      engine/src/flow/layers/clip_path_layer.cc
  25. 25
      engine/src/flow/layers/clip_path_layer.h
  26. 55
      engine/src/flow/layers/clip_rect_layer.cc
  27. 26
      engine/src/flow/layers/clip_rect_layer.h
  28. 56
      engine/src/flow/layers/clip_rrect_layer.cc
  29. 25
      engine/src/flow/layers/clip_rrect_layer.h
  30. 27
      engine/src/flow/layers/color_filter_layer.cc
  31. 21
      engine/src/flow/layers/color_filter_layer.h
  32. 64
      engine/src/flow/layers/container_layer.cc
  33. 34
      engine/src/flow/layers/container_layer.h
  34. 69
      engine/src/flow/layers/image_filter_layer.cc
  35. 23
      engine/src/flow/layers/image_filter_layer.h
  36. 83
      engine/src/flow/layers/layer.cc
  37. 172
      engine/src/flow/layers/layer.h
  38. 158
      engine/src/flow/layers/layer_tree.cc
  39. 88
      engine/src/flow/layers/layer_tree.h
  40. 108
      engine/src/flow/layers/opacity_layer.cc
  41. 41
      engine/src/flow/layers/opacity_layer.h
  42. 90
      engine/src/flow/layers/performance_overlay_layer.cc
  43. 36
      engine/src/flow/layers/performance_overlay_layer.h
  44. 179
      engine/src/flow/layers/physical_shape_layer.cc
  45. 39
      engine/src/flow/layers/physical_shape_layer.h
  46. 60
      engine/src/flow/layers/picture_layer.cc
  47. 33
      engine/src/flow/layers/picture_layer.h
  48. 39
      engine/src/flow/layers/platform_view_layer.cc
  49. 24
      engine/src/flow/layers/platform_view_layer.h
  50. 32
      engine/src/flow/layers/shader_mask_layer.cc
  51. 25
      engine/src/flow/layers/shader_mask_layer.h
  52. 31
      engine/src/flow/layers/texture_layer.cc
  53. 26
      engine/src/flow/layers/texture_layer.h
  54. 59
      engine/src/flow/layers/transform_layer.cc
  55. 23
      engine/src/flow/layers/transform_layer.h
  56. 146
      engine/src/flow/matrix_decomposition.cc
  57. 44
      engine/src/flow/matrix_decomposition.h
  58. 49
      engine/src/flow/paint_utils.cc
  59. 13
      engine/src/flow/paint_utils.h
  60. 289
      engine/src/flow/raster_cache.cc
  61. 130
      engine/src/flow/raster_cache.h
  62. 7
      engine/src/flow/raster_cache_key.cc
  63. 58
      engine/src/flow/raster_cache_key.h
  64. 87
      engine/src/flow/rtree.cc
  65. 57
      engine/src/flow/rtree.h
  66. 46
      engine/src/flow/skia_gpu_object.cc
  67. 83
      engine/src/flow/skia_gpu_object.h
  68. 44
      engine/src/flow/texture.cc
  69. 65
      engine/src/flow/texture.h
  70. 23
      engine/src/lib/ui/io_manager.h
  71. 33
      engine/src/lib/ui/painting/canvas.cc
  72. 29
      engine/src/lib/ui/painting/canvas.h
  73. 31
      engine/src/lib/ui/painting/image.cc
  74. 38
      engine/src/lib/ui/painting/image.h
  75. 11
      engine/src/lib/ui/painting/image_decoder.cc
  76. 8
      engine/src/lib/ui/painting/image_decoder.h
  77. 234
      engine/src/lib/ui/painting/image_encoding.cc
  78. 16
      engine/src/lib/ui/painting/image_encoding.h
  79. 98
      engine/src/lib/ui/painting/picture.cc
  80. 39
      engine/src/lib/ui/painting/picture.h
  81. 63
      engine/src/lib/ui/painting/picture_recorder.cc
  82. 32
      engine/src/lib/ui/painting/picture_recorder.h
  83. 16
      engine/src/lib/ui/snapshot_delegate.h
  84. 83
      engine/src/lib/ui/ui_mono_state.cc
  85. 75
      engine/src/lib/ui/ui_mono_state.h
  86. 27
      engine/src/lib/ui/window/platform_message.cc
  87. 39
      engine/src/lib/ui/window/platform_message.h
  88. 9
      engine/src/lib/ui/window/platform_message_response.cc
  89. 29
      engine/src/lib/ui/window/platform_message_response.h

16
com.unity.uiwidgets/Runtime/ui/geometry.cs


tolerance = tolerance ?? _valueNearlyZero;
return Mathf.Abs(x) <= tolerance;
}
public static float clamp(this float value, float min, float max) {
if (value < min) {
value = min;

get { return new Size(this.width, this.height); }
}
public bool hasNaN => this.left.isNaN() || this.top.isNaN() || this.right.isNaN() || this.bottom.isNaN();
public float area {
get { return this.width * this.height; }
}

this.left * scaleX, this.top * scaleY.Value,
this.right * scaleX, this.bottom * scaleY.Value);
}
public Rect outset(float dx, float dy) {
return new Rect(this.left - dx, this.top - dy, this.right + dx, this.bottom + dy);
}

public Rect roundOutScale(float scale) {
return fromLTRB(
Mathf.Floor(this.left * scale),
Mathf.Floor(this.left * scale),
Mathf.Ceil(this.right * scale),
Mathf.Ceil(this.right * scale),
Mathf.Floor(this.left * devicePixelRatio) / devicePixelRatio,
Mathf.Floor(this.left * devicePixelRatio) / devicePixelRatio,
Mathf.Ceil(this.right * devicePixelRatio) / devicePixelRatio,
Mathf.Ceil(this.right * devicePixelRatio) / devicePixelRatio,
Mathf.Ceil(this.bottom * devicePixelRatio) / devicePixelRatio);
}

131
engine/Build.bee.cs


{
Sources =
{
"src/common/settings.cc",
"src/common/settings.h",
"src/common/task_runners.cc",
"src/common/task_runners.h",
"src/flow/layers/backdrop_filter_layer.cc",
"src/flow/layers/backdrop_filter_layer.h",
"src/flow/layers/clip_path_layer.cc",
"src/flow/layers/clip_path_layer.h",
"src/flow/layers/clip_rect_layer.cc",
"src/flow/layers/clip_rect_layer.h",
"src/flow/layers/clip_rrect_layer.cc",
"src/flow/layers/clip_rrect_layer.h",
"src/flow/layers/color_filter_layer.cc",
"src/flow/layers/color_filter_layer.h",
"src/flow/layers/container_layer.cc",
"src/flow/layers/container_layer.h",
"src/flow/layers/image_filter_layer.cc",
"src/flow/layers/image_filter_layer.h",
"src/flow/layers/layer.cc",
"src/flow/layers/layer.h",
"src/flow/layers/layer_tree.cc",
"src/flow/layers/layer_tree.h",
"src/flow/layers/opacity_layer.cc",
"src/flow/layers/opacity_layer.h",
"src/flow/layers/performance_overlay_layer.cc",
"src/flow/layers/performance_overlay_layer.h",
"src/flow/layers/physical_shape_layer.cc",
"src/flow/layers/physical_shape_layer.h",
"src/flow/layers/picture_layer.cc",
"src/flow/layers/picture_layer.h",
"src/flow/layers/platform_view_layer.cc",
"src/flow/layers/platform_view_layer.h",
"src/flow/layers/shader_mask_layer.cc",
"src/flow/layers/shader_mask_layer.h",
"src/flow/layers/texture_layer.cc",
"src/flow/layers/texture_layer.h",
"src/flow/layers/transform_layer.cc",
"src/flow/layers/transform_layer.h",
"src/flow/compositor_context.cc",
"src/flow/compositor_context.h",
"src/flow/embedded_views.cc",
"src/flow/embedded_views.h",
"src/flow/instrumentation.cc",
"src/flow/instrumentation.h",
"src/flow/matrix_decomposition.cc",
"src/flow/matrix_decomposition.h",
"src/flow/paint_utils.cc",
"src/flow/paint_utils.h",
"src/flow/raster_cache.cc",
"src/flow/raster_cache.h",
"src/flow/raster_cache_key.cc",
"src/flow/raster_cache_key.h",
"src/flow/rtree.cc",
"src/flow/rtree.h",
"src/flow/skia_gpu_object.cc",
"src/flow/skia_gpu_object.h",
"src/flow/texture.cc",
"src/flow/texture.h",
"src/lib/ui/painting/canvas.cc",
"src/lib/ui/painting/canvas.h",
"src/lib/ui/painting/image.cc",
"src/lib/ui/painting/image.h",
"src/lib/ui/painting/image_decoder.cc",
"src/lib/ui/painting/image_decoder.h",
"src/lib/ui/painting/image_encoding.cc",
"src/lib/ui/painting/image_encoding.h",
"src/lib/ui/painting/picture.cc",
"src/lib/ui/painting/picture.h",
"src/lib/ui/painting/picture_recorder.cc",
"src/lib/ui/painting/picture_recorder.h",
"src/lib/ui/io_manager.h",
"src/lib/ui/snapshot_delegate.h",
"src/lib/ui/ui_mono_state.cc",
"src/lib/ui/ui_mono_state.h",
"src/runtime/mono_api.cc",
"src/runtime/mono_api.h",
"src/runtime/mono_isolate.cc",
"src/runtime/mono_isolate.h",
"src/runtime/mono_isolate_scope.cc",
"src/runtime/mono_isolate_scope.h",
"src/runtime/mono_microtask_queue.cc",
"src/runtime/mono_microtask_queue.h",
"src/runtime/mono_state.cc",
"src/runtime/mono_state.h",
"src/shell/common/animator.cc",
"src/shell/common/animator.h",
"src/shell/common/persistent_cache.cc",
"src/shell/common/persistent_cache.h",
"src/shell/common/pipeline.cc",
"src/shell/common/pipeline.h",
"src/shell/common/rasterizer.cc",
"src/shell/common/rasterizer.h",
"src/shell/common/shell_io_manager.cc",
"src/shell/common/shell_io_manager.h",
"src/shell/common/surface.cc",
"src/shell/common/surface.h",
"src/shell/common/thread_host.cc",
"src/shell/common/thread_host.h",
"src/shell/common/vsync_waiter.cc",
"src/shell/common/vsync_waiter.h",
"src/shell/common/vsync_waiter_fallback.cc",
"src/shell/common/vsync_waiter_fallback.h",
"src/shell/version/version.cc",
"src/shell/version/version.h",
"src/engine.cc",
"src/platform_base.h",
"src/render_api.cc",

};
np.CompilerSettings().Add(c => c.WithCppLanguageVersion(CppLanguageVersion.Cpp17));
np.IncludeDirectories.Add("src");
np.Defines.Add("UIWIDGETS_ENGINE_VERSION=\\\"0.0\\\"", "SKIA_VERSION=\\\"0.0\\\"");
np.Defines.Add(c => c.CodeGen == CodeGen.Release,
new[] {"UIWidgets_RELEASE=1"});
np.LinkerSettings().Add(l => l.WithCustomFlags_workaround(new[] {"/DEBUG:FULL"}));
SetupFml(np);

foreach (var codegen in codegens)
{
var config = new NativeProgramConfiguration(codegen, toolchain, lump: true);
builtNP.DeployTo("../Samples/UIWidgetsSamples_2019_4/Assets/Plugins/x86_64");
}

return np;
}
static void SetupFml(NativeProgram np)
static void SetupFml(NativeProgram np)
{
var flutterRoot = Environment.GetEnvironmentVariable("FLUTTER_ROOT");
if (string.IsNullOrEmpty(flutterRoot))

return new PrecompiledLibrary[]
{
new StaticLibrary(basePath + "/obj/flutter/fml/fml_lib.lib"),
new SystemLibrary("Rpcrt4.lib"),
};
});
}

new StaticLibrary(basePath + "/skia.lib"),
new StaticLibrary(basePath + "/skottie.lib"),
new StaticLibrary(basePath + "/sksg.lib"),
new StaticLibrary(basePath + "/skshaper.lib"),
new StaticLibrary(basePath + "/skshaper.lib"),
new StaticLibrary(basePath + "/libEGL.dll.lib"),
new StaticLibrary(basePath + "/libGLESv2.dll.lib"),
// new SystemLibrary("Opengl32.lib"),

new DeployableFile(basePath + "/libGLESv2.dll.pdb")
);
}
// static void SetupSkiaAndroid(NativeProgram np)
// {
// var skiaRoot = Environment.GetEnvironmentVariable("SKIA_ROOT");

// };
// });
// }
}

3
com.unity.uiwidgets/Runtime/ui2.meta


fileFormatVersion: 2
guid: d9f439c3630548cbada41ef026dfa6ed
timeCreated: 1595649299

6
engine/.gitignore


.idea
.vs
artifacts
build
obj
*.gen.*

3
com.unity.uiwidgets/Runtime/ui2/painting.meta


fileFormatVersion: 2
guid: e01e51b924af484789f4f45614020d99
timeCreated: 1595649311

3
com.unity.uiwidgets/Runtime/ui2/painting/Isolate.cs.meta


fileFormatVersion: 2
guid: 62a9411e3050488c8642ba3d1b8bb595
timeCreated: 1595927572

49
com.unity.uiwidgets/Runtime/ui2/painting/isolate.cs


using System;
using Unity.UIWidgets.foundation;
namespace Unity.UIWidgets.ui2 {
public static class Isolate {
public static IntPtr current() {
return NativeBindings.Isolate_current();
}
public static IDisposable getScope(IntPtr isolate) {
return new _IsolateDisposable(isolate);
}
class _IsolateDisposable : IDisposable {
IntPtr _isolate;
IntPtr _previous;
public _IsolateDisposable(IntPtr isolate) {
_isolate = isolate;
_previous = NativeBindings.Isolate_current();
if (_previous == _isolate) {
return;
}
if (_previous != IntPtr.Zero) {
NativeBindings.Isolate_exit();
}
NativeBindings.Isolate_enter(_isolate);
}
public void Dispose() {
var current = NativeBindings.Isolate_current();
D.assert(current == IntPtr.Zero || current == _isolate);
if (_previous == _isolate) {
return;
}
if (current != IntPtr.Zero) {
NativeBindings.Isolate_exit();
}
if (_previous != IntPtr.Zero) {
NativeBindings.Isolate_enter(_previous);
}
}
}
}
}

203
com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs


using System;
using System.Runtime.InteropServices;
using Unity.UIWidgets.foundation;
namespace Unity.UIWidgets.ui2 {
class NativeBindings {
#if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
const string dllName = "__Internal";
#else
const string dllName = "libUIWidgets_d";
#endif
[DllImport(dllName)]
public static extern IntPtr Isolate_current();
[DllImport(dllName)]
public static extern void Isolate_enter(IntPtr isolate);
[DllImport(dllName)]
public static extern void Isolate_exit();
[DllImport(dllName)]
public static extern IntPtr ImageShader_constructor();
[DllImport(dllName)]
public static extern void ImageShader_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern unsafe void ImageShader_initWithImage(IntPtr ptr,
IntPtr image, int tmx, int tmy, float* matrix4);
[DllImport(dllName)]
public static extern IntPtr Gradient_constructor();
[DllImport(dllName)]
public static extern void Gradient_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern unsafe void Gradient_initLinear(IntPtr ptr,
float* endPoints, int endPointsLength,
uint* colors, int colorsLength,
float* colorStops, int colorStopsLength,
int tileMode, float* matrix4);
[DllImport(dllName)]
public static extern unsafe void Gradient_initRadial(IntPtr ptr,
float centerX, float centerY, float radius,
uint* colors, int colorsLength,
float* colorStops, int colorStopsLength,
int tileMode, float* matrix4);
[DllImport(dllName)]
public static extern unsafe void Gradient_initConical(IntPtr ptr,
float startX, float startY, float startRadius,
float endX, float endY, float endRadius,
uint* colors, int colorsLength,
float* colorStops, int colorStopsLength,
int tileMode, float* matrix4);
[DllImport(dllName)]
public static extern unsafe void Gradient_initSweep(IntPtr ptr,
float centerX, float centerY,
uint* colors, int colorsLength,
float* colorStops, int colorStopsLength,
int tileMode, float startAngle, float endAngle, float* matrix4);
[DllImport(dllName)]
public static extern IntPtr ColorFilter_constructor();
[DllImport(dllName)]
public static extern void ColorFilter_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern void ColorFilter_initMode(IntPtr ptr, uint color, int blendMode);
[DllImport(dllName)]
public static extern unsafe void ColorFilter_initMatrix(IntPtr ptr, float* matrix4);
[DllImport(dllName)]
public static extern unsafe void ColorFilter_initLinearToSrgbGamma(IntPtr ptr);
[DllImport(dllName)]
public static extern unsafe void ColorFilter_initSrgbToLinearGamma(IntPtr ptr);
[DllImport(dllName)]
public static extern IntPtr ImageFilter_constructor();
[DllImport(dllName)]
public static extern void ImageFilter_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern void ImageFilter_initBlur(IntPtr ptr, float sigmaX, float sigmaY);
[DllImport(dllName)]
public static extern unsafe void ImageFilter_initMatrix(IntPtr ptr, float* matrix4, int filterQuality);
[DllImport(dllName)]
public static extern IntPtr Canvas_constructor(IntPtr recorder,
double left,
double top,
double right,
double bottom);
[DllImport(dllName)]
public static extern void Canvas_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern void Canvas_save(IntPtr ptr);
[DllImport(dllName)]
public static extern void Canvas_restore(IntPtr ptr);
[DllImport(dllName)]
public static extern void Image_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern int Image_width(IntPtr ptr);
[DllImport(dllName)]
public static extern int Image_height(IntPtr ptr);
public delegate void Image_toByteDataCallback(IntPtr callbackHandle, IntPtr data, int length);
[DllImport(dllName)]
public static extern IntPtr Image_toByteData(IntPtr ptr, int format, Image_toByteDataCallback callback,
IntPtr callbackHandle);
[DllImport(dllName)]
public static extern void Picture_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern int Picture_GetAllocationSize(IntPtr ptr);
public delegate void Picture_toImageCallback(IntPtr callbackHandle, IntPtr result);
[DllImport(dllName)]
public static extern IntPtr Picture_toImage(IntPtr ptr, int width, int height, Picture_toImageCallback callback,
IntPtr callbackHandle);
[DllImport(dllName)]
public static extern IntPtr PictureRecorder_constructor();
[DllImport(dllName)]
public static extern void PictureRecorder_dispose(IntPtr ptr);
[DllImport(dllName)]
public static extern bool PictureRecorder_isRecording(IntPtr ptr);
[DllImport(dllName)]
public static extern IntPtr PictureRecorder_endRecording(IntPtr ptr);
}
public abstract class NativeWrapper {
protected internal IntPtr _ptr { get; protected set; }
protected NativeWrapper() {
}
protected NativeWrapper(IntPtr ptr) {
D.assert(_ptr != IntPtr.Zero);
_ptr = ptr;
}
~NativeWrapper() {
if (_ptr != IntPtr.Zero) {
DisposePtr(_ptr);
_ptr = IntPtr.Zero;
}
}
protected abstract void DisposePtr(IntPtr ptr);
}
public abstract class NativeWrapperDisposable : IDisposable {
protected internal IntPtr _ptr { get; protected set; }
protected NativeWrapperDisposable() {
}
protected NativeWrapperDisposable(IntPtr ptr) {
D.assert(_ptr != IntPtr.Zero);
_ptr = ptr;
}
~NativeWrapperDisposable() {
if (_ptr != IntPtr.Zero) {
DisposePtr(_ptr);
_ptr = IntPtr.Zero;
}
}
protected abstract void DisposePtr(IntPtr ptr);
public void Dispose() {
if (_ptr != IntPtr.Zero) {
DisposePtr(_ptr);
_ptr = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
}
}

3
com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs.meta


fileFormatVersion: 2
guid: 0e40c1ae020d48bdac96e08bda834742
timeCreated: 1595649319

1001
com.unity.uiwidgets/Runtime/ui2/painting/painting.cs
文件差异内容过多而无法显示
查看文件

3
com.unity.uiwidgets/Runtime/ui2/painting/painting.cs.meta


fileFormatVersion: 2
guid: 11d086e8a07d458fbaaacf137fe9ff60
timeCreated: 1595649319

61
engine/src/common/settings.cc


#include "settings.h"
#include <sstream>
namespace uiwidgets {
constexpr FrameTiming::Phase FrameTiming::kPhases[FrameTiming::kCount];
Settings::Settings() = default;
Settings::Settings(const Settings& other) = default;
Settings::~Settings() = default;
std::string Settings::ToString() const {
std::stringstream stream;
stream << "Settings: " << std::endl;
stream << "vm_snapshot_data_path: " << vm_snapshot_data_path << std::endl;
stream << "vm_snapshot_instr_path: " << vm_snapshot_instr_path << std::endl;
stream << "isolate_snapshot_data_path: " << isolate_snapshot_data_path
<< std::endl;
stream << "isolate_snapshot_instr_path: " << isolate_snapshot_instr_path
<< std::endl;
stream << "application_library_path:" << std::endl;
for (const auto& path : application_library_path) {
stream << " " << path << std::endl;
}
stream << "temp_directory_path: " << temp_directory_path << std::endl;
stream << "dart_flags:" << std::endl;
for (const auto& dart_flag : dart_flags) {
stream << " " << dart_flag << std::endl;
}
stream << "start_paused: " << start_paused << std::endl;
stream << "trace_skia: " << trace_skia << std::endl;
stream << "trace_startup: " << trace_startup << std::endl;
stream << "trace_systrace: " << trace_systrace << std::endl;
stream << "dump_skp_on_shader_compilation: " << dump_skp_on_shader_compilation
<< std::endl;
stream << "cache_sksl: " << cache_sksl << std::endl;
stream << "endless_trace_buffer: " << endless_trace_buffer << std::endl;
stream << "enable_dart_profiling: " << enable_dart_profiling << std::endl;
stream << "disable_dart_asserts: " << disable_dart_asserts << std::endl;
stream << "enable_observatory: " << enable_observatory << std::endl;
stream << "observatory_host: " << observatory_host << std::endl;
stream << "observatory_port: " << observatory_port << std::endl;
stream << "use_test_fonts: " << use_test_fonts << std::endl;
stream << "enable_software_rendering: " << enable_software_rendering
<< std::endl;
stream << "log_tag: " << log_tag << std::endl;
stream << "icu_initialization_required: " << icu_initialization_required
<< std::endl;
stream << "icu_data_path: " << icu_data_path << std::endl;
stream << "assets_dir: " << assets_dir << std::endl;
stream << "assets_path: " << assets_path << std::endl;
stream << "frame_rasterized_callback set: " << !!frame_rasterized_callback
<< std::endl;
stream << "old_gen_heap_size: " << old_gen_heap_size << std::endl;
return stream.str();
}
} // namespace uiwidgets

204
engine/src/common/settings.h


#pragma once
#include <fcntl.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "flutter/fml/closure.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/fml/unique_fd.h"
namespace uiwidgets {
class FrameTiming {
public:
enum Phase { kBuildStart, kBuildFinish, kRasterStart, kRasterFinish, kCount };
static constexpr Phase kPhases[kCount] = {kBuildStart, kBuildFinish,
kRasterStart, kRasterFinish};
fml::TimePoint Get(Phase phase) const { return data_[phase]; }
fml::TimePoint Set(Phase phase, fml::TimePoint value) {
return data_[phase] = value;
}
private:
fml::TimePoint data_[kCount];
};
using TaskObserverAdd =
std::function<void(intptr_t /* key */, fml::closure /* callback */)>;
using TaskObserverRemove = std::function<void(intptr_t /* key */)>;
using UnhandledExceptionCallback =
std::function<bool(const std::string& /* error */,
const std::string& /* stack trace */)>;
// TODO(chinmaygarde): Deprecate all the "path" struct members in favor of the
// callback that generates the mapping from these paths.
// https://github.com/flutter/flutter/issues/26783
using MappingCallback = std::function<std::unique_ptr<fml::Mapping>(void)>;
using MappingsCallback =
std::function<std::vector<std::unique_ptr<const fml::Mapping>>(void)>;
using FrameRasterizedCallback = std::function<void(const FrameTiming&)>;
struct Settings {
Settings();
Settings(const Settings& other);
~Settings();
// VM settings
std::string vm_snapshot_data_path; // deprecated
MappingCallback vm_snapshot_data;
std::string vm_snapshot_instr_path; // deprecated
MappingCallback vm_snapshot_instr;
std::string isolate_snapshot_data_path; // deprecated
MappingCallback isolate_snapshot_data;
std::string isolate_snapshot_instr_path; // deprecated
MappingCallback isolate_snapshot_instr;
// Returns the Mapping to a kernel buffer which contains sources for dart:*
// libraries.
MappingCallback dart_library_sources_kernel;
// Path to a library containing the application's compiled Dart code.
// This is a vector so that the embedder can provide fallback paths in
// case the primary path to the library can not be loaded.
std::vector<std::string> application_library_path;
std::string application_kernel_asset; // deprecated
std::string application_kernel_list_asset; // deprecated
MappingsCallback application_kernels;
std::string temp_directory_path;
std::vector<std::string> dart_flags;
// Arguments passed as a List<String> to Dart's entrypoint function.
std::vector<std::string> dart_entrypoint_args;
// Isolate settings
bool enable_checked_mode = false;
bool start_paused = false;
bool trace_skia = false;
std::string trace_whitelist;
bool trace_startup = false;
bool trace_systrace = false;
bool dump_skp_on_shader_compilation = false;
bool cache_sksl = false;
bool endless_trace_buffer = false;
bool enable_dart_profiling = false;
bool disable_dart_asserts = false;
// Used as the script URI in debug messages. Does not affect how the Dart code
// is executed.
std::string advisory_script_uri = "main.dart";
// Used as the script entrypoint in debug messages. Does not affect how the
// Dart code is executed.
std::string advisory_script_entrypoint = "main";
// Observatory settings
// Whether the Dart VM service should be enabled.
bool enable_observatory = false;
// The IP address to which the Dart VM service is bound.
std::string observatory_host;
// The port to which the Dart VM service is bound. When set to `0`, a free
// port will be automatically selected by the OS. A message is logged on the
// target indicating the URL at which the VM service can be accessed.
uint32_t observatory_port = 0;
// Determines whether an authentication code is required to communicate with
// the VM service.
bool disable_service_auth_codes = true;
// Determine whether the vmservice should fallback to automatic port selection
// after failing to bind to a specified port.
bool enable_service_port_fallback = false;
// Font settings
bool use_test_fonts = false;
// All shells in the process share the same VM. The last shell to shutdown
// should typically shut down the VM as well. However, applications depend on
// the behavior of "warming-up" the VM by creating a shell that does not do
// anything. This used to work earlier when the VM could not be shut down (and
// hence never was). Shutting down the VM now breaks such assumptions in
// existing embedders. To keep this behavior consistent and allow existing
// embedders the chance to migrate, this flag defaults to true. Any shell
// launched with this flag set to true will leak the VM in the process. There
// is no way to shut down the VM once such a shell has been started. All
// shells in the platform (via their embedding APIs) should cooperate to make
// sure this flag is never set if they want the VM to shutdown and free all
// associated resources.
bool leak_vm = true;
// Engine settings
TaskObserverAdd task_observer_add;
TaskObserverRemove task_observer_remove;
// The main isolate is current when this callback is made. This is a good spot
// to perform native Dart bindings for libraries not built in.
fml::closure root_isolate_create_callback;
fml::closure isolate_create_callback;
// The isolate is not current and may have already been destroyed when this
// call is made.
fml::closure root_isolate_shutdown_callback;
fml::closure isolate_shutdown_callback;
// The callback made on the UI thread in an isolate scope when the engine
// detects that the framework is idle. The VM also uses this time to perform
// tasks suitable when idling. Due to this, embedders are still advised to be
// as fast as possible in returning from this callback. Long running
// operations in this callback do have the capability of introducing jank.
std::function<void(int64_t)> idle_notification_callback;
// A callback given to the embedder to react to unhandled exceptions in the
// running Flutter application. This callback is made on an internal engine
// managed thread and embedders must re-thread as necessary. Performing
// blocking calls in this callback will cause applications to jank.
UnhandledExceptionCallback unhandled_exception_callback;
bool enable_software_rendering = false;
bool skia_deterministic_rendering_on_cpu = false;
bool verbose_logging = false;
std::string log_tag = "flutter";
// The icu_initialization_required setting does not have a corresponding
// switch because it is intended to be decided during build time, not runtime.
// Some companies apply source modification here because their build system
// brings its own ICU data files.
bool icu_initialization_required = true;
std::string icu_data_path;
MappingCallback icu_mapper;
// Assets settings
fml::UniqueFD::element_type assets_dir =
fml::UniqueFD::traits_type::InvalidValue();
std::string assets_path;
// Callback to handle the timings of a rasterized frame. This is called as
// soon as a frame is rasterized.
FrameRasterizedCallback frame_rasterized_callback;
// This data will be available to the isolate immediately on launch via the
// Window.getPersistentIsolateData callback. This is meant for information
// that the isolate cannot request asynchronously (platform messages can be
// used for that purpose). This data is held for the lifetime of the shell and
// is available on isolate restarts in the shell instance. Due to this,
// the buffer must be as small as possible.
std::shared_ptr<const fml::Mapping> persistent_isolate_data;
/// Max size of old gen heap size in MB, or 0 for unlimited, -1 for default
/// value.
///
/// See also:
/// https://github.com/dart-lang/sdk/blob/ca64509108b3e7219c50d6c52877c85ab6a35ff2/runtime/vm/flag_list.h#L150
int64_t old_gen_heap_size = -1;
std::string ToString() const;
};
} // namespace uiwidgets

46
engine/src/common/task_runners.cc


#include "task_runners.h"
#include <utility>
namespace uiwidgets {
TaskRunners::TaskRunners(std::string label,
fml::RefPtr<fml::TaskRunner> platform,
fml::RefPtr<fml::TaskRunner> raster,
fml::RefPtr<fml::TaskRunner> ui,
fml::RefPtr<fml::TaskRunner> io)
: label_(std::move(label)),
platform_(std::move(platform)),
raster_(std::move(raster)),
ui_(std::move(ui)),
io_(std::move(io)) {}
TaskRunners::TaskRunners(const TaskRunners& other) = default;
TaskRunners::~TaskRunners() = default;
const std::string& TaskRunners::GetLabel() const {
return label_;
}
fml::RefPtr<fml::TaskRunner> TaskRunners::GetPlatformTaskRunner() const {
return platform_;
}
fml::RefPtr<fml::TaskRunner> TaskRunners::GetUITaskRunner() const {
return ui_;
}
fml::RefPtr<fml::TaskRunner> TaskRunners::GetIOTaskRunner() const {
return io_;
}
fml::RefPtr<fml::TaskRunner> TaskRunners::GetRasterTaskRunner() const {
return raster_;
}
bool TaskRunners::IsValid() const {
return platform_ && raster_ && ui_ && io_;
}
} // namespace uiwidgets

42
engine/src/common/task_runners.h


#pragma once
#include <string>
#include "flutter/fml/macros.h"
#include "flutter/fml/task_runner.h"
namespace uiwidgets {
class TaskRunners {
public:
TaskRunners(std::string label,
fml::RefPtr<fml::TaskRunner> platform,
fml::RefPtr<fml::TaskRunner> raster,
fml::RefPtr<fml::TaskRunner> ui,
fml::RefPtr<fml::TaskRunner> io);
TaskRunners(const TaskRunners& other);
~TaskRunners();
const std::string& GetLabel() const;
fml::RefPtr<fml::TaskRunner> GetPlatformTaskRunner() const;
fml::RefPtr<fml::TaskRunner> GetUITaskRunner() const;
fml::RefPtr<fml::TaskRunner> GetIOTaskRunner() const;
fml::RefPtr<fml::TaskRunner> GetRasterTaskRunner() const;
bool IsValid() const;
private:
const std::string label_;
fml::RefPtr<fml::TaskRunner> platform_;
fml::RefPtr<fml::TaskRunner> raster_;
fml::RefPtr<fml::TaskRunner> ui_;
fml::RefPtr<fml::TaskRunner> io_;
};
} // namespace uiwidgets

104
engine/src/flow/compositor_context.cc


#include "flow/compositor_context.h"
#include "flow/layers/layer_tree.h"
#include "include/core/SkCanvas.h"
namespace uiwidgets {
CompositorContext::CompositorContext(fml::Milliseconds frame_budget)
: raster_time_(frame_budget), ui_time_(frame_budget) {}
CompositorContext::~CompositorContext() = default;
void CompositorContext::BeginFrame(ScopedFrame& frame,
bool enable_instrumentation) {
if (enable_instrumentation) {
frame_count_.Increment();
raster_time_.Start();
}
}
void CompositorContext::EndFrame(ScopedFrame& frame,
bool enable_instrumentation) {
raster_cache_.SweepAfterFrame();
if (enable_instrumentation) {
raster_time_.Stop();
}
}
std::unique_ptr<CompositorContext::ScopedFrame> CompositorContext::AcquireFrame(
GrContext* gr_context, SkCanvas* canvas,
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation, bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
return std::make_unique<ScopedFrame>(
*this, gr_context, canvas, view_embedder, root_surface_transformation,
instrumentation_enabled, surface_supports_readback, raster_thread_merger);
}
CompositorContext::ScopedFrame::ScopedFrame(
CompositorContext& context, GrContext* gr_context, SkCanvas* canvas,
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation, bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger)
: context_(context),
gr_context_(gr_context),
canvas_(canvas),
view_embedder_(view_embedder),
root_surface_transformation_(root_surface_transformation),
instrumentation_enabled_(instrumentation_enabled),
surface_supports_readback_(surface_supports_readback),
raster_thread_merger_(raster_thread_merger) {
context_.BeginFrame(*this, instrumentation_enabled_);
}
CompositorContext::ScopedFrame::~ScopedFrame() {
context_.EndFrame(*this, instrumentation_enabled_);
}
RasterStatus CompositorContext::ScopedFrame::Raster(
LayerTree& layer_tree, bool ignore_raster_cache) {
TRACE_EVENT0("uiwidgets", "CompositorContext::ScopedFrame::Raster");
bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache);
bool needs_save_layer = root_needs_readback && !surface_supports_readback();
PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess;
if (view_embedder_ && raster_thread_merger_) {
post_preroll_result =
view_embedder_->PostPrerollAction(raster_thread_merger_);
}
if (post_preroll_result == PostPrerollResult::kResubmitFrame) {
return RasterStatus::kResubmit;
}
// Clearing canvas after preroll reduces one render target switch when preroll
// paints some raster cache.
if (canvas()) {
if (needs_save_layer) {
FML_LOG(INFO) << "Using SaveLayer to protect non-readback surface";
SkRect bounds = SkRect::Make(layer_tree.frame_size());
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas()->saveLayer(&bounds, &paint);
}
canvas()->clear(SK_ColorTRANSPARENT);
}
layer_tree.Paint(*this, ignore_raster_cache);
if (canvas() && needs_save_layer) {
canvas()->restore();
}
return RasterStatus::kSuccess;
}
void CompositorContext::OnGrContextCreated() {
texture_registry_.OnGrContextCreated();
raster_cache_.Clear();
}
void CompositorContext::OnGrContextDestroyed() {
texture_registry_.OnGrContextDestroyed();
raster_cache_.Clear();
}
} // namespace uiwidgets

114
engine/src/flow/compositor_context.h


#pragma once
#include <memory>
#include <string>
#include "flow/embedded_views.h"
#include "flow/instrumentation.h"
#include "flow/raster_cache.h"
#include "flow/texture.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/raster_thread_merger.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPictureRecorder.h"
namespace uiwidgets {
class LayerTree;
enum class RasterStatus {
// Frame has successfully rasterized.
kSuccess,
// Frame needs to be resubmitted for rasterization. This is
// currently only called when thread configuration change occurs.
kResubmit,
// Frame has been successfully rasterized, but "there are additional items in
// the pipeline waiting to be consumed. This is currently
// only called when thread configuration change occurs.
kEnqueuePipeline,
// Failed to rasterize the frame.
kFailed
};
class CompositorContext {
public:
class ScopedFrame {
public:
ScopedFrame(CompositorContext& context, GrContext* gr_context,
SkCanvas* canvas, ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled, bool surface_supports_readback,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger);
virtual ~ScopedFrame();
SkCanvas* canvas() { return canvas_; }
ExternalViewEmbedder* view_embedder() { return view_embedder_; }
CompositorContext& context() const { return context_; }
const SkMatrix& root_surface_transformation() const {
return root_surface_transformation_;
}
bool surface_supports_readback() { return surface_supports_readback_; }
GrContext* gr_context() const { return gr_context_; }
virtual RasterStatus Raster(LayerTree& layer_tree,
bool ignore_raster_cache);
private:
CompositorContext& context_;
GrContext* gr_context_;
SkCanvas* canvas_;
ExternalViewEmbedder* view_embedder_;
const SkMatrix& root_surface_transformation_;
const bool instrumentation_enabled_;
const bool surface_supports_readback_;
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger_;
FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame);
};
CompositorContext(fml::Milliseconds frame_budget = fml::kDefaultFrameBudget);
virtual ~CompositorContext();
virtual std::unique_ptr<ScopedFrame> AcquireFrame(
GrContext* gr_context, SkCanvas* canvas,
ExternalViewEmbedder* view_embedder,
const SkMatrix& root_surface_transformation, bool instrumentation_enabled,
bool surface_supports_readback,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger);
void OnGrContextCreated();
void OnGrContextDestroyed();
RasterCache& raster_cache() { return raster_cache_; }
TextureRegistry& texture_registry() { return texture_registry_; }
const Counter& frame_count() const { return frame_count_; }
const Stopwatch& raster_time() const { return raster_time_; }
Stopwatch& ui_time() { return ui_time_; }
private:
RasterCache raster_cache_;
TextureRegistry texture_registry_;
Counter frame_count_;
Stopwatch raster_time_;
Stopwatch ui_time_;
void BeginFrame(ScopedFrame& frame, bool enable_instrumentation);
void EndFrame(ScopedFrame& frame, bool enable_instrumentation);
FML_DISALLOW_COPY_AND_ASSIGN(CompositorContext);
};
} // namespace uiwidgets

49
engine/src/flow/embedded_views.cc


#include "flow/embedded_views.h"
namespace uiwidgets {
bool ExternalViewEmbedder::SubmitFrame(GrContext* context,
SkCanvas* background_canvas) {
return false;
};
void ExternalViewEmbedder::FinishFrame(){};
void MutatorsStack::PushClipRect(const SkRect& rect) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rect);
vector_.push_back(element);
};
void MutatorsStack::PushClipRRect(const SkRRect& rrect) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rrect);
vector_.push_back(element);
};
void MutatorsStack::PushClipPath(const SkPath& path) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(path);
vector_.push_back(element);
};
void MutatorsStack::PushTransform(const SkMatrix& matrix) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(matrix);
vector_.push_back(element);
};
void MutatorsStack::PushOpacity(const int& alpha) {
std::shared_ptr<Mutator> element = std::make_shared<Mutator>(alpha);
vector_.push_back(element);
};
void MutatorsStack::Pop() { vector_.pop_back(); };
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator
MutatorsStack::Top() const {
return vector_.rend();
};
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator
MutatorsStack::Bottom() const {
return vector_.rbegin();
};
} // namespace uiwidgets

253
engine/src/flow/embedded_views.h


#pragma once
#include <vector>
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/raster_thread_merger.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkSize.h"
#include "include/core/SkSurface.h"
namespace uiwidgets {
// TODO(chinmaygarde): Make these enum names match the style guide.
enum MutatorType { clip_rect, clip_rrect, clip_path, transform, opacity };
// Stores mutation information like clipping or transform.
//
// The `type` indicates the type of the mutation: clip_rect, transform and etc.
// Each `type` is paired with an object that supports the mutation. For example,
// if the `type` is clip_rect, `rect()` is used the represent the rect to be
// clipped. One mutation object must only contain one type of mutation.
class Mutator {
public:
Mutator(const Mutator& other) {
type_ = other.type_;
switch (other.type_) {
case clip_rect:
rect_ = other.rect_;
break;
case clip_rrect:
rrect_ = other.rrect_;
break;
case clip_path:
path_ = new SkPath(*other.path_);
break;
case transform:
matrix_ = other.matrix_;
break;
case opacity:
alpha_ = other.alpha_;
break;
default:
break;
}
}
explicit Mutator(const SkRect& rect) : type_(clip_rect), rect_(rect) {}
explicit Mutator(const SkRRect& rrect) : type_(clip_rrect), rrect_(rrect) {}
explicit Mutator(const SkPath& path)
: type_(clip_path), path_(new SkPath(path)) {}
explicit Mutator(const SkMatrix& matrix)
: type_(transform), matrix_(matrix) {}
explicit Mutator(const int& alpha) : type_(opacity), alpha_(alpha) {}
const MutatorType& GetType() const { return type_; }
const SkRect& GetRect() const { return rect_; }
const SkRRect& GetRRect() const { return rrect_; }
const SkPath& GetPath() const { return *path_; }
const SkMatrix& GetMatrix() const { return matrix_; }
const int& GetAlpha() const { return alpha_; }
float GetAlphaFloat() const { return (alpha_ / 255.0); }
bool operator==(const Mutator& other) const {
if (type_ != other.type_) {
return false;
}
switch (type_) {
case clip_rect:
return rect_ == other.rect_;
case clip_rrect:
return rrect_ == other.rrect_;
case clip_path:
return *path_ == *other.path_;
case transform:
return matrix_ == other.matrix_;
case opacity:
return alpha_ == other.alpha_;
}
return false;
}
bool operator!=(const Mutator& other) const { return !operator==(other); }
bool IsClipType() {
return type_ == clip_rect || type_ == clip_rrect || type_ == clip_path;
}
~Mutator() {
if (type_ == clip_path) {
delete path_;
}
};
private:
MutatorType type_;
union {
SkRect rect_;
SkRRect rrect_;
SkMatrix matrix_;
SkPath* path_;
int alpha_;
};
}; // Mutator
// A stack of mutators that can be applied to an embedded platform view.
//
// The stack may include mutators like transforms and clips, each mutator
// applies to all the mutators that are below it in the stack and to the
// embedded view.
//
// For example consider the following stack: [T1, T2, T3], where T1 is the top
// of the stack and T3 is the bottom of the stack. Applying this mutators stack
// to a platform view P1 will result in T1(T2(T2(P1))).
class MutatorsStack {
public:
MutatorsStack() = default;
void PushClipRect(const SkRect& rect);
void PushClipRRect(const SkRRect& rrect);
void PushClipPath(const SkPath& path);
void PushTransform(const SkMatrix& matrix);
void PushOpacity(const int& alpha);
// Removes the `Mutator` on the top of the stack
// and destroys it.
void Pop();
// Returns an iterator pointing to the top of the stack.
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator Top()
const;
// Returns an iterator pointing to the bottom of the stack.
const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator Bottom()
const;
bool is_empty() const { return vector_.empty(); }
bool operator==(const MutatorsStack& other) const {
if (vector_.size() != other.vector_.size()) {
return false;
}
for (size_t i = 0; i < vector_.size(); i++) {
if (*vector_[i] != *other.vector_[i]) {
return false;
}
}
return true;
}
bool operator==(const std::vector<Mutator>& other) const {
if (vector_.size() != other.size()) {
return false;
}
for (size_t i = 0; i < vector_.size(); i++) {
if (*vector_[i] != other[i]) {
return false;
}
}
return true;
}
bool operator!=(const MutatorsStack& other) const {
return !operator==(other);
}
bool operator!=(const std::vector<Mutator>& other) const {
return !operator==(other);
}
private:
std::vector<std::shared_ptr<Mutator>> vector_;
}; // MutatorsStack
class EmbeddedViewParams {
public:
EmbeddedViewParams() = default;
EmbeddedViewParams(const EmbeddedViewParams& other) {
offsetPixels = other.offsetPixels;
sizePoints = other.sizePoints;
mutatorsStack = other.mutatorsStack;
};
SkPoint offsetPixels;
SkSize sizePoints;
MutatorsStack mutatorsStack;
bool operator==(const EmbeddedViewParams& other) const {
return offsetPixels == other.offsetPixels &&
sizePoints == other.sizePoints &&
mutatorsStack == other.mutatorsStack;
}
};
enum class PostPrerollResult { kResubmitFrame, kSuccess };
// Facilitates embedding of platform views within the flow layer tree.
//
// Used on iOS and on embedded platforms that provide a system compositor
// as part of the project arguments.
class ExternalViewEmbedder {
// TODO(cyanglaz): Make embedder own the `EmbeddedViewParams`.
public:
ExternalViewEmbedder() = default;
virtual ~ExternalViewEmbedder() = default;
// Usually, the root canvas is not owned by the view embedder. However, if
// the view embedder wants to provide a canvas to the rasterizer, it may
// return one here. This canvas takes priority over the canvas materialized
// from the on-screen render target.
virtual SkCanvas* GetRootCanvas() = 0;
// Call this in-lieu of |SubmitFrame| to clear pre-roll state and
// sets the stage for the next pre-roll.
virtual void CancelFrame() = 0;
virtual void BeginFrame(SkISize frame_size, GrContext* context,
double device_pixel_ratio) = 0;
virtual void PrerollCompositeEmbeddedView(
int view_id, std::unique_ptr<EmbeddedViewParams> params) = 0;
// This needs to get called after |Preroll| finishes on the layer tree.
// Returns kResubmitFrame if the frame needs to be processed again, this is
// after it does any requisite tasks needed to bring itself to a valid state.
// Returns kSuccess if the view embedder is already in a valid state.
virtual PostPrerollResult PostPrerollAction(
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
return PostPrerollResult::kSuccess;
}
virtual std::vector<SkCanvas*> GetCurrentCanvases() = 0;
// Must be called on the UI thread.
virtual SkCanvas* CompositeEmbeddedView(int view_id) = 0;
virtual bool SubmitFrame(GrContext* context, SkCanvas* background_canvas);
// This is called after submitting the embedder frame and the surface frame.
virtual void FinishFrame();
FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder);
}; // ExternalViewEmbedder
} // namespace uiwidgets

312
engine/src/flow/instrumentation.cc


#include "flow/instrumentation.h"
#include <algorithm>
#include <limits>
#include "include/core/SkPath.h"
#include "include/core/SkSurface.h"
namespace uiwidgets {
static const size_t kMaxSamples = 120;
static const size_t kMaxFrameMarkers = 8;
Stopwatch::Stopwatch(fml::Milliseconds frame_budget)
: start_(fml::TimePoint::Now()), current_sample_(0) {
const fml::TimeDelta delta = fml::TimeDelta::Zero();
laps_.resize(kMaxSamples, delta);
cache_dirty_ = true;
prev_drawn_sample_index_ = 0;
frame_budget_ = frame_budget;
}
Stopwatch::~Stopwatch() = default;
void Stopwatch::Start() {
start_ = fml::TimePoint::Now();
current_sample_ = (current_sample_ + 1) % kMaxSamples;
}
void Stopwatch::Stop() {
laps_[current_sample_] = fml::TimePoint::Now() - start_;
}
void Stopwatch::SetLapTime(const fml::TimeDelta& delta) {
current_sample_ = (current_sample_ + 1) % kMaxSamples;
laps_[current_sample_] = delta;
}
const fml::TimeDelta& Stopwatch::LastLap() const {
return laps_[(current_sample_ - 1) % kMaxSamples];
}
double Stopwatch::UnitFrameInterval(double raster_time_ms) const {
return raster_time_ms / frame_budget_.count();
}
double Stopwatch::UnitHeight(double raster_time_ms,
double max_unit_interval) const {
double unitHeight = UnitFrameInterval(raster_time_ms) / max_unit_interval;
if (unitHeight > 1.0) unitHeight = 1.0;
return unitHeight;
}
fml::TimeDelta Stopwatch::MaxDelta() const {
fml::TimeDelta max_delta;
for (size_t i = 0; i < kMaxSamples; i++) {
if (laps_[i] > max_delta) max_delta = laps_[i];
}
return max_delta;
}
fml::TimeDelta Stopwatch::AverageDelta() const {
fml::TimeDelta sum; // default to 0
for (size_t i = 0; i < kMaxSamples; i++) {
sum = sum + laps_[i];
}
return sum / kMaxSamples;
}
// Initialize the SkSurface for drawing into. Draws the base background and any
// timing data from before the initial Visualize() call.
void Stopwatch::InitVisualizeSurface(const SkRect& rect) const {
if (!cache_dirty_) {
return;
}
cache_dirty_ = false;
// TODO(garyq): Use a GPU surface instead of a CPU surface.
visualize_cache_surface_ =
SkSurface::MakeRasterN32Premul(rect.width(), rect.height());
SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();
// Establish the graph position.
const SkScalar x = 0;
const SkScalar y = 0;
const SkScalar width = rect.width();
const SkScalar height = rect.height();
SkPaint paint;
paint.setColor(0x99FFFFFF);
cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint);
// Scale the graph to show frame times up to those that are 3 times the frame
// time.
const double one_frame_ms = frame_budget_.count();
const double max_interval = one_frame_ms * 3.0;
const double max_unit_interval = UnitFrameInterval(max_interval);
// Draw the old data to initially populate the graph.
// Prepare a path for the data. We start at the height of the last point, so
// it looks like we wrap around
SkPath path;
path.setIsVolatile(true);
path.moveTo(x, height);
path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(),
max_unit_interval)));
double unit_x;
double unit_next_x = 0.0;
for (size_t i = 0; i < kMaxSamples; i += 1) {
unit_x = unit_next_x;
unit_next_x = (static_cast<double>(i + 1) / kMaxSamples);
const double sample_y =
y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(),
max_unit_interval));
path.lineTo(x + width * unit_x, sample_y);
path.lineTo(x + width * unit_next_x, sample_y);
}
path.lineTo(
width,
y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(),
max_unit_interval)));
path.lineTo(width, height);
path.close();
// Draw the graph.
paint.setColor(0xAA0000FF);
cache_canvas->drawPath(path, paint);
}
void Stopwatch::Visualize(SkCanvas& canvas, const SkRect& rect) const {
// Initialize visualize cache if it has not yet been initialized.
InitVisualizeSurface(rect);
SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();
SkPaint paint;
// Establish the graph position.
const SkScalar x = 0;
const SkScalar y = 0;
const SkScalar width = rect.width();
const SkScalar height = rect.height();
// Scale the graph to show frame times up to those that are 3 times the frame
// time.
const double one_frame_ms = frame_budget_.count();
const double max_interval = one_frame_ms * 3.0;
const double max_unit_interval = UnitFrameInterval(max_interval);
const double sample_unit_width = (1.0 / kMaxSamples);
// Draw vertical replacement bar to erase old/stale pixels.
paint.setColor(0x99FFFFFF);
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setBlendMode(SkBlendMode::kSrc);
double sample_x =
x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples);
const auto eraser_rect = SkRect::MakeLTRB(
sample_x, y, sample_x + width * sample_unit_width, height);
cache_canvas->drawRect(eraser_rect, paint);
// Draws blue timing bar for new data.
paint.setColor(0xAA0000FF);
paint.setBlendMode(SkBlendMode::kSrcOver);
const auto bar_rect = SkRect::MakeLTRB(
sample_x,
y + height * (1.0 -
UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1
: current_sample_ - 1]
.ToMillisecondsF(),
max_unit_interval)),
sample_x + width * sample_unit_width, height);
cache_canvas->drawRect(bar_rect, paint);
// Draw horizontal frame markers.
paint.setStrokeWidth(0); // hairline
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setColor(0xCC000000);
if (max_interval > one_frame_ms) {
// Paint the horizontal markers
size_t frame_marker_count =
static_cast<size_t>(max_interval / one_frame_ms);
// Limit the number of markers displayed. After a certain point, the graph
// becomes crowded
if (frame_marker_count > kMaxFrameMarkers) frame_marker_count = 1;
for (size_t frame_index = 0; frame_index < frame_marker_count;
frame_index++) {
const double frame_height =
height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) /
max_unit_interval));
cache_canvas->drawLine(x, y + frame_height, width, y + frame_height,
paint);
}
}
// Paint the vertical marker for the current frame.
// We paint it over the current frame, not after it, because when we
// paint this we don't yet have all the times for the current frame.
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setBlendMode(SkBlendMode::kSrcOver);
if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) {
// budget exceeded
paint.setColor(SK_ColorRED);
} else {
// within budget
paint.setColor(SK_ColorGREEN);
}
sample_x = x + width * (static_cast<double>(current_sample_) / kMaxSamples);
const auto marker_rect = SkRect::MakeLTRB(
sample_x, y, sample_x + width * sample_unit_width, height);
cache_canvas->drawRect(marker_rect, paint);
prev_drawn_sample_index_ = current_sample_;
// Draw the cached surface onto the output canvas.
paint.reset();
visualize_cache_surface_->draw(&canvas, rect.x(), rect.y(), &paint);
}
CounterValues::CounterValues() : current_sample_(kMaxSamples - 1) {
values_.resize(kMaxSamples, 0);
}
CounterValues::~CounterValues() = default;
void CounterValues::Add(int64_t value) {
current_sample_ = (current_sample_ + 1) % kMaxSamples;
values_[current_sample_] = value;
}
void CounterValues::Visualize(SkCanvas& canvas, const SkRect& rect) const {
size_t max_bytes = GetMaxValue();
if (max_bytes == 0) {
// The backend for this counter probably did not fill in any values.
return;
}
size_t min_bytes = GetMinValue();
SkPaint paint;
// Paint the background.
paint.setColor(0x99FFFFFF);
canvas.drawRect(rect, paint);
// Establish the graph position.
const SkScalar x = rect.x();
const SkScalar y = rect.y();
const SkScalar width = rect.width();
const SkScalar height = rect.height();
const SkScalar bottom = y + height;
const SkScalar right = x + width;
// Prepare a path for the data.
SkPath path;
path.moveTo(x, bottom);
for (size_t i = 0; i < kMaxSamples; ++i) {
int64_t current_bytes = values_[i];
double ratio =
(double)(current_bytes - min_bytes) / (max_bytes - min_bytes);
path.lineTo(x + (((double)(i) / (double)kMaxSamples) * width),
y + ((1.0 - ratio) * height));
}
path.rLineTo(100, 0);
path.lineTo(right, bottom);
path.close();
// Draw the graph.
paint.setColor(0xAA0000FF);
canvas.drawPath(path, paint);
// Paint the vertical marker for the current frame.
const double sample_unit_width = (1.0 / kMaxSamples);
const double sample_margin_unit_width = sample_unit_width / 6.0;
const double sample_margin_width = width * sample_margin_unit_width;
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setColor(SK_ColorGRAY);
double sample_x =
x + width * (static_cast<double>(current_sample_) / kMaxSamples) -
sample_margin_width;
const auto marker_rect = SkRect::MakeLTRB(
sample_x, y,
sample_x + width * sample_unit_width + sample_margin_width * 2, bottom);
canvas.drawRect(marker_rect, paint);
}
int64_t CounterValues::GetCurrentValue() const {
return values_[current_sample_];
}
int64_t CounterValues::GetMaxValue() const {
auto max = std::numeric_limits<int64_t>::min();
for (size_t i = 0; i < kMaxSamples; ++i) {
max = std::max<int64_t>(max, values_[i]);
}
return max;
}
int64_t CounterValues::GetMinValue() const {
auto min = std::numeric_limits<int64_t>::max();
for (size_t i = 0; i < kMaxSamples; ++i) {
min = std::min<int64_t>(min, values_[i]);
}
return min;
}
} // namespace uiwidgets

94
engine/src/flow/instrumentation.h


#pragma once
#include <vector>
#include "flutter/fml/macros.h"
#include "flutter/fml/time/time_delta.h"
#include "flutter/fml/time/time_point.h"
#include "include/core/SkCanvas.h"
namespace uiwidgets {
class Stopwatch {
public:
Stopwatch(fml::Milliseconds frame_budget = fml::kDefaultFrameBudget);
~Stopwatch();
const fml::TimeDelta& LastLap() const;
fml::TimeDelta CurrentLap() const { return fml::TimePoint::Now() - start_; }
fml::TimeDelta MaxDelta() const;
fml::TimeDelta AverageDelta() const;
void InitVisualizeSurface(const SkRect& rect) const;
void Visualize(SkCanvas& canvas, const SkRect& rect) const;
void Start();
void Stop();
void SetLapTime(const fml::TimeDelta& delta);
private:
inline double UnitFrameInterval(double time_ms) const;
inline double UnitHeight(double time_ms, double max_height) const;
fml::TimePoint start_;
std::vector<fml::TimeDelta> laps_;
size_t current_sample_;
fml::Milliseconds frame_budget_;
// Mutable data cache for performance optimization of the graphs. Prevents
// expensive redrawing of old data.
mutable bool cache_dirty_;
mutable sk_sp<SkSurface> visualize_cache_surface_;
mutable size_t prev_drawn_sample_index_;
FML_DISALLOW_COPY_AND_ASSIGN(Stopwatch);
};
class Counter {
public:
Counter() : count_(0) {}
size_t count() const { return count_; }
void Reset(size_t count = 0) { count_ = count; }
void Increment(size_t count = 1) { count_ += count; }
private:
size_t count_;
FML_DISALLOW_COPY_AND_ASSIGN(Counter);
};
class CounterValues {
public:
CounterValues();
~CounterValues();
void Add(int64_t value);
void Visualize(SkCanvas& canvas, const SkRect& rect) const;
int64_t GetCurrentValue() const;
int64_t GetMaxValue() const;
int64_t GetMinValue() const;
private:
std::vector<int64_t> values_;
size_t current_sample_;
FML_DISALLOW_COPY_AND_ASSIGN(CounterValues);
};
} // namespace uiwidgets

25
engine/src/flow/layers/backdrop_filter_layer.cc


#include "flow/layers/backdrop_filter_layer.h"
namespace uiwidgets {
BackdropFilterLayer::BackdropFilterLayer(sk_sp<SkImageFilter> filter)
: filter_(std::move(filter)) {}
void BackdropFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_));
ContainerLayer::Preroll(context, matrix);
}
void BackdropFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "BackdropFilterLayer::Paint");
FML_DCHECK(needs_painting());
Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create(
context,
SkCanvas::SaveLayerRec{&paint_bounds(), nullptr, filter_.get(), 0});
PaintChildren(context);
}
} // namespace uiwidgets

20
engine/src/flow/layers/backdrop_filter_layer.h


#include "flow/layers/container_layer.h"
#include "include/core/SkImageFilter.h"
namespace uiwidgets {
class BackdropFilterLayer : public ContainerLayer {
public:
BackdropFilterLayer(sk_sp<SkImageFilter> filter);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
sk_sp<SkImageFilter> filter_;
FML_DISALLOW_COPY_AND_ASSIGN(BackdropFilterLayer);
};
} // namespace uiwidgets

56
engine/src/flow/layers/clip_path_layer.cc


#include "flow/layers/clip_path_layer.h"
namespace uiwidgets {
ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior)
: clip_path_(clip_path), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
}
void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "ClipPathLayer::Preroll");
SkRect previous_cull_rect = context->cull_rect;
SkRect clip_path_bounds = clip_path_.getBounds();
children_inside_clip_ = context->cull_rect.intersect(clip_path_bounds);
if (children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets", "children inside clip rect");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipPath(clip_path_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
if (child_paint_bounds.intersect(clip_path_bounds)) {
set_paint_bounds(child_paint_bounds);
}
context->mutators_stack.Pop();
}
context->cull_rect = previous_cull_rect;
}
void ClipPathLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ClipPathLayer::Paint");
FML_DCHECK(needs_painting());
if (!children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets",
"children not inside clip rect, skipping");
return;
}
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->clipPath(clip_path_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
}
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}
} // namespace uiwidgets

25
engine/src/flow/layers/clip_path_layer.h


#include "flow/layers/container_layer.h"
namespace uiwidgets {
class ClipPathLayer : public ContainerLayer {
public:
ClipPathLayer(const SkPath& clip_path, Clip clip_behavior = Clip::antiAlias);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
private:
SkPath clip_path_;
Clip clip_behavior_;
bool children_inside_clip_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(ClipPathLayer);
};
} // namespace uiwidgets

55
engine/src/flow/layers/clip_rect_layer.cc


#include "flow/layers/clip_rect_layer.h"
namespace uiwidgets {
ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior)
: clip_rect_(clip_rect), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
}
void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "ClipRectLayer::Preroll");
SkRect previous_cull_rect = context->cull_rect;
children_inside_clip_ = context->cull_rect.intersect(clip_rect_);
if (children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets", "children inside clip rect");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipRect(clip_rect_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
if (child_paint_bounds.intersect(clip_rect_)) {
set_paint_bounds(child_paint_bounds);
}
context->mutators_stack.Pop();
}
context->cull_rect = previous_cull_rect;
}
void ClipRectLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ClipRectLayer::Paint");
FML_DCHECK(needs_painting());
if (!children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets",
"children not inside clip rect, skipping");
return;
}
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->clipRect(clip_rect_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr);
}
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}
} // namespace uiwidgets

26
engine/src/flow/layers/clip_rect_layer.h


#pragma once
#include "flow/layers/container_layer.h"
namespace uiwidgets {
class ClipRectLayer : public ContainerLayer {
public:
ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
private:
SkRect clip_rect_;
Clip clip_behavior_;
bool children_inside_clip_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(ClipRectLayer);
};
} // namespace uiwidgets

56
engine/src/flow/layers/clip_rrect_layer.cc


#include "flow/layers/clip_rrect_layer.h"
namespace uiwidgets {
ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior)
: clip_rrect_(clip_rrect), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
}
void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "ClipRRectLayer::Preroll");
SkRect previous_cull_rect = context->cull_rect;
SkRect clip_rrect_bounds = clip_rrect_.getBounds();
children_inside_clip_ = context->cull_rect.intersect(clip_rrect_bounds);
if (children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets", "children inside clip rect");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipRRect(clip_rrect_);
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
if (child_paint_bounds.intersect(clip_rrect_bounds)) {
set_paint_bounds(child_paint_bounds);
}
context->mutators_stack.Pop();
}
context->cull_rect = previous_cull_rect;
}
void ClipRRectLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ClipRRectLayer::Paint");
FML_DCHECK(needs_painting());
if (!children_inside_clip_) {
TRACE_EVENT_INSTANT0("uiwidgets",
"children not inside clip rect, skipping");
return;
}
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->clipRRect(clip_rrect_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
}
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
}
}
} // namespace uiwidgets

25
engine/src/flow/layers/clip_rrect_layer.h


#include "flow/layers/container_layer.h"
namespace uiwidgets {
class ClipRRectLayer : public ContainerLayer {
public:
ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
private:
SkRRect clip_rrect_;
Clip clip_behavior_;
bool children_inside_clip_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(ClipRRectLayer);
};
} // namespace uiwidgets

27
engine/src/flow/layers/color_filter_layer.cc


#include "flow/layers/color_filter_layer.h"
namespace uiwidgets {
ColorFilterLayer::ColorFilterLayer(sk_sp<SkColorFilter> filter)
: filter_(std::move(filter)) {}
void ColorFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, matrix);
}
void ColorFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ColorFilterLayer::Paint");
FML_DCHECK(needs_painting());
SkPaint paint;
paint.setColorFilter(filter_);
Layer::AutoSaveLayer save =
Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint);
PaintChildren(context);
}
} // namespace uiwidgets

21
engine/src/flow/layers/color_filter_layer.h


#pragma once
#include "flow/layers/container_layer.h"
#include "include/core/SkColorFilter.h"
namespace uiwidgets {
class ColorFilterLayer : public ContainerLayer {
public:
ColorFilterLayer(sk_sp<SkColorFilter> filter);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
sk_sp<SkColorFilter> filter_;
FML_DISALLOW_COPY_AND_ASSIGN(ColorFilterLayer);
};
} // namespace uiwidgets

64
engine/src/flow/layers/container_layer.cc


#include "flow/layers/container_layer.h"
namespace uiwidgets {
ContainerLayer::ContainerLayer() {}
void ContainerLayer::Add(std::shared_ptr<Layer> layer) {
layers_.emplace_back(std::move(layer));
}
void ContainerLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "ContainerLayer::Preroll");
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
set_paint_bounds(child_paint_bounds);
}
void ContainerLayer::Paint(PaintContext& context) const {
FML_DCHECK(needs_painting());
PaintChildren(context);
}
void ContainerLayer::PrerollChildren(PrerollContext* context,
const SkMatrix& child_matrix,
SkRect* child_paint_bounds) {
// Platform views have no children, so context->has_platform_view should
// always be false.
FML_DCHECK(!context->has_platform_view);
bool child_has_platform_view = false;
for (auto& layer : layers_) {
// Reset context->has_platform_view to false so that layers aren't treated
// as if they have a platform view based on one being previously found in a
// sibling tree.
context->has_platform_view = false;
layer->Preroll(context, child_matrix);
if (layer->needs_system_composite()) {
set_needs_system_composite(true);
}
child_paint_bounds->join(layer->paint_bounds());
child_has_platform_view =
child_has_platform_view || context->has_platform_view;
}
context->has_platform_view = child_has_platform_view;
}
void ContainerLayer::PaintChildren(PaintContext& context) const {
FML_DCHECK(needs_painting());
// Intentionally not tracing here as there should be no self-time
// and the trace event on this common function has a small overhead.
for (auto& layer : layers_) {
if (layer->needs_painting()) {
layer->Paint(context);
}
}
}
} // namespace uiwidgets

34
engine/src/flow/layers/container_layer.h


#pragma once
#include <vector>
#include "flow/layers/layer.h"
namespace uiwidgets {
class ContainerLayer : public Layer {
public:
ContainerLayer();
virtual void Add(std::shared_ptr<Layer> layer);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
const std::vector<std::shared_ptr<Layer>>& layers() const { return layers_; }
protected:
void PrerollChildren(PrerollContext* context, const SkMatrix& child_matrix,
SkRect* child_paint_bounds);
void PaintChildren(PaintContext& context) const;
// For OpacityLayer to restructure to have a single child.
void ClearChildren() { layers_.clear(); }
private:
std::vector<std::shared_ptr<Layer>> layers_;
FML_DISALLOW_COPY_AND_ASSIGN(ContainerLayer);
};
} // namespace uiwidgets

69
engine/src/flow/layers/image_filter_layer.cc


#include "flow/layers/image_filter_layer.h"
namespace uiwidgets {
ImageFilterLayer::ImageFilterLayer(sk_sp<SkImageFilter> filter)
: filter_(std::move(filter)) {}
void ImageFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "ImageFilterLayer::Preroll");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
child_paint_bounds_ = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds_);
if (filter_) {
const SkIRect filter_input_bounds = child_paint_bounds_.roundOut();
SkIRect filter_output_bounds =
filter_->filterBounds(filter_input_bounds, SkMatrix::I(),
SkImageFilter::kForward_MapDirection);
set_paint_bounds(SkRect::Make(filter_output_bounds));
} else {
set_paint_bounds(child_paint_bounds_);
}
if (!context->has_platform_view && context->raster_cache &&
SkRect::Intersects(context->cull_rect, paint_bounds())) {
SkMatrix ctm = matrix;
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
ctm = RasterCache::GetIntegralTransCTM(ctm);
#endif
context->raster_cache->Prepare(context, this, ctm);
}
}
void ImageFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ImageFilterLayer::Paint");
FML_DCHECK(needs_painting());
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
SkAutoCanvasRestore save(context.leaf_nodes_canvas, true);
context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM(
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
if (context.raster_cache) {
const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix();
RasterCacheResult layer_cache =
context.raster_cache->Get((Layer*)this, ctm);
if (layer_cache.is_valid()) {
layer_cache.draw(*context.leaf_nodes_canvas);
return;
}
}
SkPaint paint;
paint.setImageFilter(filter_);
// Normally a save_layer is sized to the current layer bounds, but in this
// case the bounds of the child may not be the same as the filtered version
// so we use the child_paint_bounds_ which were snapshotted from the
// Preroll on the children before we adjusted them based on the filter.
Layer::AutoSaveLayer save_layer =
Layer::AutoSaveLayer::Create(context, child_paint_bounds_, &paint);
PaintChildren(context);
}
} // namespace uiwidgets

23
engine/src/flow/layers/image_filter_layer.h


#pragma once
#include "flow/layers/container_layer.h"
#include "include/core/SkImageFilter.h"
namespace uiwidgets {
class ImageFilterLayer : public ContainerLayer {
public:
ImageFilterLayer(sk_sp<SkImageFilter> filter);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
sk_sp<SkImageFilter> filter_;
SkRect child_paint_bounds_;
FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer);
};
} // namespace uiwidgets

83
engine/src/flow/layers/layer.cc


#include "flow/layers/layer.h"
#include "flow/paint_utils.h"
#include "include/core/SkColorFilter.h"
namespace uiwidgets {
Layer::Layer()
: paint_bounds_(SkRect::MakeEmpty()),
unique_id_(NextUniqueID()),
needs_system_composite_(false) {}
Layer::~Layer() = default;
uint64_t Layer::NextUniqueID() {
static std::atomic<uint64_t> nextID(1);
uint64_t id;
do {
id = nextID.fetch_add(1);
} while (id == 0); // 0 is reserved for an invalid id.
return id;
}
void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {}
Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState(
PrerollContext* preroll_context, bool save_layer_is_active,
bool layer_itself_performs_readback)
: preroll_context_(preroll_context),
save_layer_is_active_(save_layer_is_active),
layer_itself_performs_readback_(layer_itself_performs_readback) {
if (save_layer_is_active_) {
prev_surface_needs_readback_ = preroll_context_->surface_needs_readback;
preroll_context_->surface_needs_readback = false;
}
}
Layer::AutoPrerollSaveLayerState Layer::AutoPrerollSaveLayerState::Create(
PrerollContext* preroll_context, bool save_layer_is_active,
bool layer_itself_performs_readback) {
return Layer::AutoPrerollSaveLayerState(preroll_context, save_layer_is_active,
layer_itself_performs_readback);
}
Layer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() {
if (save_layer_is_active_) {
preroll_context_->surface_needs_readback =
(prev_surface_needs_readback_ || layer_itself_performs_readback_);
}
}
Layer::AutoSaveLayer::AutoSaveLayer(const PaintContext& paint_context,
const SkRect& bounds, const SkPaint* paint)
: paint_context_(paint_context), bounds_(bounds) {
paint_context_.internal_nodes_canvas->saveLayer(bounds_, paint);
}
Layer::AutoSaveLayer::AutoSaveLayer(const PaintContext& paint_context,
const SkCanvas::SaveLayerRec& layer_rec)
: paint_context_(paint_context), bounds_(*layer_rec.fBounds) {
paint_context_.internal_nodes_canvas->saveLayer(layer_rec);
}
Layer::AutoSaveLayer Layer::AutoSaveLayer::Create(
const PaintContext& paint_context, const SkRect& bounds,
const SkPaint* paint) {
return Layer::AutoSaveLayer(paint_context, bounds, paint);
}
Layer::AutoSaveLayer Layer::AutoSaveLayer::Create(
const PaintContext& paint_context,
const SkCanvas::SaveLayerRec& layer_rec) {
return Layer::AutoSaveLayer(paint_context, layer_rec);
}
Layer::AutoSaveLayer::~AutoSaveLayer() {
if (paint_context_.checkerboard_offscreen_layers) {
DrawCheckerboard(paint_context_.internal_nodes_canvas, bounds_);
}
paint_context_.internal_nodes_canvas->restore();
}
} // namespace uiwidgets

172
engine/src/flow/layers/layer.h


#pragma once
#include <memory>
#include <vector>
#include "flow/embedded_views.h"
#include "flow/instrumentation.h"
#include "flow/raster_cache.h"
#include "flow/texture.h"
#include "flutter/fml/build_config.h"
#include "flutter/fml/compiler_specific.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPicture.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/utils/SkNWayCanvas.h"
namespace uiwidgets {
static constexpr SkRect kGiantRect = SkRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F);
// This should be an exact copy of the Clip enum in painting.dart.
enum Clip { none, hardEdge, antiAlias, antiAliasWithSaveLayer };
struct PrerollContext {
RasterCache* raster_cache;
GrContext* gr_context;
ExternalViewEmbedder* view_embedder;
MutatorsStack& mutators_stack;
SkColorSpace* dst_color_space;
SkRect cull_rect;
bool surface_needs_readback;
// These allow us to paint in the end of subtree Preroll.
const Stopwatch& raster_time;
const Stopwatch& ui_time;
TextureRegistry& texture_registry;
const bool checkerboard_offscreen_layers;
// These allow us to make use of the scene metrics during Preroll.
float frame_physical_depth;
float frame_device_pixel_ratio;
// These allow us to track properties like elevation, opacity, and the
// prescence of a platform view during Preroll.
float total_elevation = 0.0f;
bool has_platform_view = false;
bool is_opaque = true;
};
// Represents a single composited layer. Created on the UI thread but then
// subquently used on the Rasterizer thread.
class Layer {
public:
Layer();
virtual ~Layer();
virtual void Preroll(PrerollContext* context, const SkMatrix& matrix);
// Used during Preroll by layers that employ a saveLayer to manage the
// PrerollContext settings with values affected by the saveLayer mechanism.
// This object must be created before calling Preroll on the children to
// set up the state for the children and then restore the state upon
// destruction.
class AutoPrerollSaveLayerState {
public:
[[nodiscard]] static AutoPrerollSaveLayerState Create(
PrerollContext* preroll_context, bool save_layer_is_active = true,
bool layer_itself_performs_readback = false);
~AutoPrerollSaveLayerState();
private:
AutoPrerollSaveLayerState(PrerollContext* preroll_context,
bool save_layer_is_active,
bool layer_itself_performs_readback);
PrerollContext* preroll_context_;
bool save_layer_is_active_;
bool layer_itself_performs_readback_;
bool prev_surface_needs_readback_;
};
struct PaintContext {
// When splitting the scene into multiple canvases (e.g when embedding
// a platform view on iOS) during the paint traversal we apply the non leaf
// flow layers to all canvases, and leaf layers just to the "current"
// canvas. Applying the non leaf layers to all canvases ensures that when
// we switch a canvas (when painting a PlatformViewLayer) the next canvas
// has the exact same state as the current canvas.
// The internal_nodes_canvas is a SkNWayCanvas which is used by non leaf
// and applies the operations to all canvases.
// The leaf_nodes_canvas is the "current" canvas and is used by leaf
// layers.
SkCanvas* internal_nodes_canvas;
SkCanvas* leaf_nodes_canvas;
GrContext* gr_context;
ExternalViewEmbedder* view_embedder;
const Stopwatch& raster_time;
const Stopwatch& ui_time;
TextureRegistry& texture_registry;
const RasterCache* raster_cache;
const bool checkerboard_offscreen_layers;
// These allow us to make use of the scene metrics during Paint.
float frame_physical_depth;
float frame_device_pixel_ratio;
};
// Calls SkCanvas::saveLayer and restores the layer upon destruction. Also
// draws a checkerboard over the layer if that is enabled in the PaintContext.
class AutoSaveLayer {
public:
[[nodiscard]] static AutoSaveLayer Create(const PaintContext& paint_context,
const SkRect& bounds,
const SkPaint* paint);
[[nodiscard]] static AutoSaveLayer Create(
const PaintContext& paint_context,
const SkCanvas::SaveLayerRec& layer_rec);
~AutoSaveLayer();
private:
AutoSaveLayer(const PaintContext& paint_context, const SkRect& bounds,
const SkPaint* paint);
AutoSaveLayer(const PaintContext& paint_context,
const SkCanvas::SaveLayerRec& layer_rec);
const PaintContext& paint_context_;
const SkRect bounds_;
};
virtual void Paint(PaintContext& context) const = 0;
bool needs_system_composite() const { return needs_system_composite_; }
void set_needs_system_composite(bool value) {
needs_system_composite_ = value;
}
const SkRect& paint_bounds() const { return paint_bounds_; }
// This must be set by the time Preroll() returns otherwise the layer will
// be assumed to have empty paint bounds (paints no content).
void set_paint_bounds(const SkRect& paint_bounds) {
paint_bounds_ = paint_bounds;
}
bool needs_painting() const { return !paint_bounds_.isEmpty(); }
uint64_t unique_id() const { return unique_id_; }
private:
SkRect paint_bounds_;
uint64_t unique_id_;
bool needs_system_composite_;
static uint64_t NextUniqueID();
FML_DISALLOW_COPY_AND_ASSIGN(Layer);
};
} // namespace uiwidgets

158
engine/src/flow/layers/layer_tree.cc


#include "flow/layers/layer_tree.h"
#include "flow/layers/layer.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkPictureRecorder.h"
#include "include/utils/SkNWayCanvas.h"
namespace uiwidgets {
LayerTree::LayerTree(const SkISize& frame_size, float frame_physical_depth,
float frame_device_pixel_ratio)
: frame_size_(frame_size),
frame_physical_depth_(frame_physical_depth),
frame_device_pixel_ratio_(frame_device_pixel_ratio),
rasterizer_tracing_threshold_(0),
checkerboard_raster_cache_images_(false),
checkerboard_offscreen_layers_(false) {}
void LayerTree::RecordBuildTime(fml::TimePoint build_start,
fml::TimePoint target_time) {
build_start_ = build_start;
target_time_ = target_time;
build_finish_ = fml::TimePoint::Now();
}
bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache) {
TRACE_EVENT0("uiwidgets", "LayerTree::Preroll");
if (!root_layer_) {
FML_LOG(ERROR) << "The scene did not specify any layers.";
return false;
}
SkColorSpace* color_space =
frame.canvas() ? frame.canvas()->imageInfo().colorSpace() : nullptr;
frame.context().raster_cache().SetCheckboardCacheImages(
checkerboard_raster_cache_images_);
MutatorsStack stack;
PrerollContext context = {
ignore_raster_cache ? nullptr : &frame.context().raster_cache(),
frame.gr_context(),
frame.view_embedder(),
stack,
color_space,
kGiantRect,
false,
frame.context().raster_time(),
frame.context().ui_time(),
frame.context().texture_registry(),
checkerboard_offscreen_layers_,
frame_physical_depth_,
frame_device_pixel_ratio_};
root_layer_->Preroll(&context, frame.root_surface_transformation());
return context.surface_needs_readback;
}
void LayerTree::Paint(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache) const {
TRACE_EVENT0("uiwidgets", "LayerTree::Paint");
if (!root_layer_) {
FML_LOG(ERROR) << "The scene did not specify any layers to paint.";
return;
}
SkISize canvas_size = frame.canvas()->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height());
internal_nodes_canvas.addCanvas(frame.canvas());
if (frame.view_embedder() != nullptr) {
auto overlay_canvases = frame.view_embedder()->GetCurrentCanvases();
for (size_t i = 0; i < overlay_canvases.size(); i++) {
internal_nodes_canvas.addCanvas(overlay_canvases[i]);
}
}
Layer::PaintContext context = {
(SkCanvas*)&internal_nodes_canvas,
frame.canvas(),
frame.gr_context(),
frame.view_embedder(),
frame.context().raster_time(),
frame.context().ui_time(),
frame.context().texture_registry(),
ignore_raster_cache ? nullptr : &frame.context().raster_cache(),
checkerboard_offscreen_layers_,
frame_physical_depth_,
frame_device_pixel_ratio_};
if (root_layer_->needs_painting()) root_layer_->Paint(context);
}
sk_sp<SkPicture> LayerTree::Flatten(const SkRect& bounds) {
TRACE_EVENT0("uiwidgets", "LayerTree::Flatten");
SkPictureRecorder recorder;
auto* canvas = recorder.beginRecording(bounds);
if (!canvas) {
return nullptr;
}
MutatorsStack unused_stack;
const Stopwatch unused_stopwatch;
TextureRegistry unused_texture_registry;
SkMatrix root_surface_transformation;
// No root surface transformation. So assume identity.
root_surface_transformation.reset();
PrerollContext preroll_context{
nullptr, // raster_cache (don't consult the cache)
nullptr, // gr_context (used for the raster cache)
nullptr, // external view embedder
unused_stack, // mutator stack
nullptr, // SkColorSpace* dst_color_space
kGiantRect, // SkRect cull_rect
false, // layer reads from surface
unused_stopwatch, // frame time (dont care)
unused_stopwatch, // engine time (dont care)
unused_texture_registry, // texture registry (not supported)
false, // checkerboard_offscreen_layers
frame_physical_depth_, // maximum depth allowed for rendering
frame_device_pixel_ratio_ // ratio between logical and physical
};
SkISize canvas_size = canvas->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height());
internal_nodes_canvas.addCanvas(canvas);
Layer::PaintContext paint_context = {
(SkCanvas*)&internal_nodes_canvas,
canvas, // canvas
nullptr,
nullptr,
unused_stopwatch, // frame time (dont care)
unused_stopwatch, // engine time (dont care)
unused_texture_registry, // texture registry (not supported)
nullptr, // raster cache
false, // checkerboard offscreen layers
frame_physical_depth_, // maximum depth allowed for rendering
frame_device_pixel_ratio_ // ratio between logical and physical
};
// Even if we don't have a root layer, we still need to create an empty
// picture.
if (root_layer_) {
root_layer_->Preroll(&preroll_context, root_surface_transformation);
// The needs painting flag may be set after the preroll. So check it after.
if (root_layer_->needs_painting()) {
root_layer_->Paint(paint_context);
}
}
return recorder.finishRecordingAsPicture();
}
} // namespace uiwidgets

88
engine/src/flow/layers/layer_tree.h


#pragma once
#include <stdint.h>
#include <memory>
#include "flow/compositor_context.h"
#include "flow/layers/layer.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/time/time_delta.h"
#include "include/core/SkPicture.h"
#include "include/core/SkSize.h"
namespace uiwidgets {
class LayerTree {
public:
LayerTree(const SkISize& frame_size, float frame_physical_depth,
float frame_device_pixel_ratio);
// Perform a preroll pass on the tree and return information about
// the tree that affects rendering this frame.
//
// Returns:
// - a boolean indicating whether or not the top level of the
// layer tree performs any operations that require readback
// from the root surface.
bool Preroll(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache = false);
void Paint(CompositorContext::ScopedFrame& frame,
bool ignore_raster_cache = false) const;
sk_sp<SkPicture> Flatten(const SkRect& bounds);
Layer* root_layer() const { return root_layer_.get(); }
void set_root_layer(std::shared_ptr<Layer> root_layer) {
root_layer_ = std::move(root_layer);
}
const SkISize& frame_size() const { return frame_size_; }
float frame_physical_depth() const { return frame_physical_depth_; }
float frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; }
void RecordBuildTime(fml::TimePoint build_start, fml::TimePoint target_time);
fml::TimePoint build_start() const { return build_start_; }
fml::TimePoint build_finish() const { return build_finish_; }
fml::TimeDelta build_time() const { return build_finish_ - build_start_; }
fml::TimePoint target_time() const { return target_time_; }
// The number of frame intervals missed after which the compositor must
// trace the rasterized picture to a trace file. Specify 0 to disable all
// tracing
void set_rasterizer_tracing_threshold(uint32_t interval) {
rasterizer_tracing_threshold_ = interval;
}
uint32_t rasterizer_tracing_threshold() const {
return rasterizer_tracing_threshold_;
}
void set_checkerboard_raster_cache_images(bool checkerboard) {
checkerboard_raster_cache_images_ = checkerboard;
}
void set_checkerboard_offscreen_layers(bool checkerboard) {
checkerboard_offscreen_layers_ = checkerboard;
}
double device_pixel_ratio() const { return frame_device_pixel_ratio_; }
private:
std::shared_ptr<Layer> root_layer_;
fml::TimePoint build_start_;
fml::TimePoint build_finish_;
fml::TimePoint target_time_;
SkISize frame_size_ = SkISize::MakeEmpty(); // Physical pixels.
float frame_physical_depth_;
float frame_device_pixel_ratio_ = 1.0f; // Logical / Physical pixels ratio.
uint32_t rasterizer_tracing_threshold_;
bool checkerboard_raster_cache_images_;
bool checkerboard_offscreen_layers_;
FML_DISALLOW_COPY_AND_ASSIGN(LayerTree);
};
} // namespace uiwidgets

108
engine/src/flow/layers/opacity_layer.cc


#include "flow/layers/opacity_layer.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkPaint.h"
namespace uiwidgets {
OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset)
: alpha_(alpha), offset_(offset) {
// Ensure OpacityLayer has only one direct child.
//
// This is needed to ensure that retained rendering can always be applied to
// save the costly saveLayer.
//
// Any children will be actually added as children of this empty
// ContainerLayer.
ContainerLayer::Add(std::make_shared<ContainerLayer>());
}
void OpacityLayer::Add(std::shared_ptr<Layer> layer) {
GetChildContainer()->Add(std::move(layer));
}
void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "OpacityLayer::Preroll");
ContainerLayer* container = GetChildContainer();
FML_DCHECK(!container->layers().empty()); // OpacityLayer can't be a leaf.
const bool parent_is_opaque = context->is_opaque;
SkMatrix child_matrix = matrix;
child_matrix.postTranslate(offset_.fX, offset_.fY);
context->is_opaque = parent_is_opaque && (alpha_ == SK_AlphaOPAQUE);
context->mutators_stack.PushTransform(
SkMatrix::Translate(offset_.fX, offset_.fY));
context->mutators_stack.PushOpacity(alpha_);
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, child_matrix);
context->mutators_stack.Pop();
context->mutators_stack.Pop();
context->is_opaque = parent_is_opaque;
{
set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY));
if (!context->has_platform_view && context->raster_cache &&
SkRect::Intersects(context->cull_rect, paint_bounds())) {
SkMatrix ctm = child_matrix;
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
ctm = RasterCache::GetIntegralTransCTM(ctm);
#endif
context->raster_cache->Prepare(context, container, ctm);
}
}
}
void OpacityLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "OpacityLayer::Paint");
FML_DCHECK(needs_painting());
SkPaint paint;
paint.setAlpha(alpha_);
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->translate(offset_.fX, offset_.fY);
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
context.internal_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM(
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
if (context.raster_cache) {
ContainerLayer* container = GetChildContainer();
const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix();
RasterCacheResult child_cache = context.raster_cache->Get(container, ctm);
if (child_cache.is_valid()) {
child_cache.draw(*context.leaf_nodes_canvas, &paint);
return;
}
}
// Skia may clip the content with saveLayerBounds (although it's not a
// guaranteed clip). So we have to provide a big enough saveLayerBounds. To do
// so, we first remove the offset from paint bounds since it's already in the
// matrix. Then we round out the bounds because of our
// RasterCache::GetIntegralTransCTM optimization.
//
// Note that the following lines are only accessible when the raster cache is
// not available (e.g., when we're using the software backend in golden
// tests).
SkRect saveLayerBounds;
paint_bounds()
.makeOffset(-offset_.fX, -offset_.fY)
.roundOut(&saveLayerBounds);
Layer::AutoSaveLayer save_layer =
Layer::AutoSaveLayer::Create(context, saveLayerBounds, &paint);
PaintChildren(context);
}
ContainerLayer* OpacityLayer::GetChildContainer() const {
FML_DCHECK(layers().size() == 1);
return static_cast<ContainerLayer*>(layers()[0].get());
}
} // namespace uiwidgets

41
engine/src/flow/layers/opacity_layer.h


#pragma once
#include "flow/layers/container_layer.h"
namespace uiwidgets {
// Don't add an OpacityLayer with no children to the layer tree. Painting an
// OpacityLayer is very costly due to the saveLayer call. If there's no child,
// having the OpacityLayer or not has the same effect. In debug_unopt build,
// |Preroll| will assert if there are no children.
class OpacityLayer : public ContainerLayer {
public:
// An offset is provided here because OpacityLayer.addToScene method in the
// Flutter framework can take an optional offset argument.
//
// By default, that offset is always zero, and all the offsets are handled by
// some parent TransformLayers. But we allow the offset to be non-zero for
// backward compatibility. If it's non-zero, the old behavior is to propage
// that offset to all the leaf layers (e.g., PictureLayer). That will make
// the retained rendering inefficient as a small offset change could propagate
// to many leaf layers. Therefore we try to capture that offset here to stop
// the propagation as repainting the OpacityLayer is expensive.
OpacityLayer(SkAlpha alpha, const SkPoint& offset);
void Add(std::shared_ptr<Layer> layer) override;
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
ContainerLayer* GetChildContainer() const;
SkAlpha alpha_;
SkPoint offset_;
SkRRect frameRRect_;
FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer);
};
} // namespace uiwidgets

90
engine/src/flow/layers/performance_overlay_layer.cc


#include "flow/layers/performance_overlay_layer.h"
#include <iomanip>
#include <iostream>
#include <string>
#include "include/core/SkFont.h"
#include "include/core/SkTextBlob.h"
namespace uiwidgets {
namespace {
void VisualizeStopWatch(SkCanvas& canvas, const Stopwatch& stopwatch,
SkScalar x, SkScalar y, SkScalar width, SkScalar height,
bool show_graph, bool show_labels,
const std::string& label_prefix,
const std::string& font_path) {
const int label_x = 8; // distance from x
const int label_y = -10; // distance from y+height
if (show_graph) {
SkRect visualization_rect = SkRect::MakeXYWH(x, y, width, height);
stopwatch.Visualize(canvas, visualization_rect);
}
if (show_labels) {
auto text = PerformanceOverlayLayer::MakeStatisticsText(
stopwatch, label_prefix, font_path);
SkPaint paint;
paint.setColor(SK_ColorGRAY);
canvas.drawTextBlob(text, x + label_x, y + height + label_y, paint);
}
}
} // namespace
sk_sp<SkTextBlob> PerformanceOverlayLayer::MakeStatisticsText(
const Stopwatch& stopwatch, const std::string& label_prefix,
const std::string& font_path) {
SkFont font;
if (font_path != "") {
font = SkFont(SkTypeface::MakeFromFile(font_path.c_str()));
}
font.setSize(15);
double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF();
double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF();
std::stringstream stream;
stream.setf(std::ios::fixed | std::ios::showpoint);
stream << std::setprecision(1);
stream << label_prefix << " "
<< "max " << max_ms_per_frame << " ms/frame, "
<< "avg " << average_ms_per_frame << " ms/frame";
auto text = stream.str();
return SkTextBlob::MakeFromText(text.c_str(), text.size(), font,
SkTextEncoding::kUTF8);
}
PerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options,
const char* font_path)
: options_(options) {
if (font_path != nullptr) {
font_path_ = font_path;
}
}
void PerformanceOverlayLayer::Paint(PaintContext& context) const {
const int padding = 8;
if (!options_) return;
TRACE_EVENT0("uiwidgets", "PerformanceOverlayLayer::Paint");
SkScalar x = paint_bounds().x() + padding;
SkScalar y = paint_bounds().y() + padding;
SkScalar width = paint_bounds().width() - (padding * 2);
SkScalar height = paint_bounds().height() / 2;
SkAutoCanvasRestore save(context.leaf_nodes_canvas, true);
VisualizeStopWatch(
*context.leaf_nodes_canvas, context.raster_time, x, y, width,
height - padding, options_ & kVisualizeRasterizerStatistics,
options_ & kDisplayRasterizerStatistics, "Raster", font_path_);
VisualizeStopWatch(*context.leaf_nodes_canvas, context.ui_time, x, y + height,
width, height - padding,
options_ & kVisualizeEngineStatistics,
options_ & kDisplayEngineStatistics, "UI", font_path_);
}
} // namespace uiwidgets

36
engine/src/flow/layers/performance_overlay_layer.h


#pragma once
#include <string>
#include "flow/instrumentation.h"
#include "flow/layers/layer.h"
#include "flutter/fml/macros.h"
class SkTextBlob;
namespace uiwidgets {
const int kDisplayRasterizerStatistics = 1 << 0;
const int kVisualizeRasterizerStatistics = 1 << 1;
const int kDisplayEngineStatistics = 1 << 2;
const int kVisualizeEngineStatistics = 1 << 3;
class PerformanceOverlayLayer : public Layer {
public:
static sk_sp<SkTextBlob> MakeStatisticsText(const Stopwatch& stopwatch,
const std::string& label_prefix,
const std::string& font_path);
explicit PerformanceOverlayLayer(uint64_t options,
const char* font_path = nullptr);
void Paint(PaintContext& context) const override;
private:
int options_;
std::string font_path_;
FML_DISALLOW_COPY_AND_ASSIGN(PerformanceOverlayLayer);
};
} // namespace uiwidgets

179
engine/src/flow/layers/physical_shape_layer.cc


#include "flow/layers/physical_shape_layer.h"
#include "flow/paint_utils.h"
#include "include/utils/SkShadowUtils.h"
namespace uiwidgets {
const SkScalar kLightHeight = 600;
const SkScalar kLightRadius = 800;
PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, SkColor shadow_color,
float elevation, const SkPath& path,
Clip clip_behavior)
: color_(color),
shadow_color_(shadow_color),
elevation_(elevation),
path_(path),
isRect_(false),
clip_behavior_(clip_behavior) {
SkRect rect;
if (path.isRect(&rect)) {
isRect_ = true;
frameRRect_ = SkRRect::MakeRect(rect);
} else if (path.isRRect(&frameRRect_)) {
isRect_ = frameRRect_.isRect();
} else if (path.isOval(&rect)) {
// isRRect returns false for ovals, so we need to explicitly check isOval
// as well.
frameRRect_ = SkRRect::MakeOval(rect);
} else {
// Scenic currently doesn't provide an easy way to create shapes from
// arbitrary paths.
// For shapes that cannot be represented as a rounded rectangle we
// default to use the bounding rectangle.
// TODO(amirh): fix this once we have a way to create a Scenic shape from
// an SkPath.
frameRRect_ = SkRRect::MakeRect(path.getBounds());
}
}
void PhysicalShapeLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "PhysicalShapeLayer::Preroll");
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->total_elevation += elevation_;
total_elevation_ = context->total_elevation;
SkRect child_paint_bounds;
PrerollChildren(context, matrix, &child_paint_bounds);
context->total_elevation -= elevation_;
if (elevation_ == 0) {
set_paint_bounds(path_.getBounds());
} else {
// We will draw the shadow in Paint(), so add some margin to the paint
// bounds to leave space for the shadow. We fill this whole region and clip
// children to it so we don't need to join the child paint bounds.
set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation_,
context->frame_device_pixel_ratio));
}
}
void PhysicalShapeLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "PhysicalShapeLayer::Paint");
FML_DCHECK(needs_painting());
if (elevation_ != 0) {
DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_,
SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio);
}
// Call drawPath without clip if possible for better performance.
SkPaint paint;
paint.setColor(color_);
paint.setAntiAlias(true);
if (clip_behavior_ != Clip::antiAliasWithSaveLayer) {
context.leaf_nodes_canvas->drawPath(path_, paint);
}
int saveCount = context.internal_nodes_canvas->save();
switch (clip_behavior_) {
case Clip::hardEdge:
context.internal_nodes_canvas->clipPath(path_, false);
break;
case Clip::antiAlias:
context.internal_nodes_canvas->clipPath(path_, true);
break;
case Clip::antiAliasWithSaveLayer:
context.internal_nodes_canvas->clipPath(path_, true);
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
break;
case Clip::none:
break;
}
if (UsesSaveLayer()) {
// If we want to avoid the bleeding edge artifact
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
// using saveLayer, we have to call drawPaint instead of drawPath as
// anti-aliased drawPath will always have such artifacts.
context.leaf_nodes_canvas->drawPaint(paint);
}
PaintChildren(context);
context.internal_nodes_canvas->restoreToCount(saveCount);
}
SkRect PhysicalShapeLayer::ComputeShadowBounds(const SkRect& bounds,
float elevation,
float pixel_ratio) {
// The shadow offset is calculated as follows:
// .--- (kLightRadius)
// -------/ (light)
// | /
// | /
// |/
// |O
// /| (kLightHeight)
// / |
// / |
// / |
// / |
// ------------- (layer)
// /| |
// / | | (elevation)
// A / | |B
// ------------------------------------------------ (canvas)
// --- (extent of shadow)
//
// E = lt } t = (r + w/2)/h
// } =>
// r + w/2 = ht } E = (l/h)(r + w/2)
//
// Where: E = extent of shadow
// l = elevation of layer
// r = radius of the light source
// w = width of the layer
// h = light height
// t = tangent of AOB, i.e., multiplier for elevation to extent
// tangent for x
double tx =
(kLightRadius * pixel_ratio + bounds.width() * 0.5) / kLightHeight;
// tangent for y
double ty =
(kLightRadius * pixel_ratio + bounds.height() * 0.5) / kLightHeight;
SkRect shadow_bounds(bounds);
shadow_bounds.outset(elevation * tx, elevation * ty);
return shadow_bounds;
}
void PhysicalShapeLayer::DrawShadow(SkCanvas* canvas, const SkPath& path,
SkColor color, float elevation,
bool transparentOccluder, SkScalar dpr) {
const SkScalar kAmbientAlpha = 0.039f;
const SkScalar kSpotAlpha = 0.25f;
SkShadowFlags flags = transparentOccluder
? SkShadowFlags::kTransparentOccluder_ShadowFlag
: SkShadowFlags::kNone_ShadowFlag;
const SkRect& bounds = path.getBounds();
SkScalar shadow_x = (bounds.left() + bounds.right()) / 2;
SkScalar shadow_y = bounds.top() - 600.0f;
SkColor inAmbient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));
SkColor inSpot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));
SkColor ambientColor, spotColor;
SkShadowUtils::ComputeTonalColors(inAmbient, inSpot, &ambientColor,
&spotColor);
SkShadowUtils::DrawShadow(
canvas, path, SkPoint3::Make(0, 0, dpr * elevation),
SkPoint3::Make(shadow_x, shadow_y, dpr * kLightHeight),
dpr * kLightRadius, ambientColor, spotColor, flags);
}
} // namespace uiwidgets

39
engine/src/flow/layers/physical_shape_layer.h


#pragma once
#include "flow/layers/container_layer.h"
namespace uiwidgets {
class PhysicalShapeLayer : public ContainerLayer {
public:
PhysicalShapeLayer(SkColor color, SkColor shadow_color, float elevation,
const SkPath& path, Clip clip_behavior);
static SkRect ComputeShadowBounds(const SkRect& bounds, float elevation,
float pixel_ratio);
static void DrawShadow(SkCanvas* canvas, const SkPath& path, SkColor color,
float elevation, bool transparentOccluder,
SkScalar dpr);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
bool UsesSaveLayer() const {
return clip_behavior_ == Clip::antiAliasWithSaveLayer;
}
float total_elevation() const { return total_elevation_; }
private:
SkColor color_;
SkColor shadow_color_;
float elevation_ = 0.0f;
float total_elevation_ = 0.0f;
SkPath path_;
bool isRect_;
SkRRect frameRRect_;
Clip clip_behavior_;
};
} // namespace uiwidgets

60
engine/src/flow/layers/picture_layer.cc


#include "flow/layers/picture_layer.h"
#include "flutter/fml/logging.h"
namespace uiwidgets {
PictureLayer::PictureLayer(const SkPoint& offset,
SkiaGPUObject<SkPicture> picture, bool is_complex,
bool will_change)
: offset_(offset),
picture_(std::move(picture)),
is_complex_(is_complex),
will_change_(will_change) {}
void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "PictureLayer::Preroll");
SkPicture* sk_picture = picture();
if (auto* cache = context->raster_cache) {
TRACE_EVENT0("uiwidgets", "PictureLayer::RasterCache (Preroll)");
SkMatrix ctm = matrix;
ctm.postTranslate(offset_.x(), offset_.y());
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
ctm = RasterCache::GetIntegralTransCTM(ctm);
#endif
cache->Prepare(context->gr_context, sk_picture, ctm,
context->dst_color_space, is_complex_, will_change_);
}
SkRect bounds = sk_picture->cullRect().makeOffset(offset_.x(), offset_.y());
set_paint_bounds(bounds);
}
void PictureLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "PictureLayer::Paint");
FML_DCHECK(picture_.get());
FML_DCHECK(needs_painting());
SkAutoCanvasRestore save(context.leaf_nodes_canvas, true);
context.leaf_nodes_canvas->translate(offset_.x(), offset_.y());
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM(
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
if (context.raster_cache) {
const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix();
RasterCacheResult result = context.raster_cache->Get(*picture(), ctm);
if (result.is_valid()) {
TRACE_EVENT_INSTANT0("uiwidgets", "raster cache hit");
result.draw(*context.leaf_nodes_canvas);
return;
}
}
picture()->playback(context.leaf_nodes_canvas);
}
} // namespace uiwidgets

33
engine/src/flow/layers/picture_layer.h


#pragma once
#include <memory>
#include "flow/layers/layer.h"
#include "flow/raster_cache.h"
#include "flow/skia_gpu_object.h"
namespace uiwidgets {
class PictureLayer : public Layer {
public:
PictureLayer(const SkPoint& offset, SkiaGPUObject<SkPicture> picture,
bool is_complex, bool will_change);
SkPicture* picture() const { return picture_.get().get(); }
void Preroll(PrerollContext* frame, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
SkPoint offset_;
// Even though pictures themselves are not GPU resources, they may reference
// images that have a reference to a GPU resource.
SkiaGPUObject<SkPicture> picture_;
bool is_complex_ = false;
bool will_change_ = false;
FML_DISALLOW_COPY_AND_ASSIGN(PictureLayer);
};
} // namespace uiwidgets

39
engine/src/flow/layers/platform_view_layer.cc


#include "flow/layers/platform_view_layer.h"
namespace uiwidgets {
PlatformViewLayer::PlatformViewLayer(const SkPoint& offset, const SkSize& size,
int64_t view_id)
: offset_(offset), size_(size), view_id_(view_id) {}
void PlatformViewLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(),
size_.height()));
if (context->view_embedder == nullptr) {
FML_LOG(ERROR) << "Trying to embed a platform view but the PrerollContext "
"does not support embedding";
return;
}
context->has_platform_view = true;
std::unique_ptr<EmbeddedViewParams> params =
std::make_unique<EmbeddedViewParams>();
params->offsetPixels =
SkPoint::Make(matrix.getTranslateX(), matrix.getTranslateY());
params->sizePoints = size_;
params->mutatorsStack = context->mutators_stack;
context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
std::move(params));
}
void PlatformViewLayer::Paint(PaintContext& context) const {
if (context.view_embedder == nullptr) {
FML_LOG(ERROR) << "Trying to embed a platform view but the PaintContext "
"does not support embedding";
return;
}
SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_);
context.leaf_nodes_canvas = canvas;
}
} // namespace uiwidgets

24
engine/src/flow/layers/platform_view_layer.h


#pragma once
#include "flow/layers/layer.h"
#include "include/core/SkPoint.h"
#include "include/core/SkSize.h"
namespace uiwidgets {
class PlatformViewLayer : public Layer {
public:
PlatformViewLayer(const SkPoint& offset, const SkSize& size, int64_t view_id);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
SkPoint offset_;
SkSize size_;
int64_t view_id_;
FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewLayer);
};
} // namespace uiwidgets

32
engine/src/flow/layers/shader_mask_layer.cc


#include "flow/layers/shader_mask_layer.h"
namespace uiwidgets {
ShaderMaskLayer::ShaderMaskLayer(sk_sp<SkShader> shader,
const SkRect& mask_rect,
SkBlendMode blend_mode)
: shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {}
void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, matrix);
}
void ShaderMaskLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "ShaderMaskLayer::Paint");
FML_DCHECK(needs_painting());
Layer::AutoSaveLayer save =
Layer::AutoSaveLayer::Create(context, paint_bounds(), nullptr);
PaintChildren(context);
SkPaint paint;
paint.setBlendMode(blend_mode_);
paint.setShader(shader_);
context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top());
context.leaf_nodes_canvas->drawRect(
SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint);
}
} // namespace uiwidgets

25
engine/src/flow/layers/shader_mask_layer.h


#pragma once
#include "flow/layers/container_layer.h"
#include "include/core/SkShader.h"
namespace uiwidgets {
class ShaderMaskLayer : public ContainerLayer {
public:
ShaderMaskLayer(sk_sp<SkShader> shader, const SkRect& mask_rect,
SkBlendMode blend_mode);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
sk_sp<SkShader> shader_;
SkRect mask_rect_;
SkBlendMode blend_mode_;
FML_DISALLOW_COPY_AND_ASSIGN(ShaderMaskLayer);
};
} // namespace uiwidgets

31
engine/src/flow/layers/texture_layer.cc


#include "flow/layers/texture_layer.h"
#include "flow/texture.h"
namespace uiwidgets {
TextureLayer::TextureLayer(const SkPoint& offset, const SkSize& size,
int64_t texture_id, bool freeze)
: offset_(offset), size_(size), texture_id_(texture_id), freeze_(freeze) {}
void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "TextureLayer::Preroll");
set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(),
size_.height()));
}
void TextureLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "TextureLayer::Paint");
std::shared_ptr<Texture> texture =
context.texture_registry.GetTexture(texture_id_);
if (!texture) {
TRACE_EVENT_INSTANT0("uiwidgets", "null texture");
return;
}
texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_,
context.gr_context);
}
} // namespace uiwidgets

26
engine/src/flow/layers/texture_layer.h


#pragma once
#include "flow/layers/layer.h"
#include "include/core/SkPoint.h"
#include "include/core/SkSize.h"
namespace uiwidgets {
class TextureLayer : public Layer {
public:
TextureLayer(const SkPoint& offset, const SkSize& size, int64_t texture_id,
bool freeze);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
SkPoint offset_;
SkSize size_;
int64_t texture_id_;
bool freeze_;
FML_DISALLOW_COPY_AND_ASSIGN(TextureLayer);
};
} // namespace uiwidgets

59
engine/src/flow/layers/transform_layer.cc


#include "flow/layers/transform_layer.h"
namespace uiwidgets {
TransformLayer::TransformLayer(const SkMatrix& transform)
: transform_(transform) {
// Checks (in some degree) that SkMatrix transform_ is valid and initialized.
//
// If transform_ is uninitialized, this assert may look flaky as it doesn't
// fail all the time, and some rerun may make it pass. But don't ignore it and
// just rerun the test if this is triggered, since even a flaky failure here
// may signify a potentially big problem in the code.
//
// We have to write this flaky test because there is no reliable way to test
// whether a variable is initialized or not in C++.
FML_DCHECK(transform_.isFinite());
if (!transform_.isFinite()) {
FML_LOG(ERROR) << "TransformLayer is constructed with an invalid matrix.";
transform_.setIdentity();
}
}
void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("uiwidgets", "TransformLayer::Preroll");
SkMatrix child_matrix;
child_matrix.setConcat(matrix, transform_);
context->mutators_stack.PushTransform(transform_);
SkRect previous_cull_rect = context->cull_rect;
SkMatrix inverse_transform_;
// Perspective projections don't produce rectangles that are useful for
// culling for some reason.
if (!transform_.hasPerspective() && transform_.invert(&inverse_transform_)) {
inverse_transform_.mapRect(&context->cull_rect);
} else {
context->cull_rect = kGiantRect;
}
SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, child_matrix, &child_paint_bounds);
transform_.mapRect(&child_paint_bounds);
set_paint_bounds(child_paint_bounds);
context->cull_rect = previous_cull_rect;
context->mutators_stack.Pop();
}
void TransformLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("uiwidgets", "TransformLayer::Paint");
FML_DCHECK(needs_painting());
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->concat(transform_);
PaintChildren(context);
}
} // namespace uiwidgets

23
engine/src/flow/layers/transform_layer.h


#pragma once
#include "flow/layers/container_layer.h"
namespace uiwidgets {
// Be careful that SkMatrix's default constructor doesn't initialize the matrix
// at all. Hence |set_transform| must be called with an initialized SkMatrix.
class TransformLayer : public ContainerLayer {
public:
TransformLayer(const SkMatrix& transform);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
void Paint(PaintContext& context) const override;
private:
SkMatrix transform_;
FML_DISALLOW_COPY_AND_ASSIGN(TransformLayer);
};
} // namespace uiwidgets

146
engine/src/flow/matrix_decomposition.cc


#include "flow/matrix_decomposition.h"
namespace uiwidgets {
static inline SkVector3 SkVector3Combine(const SkVector3& a, float a_scale,
const SkVector3& b, float b_scale) {
return {
a_scale * a.fX + b_scale * b.fX, //
a_scale * a.fY + b_scale * b.fY, //
a_scale * a.fZ + b_scale * b.fZ, //
};
}
static inline SkVector3 SkVector3Cross(const SkVector3& a, const SkVector3& b) {
return {
(a.fY * b.fZ) - (a.fZ * b.fY), //
(a.fZ * b.fX) - (a.fX * b.fZ), //
(a.fX * b.fY) - (a.fY * b.fX) //
};
}
MatrixDecomposition::MatrixDecomposition(const SkMatrix& matrix)
: MatrixDecomposition(SkMatrix44{matrix}) {}
// Use custom normalize to avoid skia precision loss/normalize() privatization.
static inline void SkVector3Normalize(SkVector3& v) {
double mag = sqrt(v.fX * v.fX + v.fY * v.fY + v.fZ * v.fZ);
double scale = 1.0 / mag;
v.fX *= scale;
v.fY *= scale;
v.fZ *= scale;
}
MatrixDecomposition::MatrixDecomposition(SkMatrix44 matrix) : valid_(false) {
if (matrix.get(3, 3) == 0) {
return;
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
matrix.set(j, i, matrix.get(j, i) / matrix.get(3, 3));
}
}
SkMatrix44 perpective_matrix = matrix;
for (int i = 0; i < 3; i++) {
perpective_matrix.set(3, i, 0.0);
}
perpective_matrix.set(3, 3, 1.0);
if (perpective_matrix.determinant() == 0.0) {
return;
}
if (matrix.get(3, 0) != 0.0 || matrix.get(3, 1) != 0.0 ||
matrix.get(3, 2) != 0.0) {
const SkVector4 right_hand_side(matrix.get(3, 0), matrix.get(3, 1),
matrix.get(3, 2), matrix.get(3, 3));
SkMatrix44 inverted_transposed(
SkMatrix44::Uninitialized_Constructor::kUninitialized_Constructor);
if (!perpective_matrix.invert(&inverted_transposed)) {
return;
}
inverted_transposed.transpose();
perspective_ = inverted_transposed * right_hand_side;
matrix.set(3, 0, 0);
matrix.set(3, 1, 0);
matrix.set(3, 2, 0);
matrix.set(3, 3, 1);
}
translation_ = {matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3)};
matrix.set(0, 3, 0.0);
matrix.set(1, 3, 0.0);
matrix.set(2, 3, 0.0);
SkVector3 row[3];
for (int i = 0; i < 3; i++) {
row[i].set(matrix.get(0, i), matrix.get(1, i), matrix.get(2, i));
}
scale_.fX = row[0].length();
SkVector3Normalize(row[0]);
shear_.fX = row[0].dot(row[1]);
row[1] = SkVector3Combine(row[1], 1.0, row[0], -shear_.fX);
scale_.fY = row[1].length();
SkVector3Normalize(row[1]);
shear_.fX /= scale_.fY;
shear_.fY = row[0].dot(row[2]);
row[2] = SkVector3Combine(row[2], 1.0, row[0], -shear_.fY);
shear_.fZ = row[1].dot(row[2]);
row[2] = SkVector3Combine(row[2], 1.0, row[1], -shear_.fZ);
scale_.fZ = row[2].length();
SkVector3Normalize(row[2]);
shear_.fY /= scale_.fZ;
shear_.fZ /= scale_.fZ;
if (row[0].dot(SkVector3Cross(row[1], row[2])) < 0) {
scale_.fX *= -1;
scale_.fY *= -1;
scale_.fZ *= -1;
for (int i = 0; i < 3; i++) {
row[i].fX *= -1;
row[i].fY *= -1;
row[i].fZ *= -1;
}
}
rotation_.set(0.5 * sqrt(fmax(1.0 + row[0].fX - row[1].fY - row[2].fZ, 0.0)),
0.5 * sqrt(fmax(1.0 - row[0].fX + row[1].fY - row[2].fZ, 0.0)),
0.5 * sqrt(fmax(1.0 - row[0].fX - row[1].fY + row[2].fZ, 0.0)),
0.5 * sqrt(fmax(1.0 + row[0].fX + row[1].fY + row[2].fZ, 0.0)));
if (row[2].fY > row[1].fZ) {
rotation_.fData[0] = -rotation_.fData[0];
}
if (row[0].fZ > row[2].fX) {
rotation_.fData[1] = -rotation_.fData[1];
}
if (row[1].fX > row[0].fY) {
rotation_.fData[2] = -rotation_.fData[2];
}
valid_ = true;
}
MatrixDecomposition::~MatrixDecomposition() = default;
bool MatrixDecomposition::IsValid() const { return valid_; }
} // namespace uiwidgets

44
engine/src/flow/matrix_decomposition.h


#pragma once
#include "flutter/fml/macros.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkMatrix44.h"
#include "include/core/SkPoint3.h"
namespace uiwidgets {
/// Decomposes a given non-degenerate transformation matrix into a sequence of
/// operations that produced it. The validity of the decomposition must always
/// be checked before attempting to access any of the decomposed elements.
class MatrixDecomposition {
public:
MatrixDecomposition(const SkMatrix& matrix);
MatrixDecomposition(SkMatrix44 matrix);
~MatrixDecomposition();
bool IsValid() const;
const SkVector3& translation() const { return translation_; }
const SkVector3& scale() const { return scale_; }
const SkVector3& shear() const { return shear_; }
const SkVector4& perspective() const { return perspective_; }
const SkVector4& rotation() const { return rotation_; }
private:
bool valid_;
SkVector3 translation_;
SkVector3 scale_;
SkVector3 shear_;
SkVector4 perspective_;
SkVector4 rotation_;
FML_DISALLOW_COPY_AND_ASSIGN(MatrixDecomposition);
};
} // namespace uiwidgets

49
engine/src/flow/paint_utils.cc


#include "flow/paint_utils.h"
#include <stdlib.h>
#include "include/core/SkBitmap.h"
#include "include/core/SkPaint.h"
#include "include/core/SkShader.h"
namespace uiwidgets {
namespace {
sk_sp<SkShader> CreateCheckerboardShader(SkColor c1, SkColor c2, int size) {
SkBitmap bm;
bm.allocN32Pixels(2 * size, 2 * size);
bm.eraseColor(c1);
bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2);
bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2);
return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
}
} // anonymous namespace
void DrawCheckerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size) {
SkPaint paint;
paint.setShader(CreateCheckerboardShader(c1, c2, size));
canvas->drawPaint(paint);
}
void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect) {
// Draw a checkerboard
canvas->save();
canvas->clipRect(rect);
auto checkerboard_color =
SkColorSetARGB(64, rand() % 256, rand() % 256, rand() % 256);
DrawCheckerboard(canvas, checkerboard_color, 0x00000000, 12);
canvas->restore();
// Stroke the drawn area
SkPaint debugPaint;
debugPaint.setStrokeWidth(8);
debugPaint.setColor(SkColorSetA(checkerboard_color, 255));
debugPaint.setStyle(SkPaint::kStroke_Style);
canvas->drawRect(rect, debugPaint);
}
} // namespace uiwidgets

13
engine/src/flow/paint_utils.h


#pragma once
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkRect.h"
namespace uiwidgets {
void DrawCheckerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size);
void DrawCheckerboard(SkCanvas* canvas, const SkRect& rect);
} // namespace uiwidgets

289
engine/src/flow/raster_cache.cc


#include "flow/raster_cache.h"
#include <vector>
#include "flow/layers/layer.h"
#include "flow/paint_utils.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkImage.h"
#include "include/core/SkPicture.h"
#include "include/core/SkSurface.h"
namespace uiwidgets {
RasterCacheResult::RasterCacheResult(sk_sp<SkImage> image,
const SkRect& logical_rect)
: image_(std::move(image)), logical_rect_(logical_rect) {}
void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const {
TRACE_EVENT0("uiwidgets", "RasterCacheResult::draw");
SkAutoCanvasRestore auto_restore(&canvas, true);
SkIRect bounds =
RasterCache::GetDeviceBounds(logical_rect_, canvas.getTotalMatrix());
FML_DCHECK(
std::abs(bounds.size().width() - image_->dimensions().width()) <= 1 &&
std::abs(bounds.size().height() - image_->dimensions().height()) <= 1);
canvas.resetMatrix();
canvas.drawImage(image_, bounds.fLeft, bounds.fTop, paint);
}
RasterCache::RasterCache(size_t access_threshold,
size_t picture_cache_limit_per_frame)
: access_threshold_(access_threshold),
picture_cache_limit_per_frame_(picture_cache_limit_per_frame),
checkerboard_images_(false) {}
static bool CanRasterizePicture(SkPicture* picture) {
if (picture == nullptr) {
return false;
}
const SkRect cull_rect = picture->cullRect();
if (cull_rect.isEmpty()) {
// No point in ever rasterizing an empty picture.
return false;
}
if (!cull_rect.isFinite()) {
// Cannot attempt to rasterize into an infinitely large surface.
return false;
}
return true;
}
static bool IsPictureWorthRasterizing(SkPicture* picture, bool will_change,
bool is_complex) {
if (will_change) {
// If the picture is going to change in the future, there is no point in
// doing to extra work to rasterize.
return false;
}
if (!CanRasterizePicture(picture)) {
// No point in deciding whether the picture is worth rasterizing if it
// cannot be rasterized at all.
return false;
}
if (is_complex) {
// The caller seems to have extra information about the picture and thinks
// the picture is always worth rasterizing.
return true;
}
// TODO(abarth): We should find a better heuristic here that lets us avoid
// wasting memory on trivial layers that are easy to re-rasterize every frame.
return picture->approximateOpCount() > 5;
}
/// @note Procedure doesn't copy all closures.
static RasterCacheResult Rasterize(
GrContext* context, const SkMatrix& ctm, SkColorSpace* dst_color_space,
bool checkerboard, const SkRect& logical_rect,
const std::function<void(SkCanvas*)>& draw_function) {
TRACE_EVENT0("uiwidgets", "RasterCachePopulate");
SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm);
const SkImageInfo image_info = SkImageInfo::MakeN32Premul(
cache_rect.width(), cache_rect.height(), sk_ref_sp(dst_color_space));
sk_sp<SkSurface> surface =
context
? SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, image_info)
: SkSurface::MakeRaster(image_info);
if (!surface) {
return {};
}
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
canvas->translate(-cache_rect.left(), -cache_rect.top());
canvas->concat(ctm);
draw_function(canvas);
if (checkerboard) {
DrawCheckerboard(canvas, logical_rect);
}
return {surface->makeImageSnapshot(), logical_rect};
}
RasterCacheResult RasterizePicture(SkPicture* picture, GrContext* context,
const SkMatrix& ctm,
SkColorSpace* dst_color_space,
bool checkerboard) {
return Rasterize(context, ctm, dst_color_space, checkerboard,
picture->cullRect(),
[=](SkCanvas* canvas) { canvas->drawPicture(picture); });
}
void RasterCache::Prepare(PrerollContext* context, Layer* layer,
const SkMatrix& ctm) {
LayerRasterCacheKey cache_key(layer->unique_id(), ctm);
Entry& entry = layer_cache_[cache_key];
entry.access_count++;
entry.used_this_frame = true;
if (!entry.image.is_valid()) {
entry.image = Rasterize(
context->gr_context, ctm, context->dst_color_space,
checkerboard_images_, layer->paint_bounds(),
[layer, context](SkCanvas* canvas) {
SkISize canvas_size = canvas->getBaseLayerSize();
SkNWayCanvas internal_nodes_canvas(canvas_size.width(),
canvas_size.height());
internal_nodes_canvas.addCanvas(canvas);
Layer::PaintContext paintContext = {
(SkCanvas*)&internal_nodes_canvas,
canvas,
context->gr_context,
nullptr,
context->raster_time,
context->ui_time,
context->texture_registry,
context->has_platform_view ? nullptr : context->raster_cache,
context->checkerboard_offscreen_layers,
context->frame_physical_depth,
context->frame_device_pixel_ratio};
if (layer->needs_painting()) {
layer->Paint(paintContext);
}
});
}
}
bool RasterCache::Prepare(GrContext* context, SkPicture* picture,
const SkMatrix& transformation_matrix,
SkColorSpace* dst_color_space, bool is_complex,
bool will_change) {
// Disabling caching when access_threshold is zero is historic behavior.
if (access_threshold_ == 0) {
return false;
}
if (picture_cached_this_frame_ >= picture_cache_limit_per_frame_) {
return false;
}
if (!IsPictureWorthRasterizing(picture, will_change, is_complex)) {
// We only deal with pictures that are worthy of rasterization.
return false;
}
// Decompose the matrix (once) for all subsequent operations. We want to make
// sure to avoid volumetric distortions while accounting for scaling.
const MatrixDecomposition matrix(transformation_matrix);
if (!matrix.IsValid()) {
// The matrix was singular. No point in going further.
return false;
}
PictureRasterCacheKey cache_key(picture->uniqueID(), transformation_matrix);
// Creates an entry, if not present prior.
Entry& entry = picture_cache_[cache_key];
if (entry.access_count < access_threshold_) {
// Frame threshold has not yet been reached.
return false;
}
if (!entry.image.is_valid()) {
entry.image = RasterizePicture(picture, context, transformation_matrix,
dst_color_space, checkerboard_images_);
picture_cached_this_frame_++;
}
return true;
}
RasterCacheResult RasterCache::Get(const SkPicture& picture,
const SkMatrix& ctm) const {
PictureRasterCacheKey cache_key(picture.uniqueID(), ctm);
auto it = picture_cache_.find(cache_key);
if (it == picture_cache_.end()) {
return RasterCacheResult();
}
Entry& entry = it->second;
entry.access_count++;
entry.used_this_frame = true;
return entry.image;
}
RasterCacheResult RasterCache::Get(Layer* layer, const SkMatrix& ctm) const {
LayerRasterCacheKey cache_key(layer->unique_id(), ctm);
auto it = layer_cache_.find(cache_key);
if (it == layer_cache_.end()) {
return RasterCacheResult();
}
Entry& entry = it->second;
entry.access_count++;
entry.used_this_frame = true;
return entry.image;
}
void RasterCache::SweepAfterFrame() {
SweepOneCacheAfterFrame(picture_cache_);
SweepOneCacheAfterFrame(layer_cache_);
picture_cached_this_frame_ = 0;
TraceStatsToTimeline();
}
void RasterCache::Clear() {
picture_cache_.clear();
layer_cache_.clear();
}
size_t RasterCache::GetCachedEntriesCount() const {
return layer_cache_.size() + picture_cache_.size();
}
void RasterCache::SetCheckboardCacheImages(bool checkerboard) {
if (checkerboard_images_ == checkerboard) {
return;
}
checkerboard_images_ = checkerboard;
// Clear all existing entries so previously rasterized items (with or without
// a checkerboard) will be refreshed in subsequent passes.
Clear();
}
void RasterCache::TraceStatsToTimeline() const {
#if !UIWidgets_RELEASE
size_t layer_cache_count = 0;
size_t layer_cache_bytes = 0;
size_t picture_cache_count = 0;
size_t picture_cache_bytes = 0;
for (const auto& item : layer_cache_) {
const auto dimensions = item.second.image.image_dimensions();
layer_cache_count++;
layer_cache_bytes += dimensions.width() * dimensions.height() * 4;
}
for (const auto& item : picture_cache_) {
const auto dimensions = item.second.image.image_dimensions();
picture_cache_count++;
picture_cache_bytes += dimensions.width() * dimensions.height() * 4;
}
FML_TRACE_COUNTER("uiwidgets", "RasterCache",
reinterpret_cast<int64_t>(this), //
"LayerCount", layer_cache_count, //
"LayerMBytes", layer_cache_bytes * 1e-6, //
"PictureCount", picture_cache_count, //
"PictureMBytes", picture_cache_bytes * 1e-6 //
);
#endif // !UIWidgets_RELEASE
}
} // namespace uiwidgets

130
engine/src/flow/raster_cache.h


#pragma once
#include <memory>
#include <unordered_map>
#include "flow/instrumentation.h"
#include "flow/raster_cache_key.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "include/core/SkImage.h"
#include "include/core/SkSize.h"
namespace uiwidgets {
class RasterCacheResult {
public:
RasterCacheResult() = default;
RasterCacheResult(const RasterCacheResult& other) = default;
RasterCacheResult(sk_sp<SkImage> image, const SkRect& logical_rect);
operator bool() const { return static_cast<bool>(image_); }
bool is_valid() const { return static_cast<bool>(image_); };
void draw(SkCanvas& canvas, const SkPaint* paint = nullptr) const;
SkISize image_dimensions() const {
return image_ ? image_->dimensions() : SkISize::Make(0, 0);
};
private:
sk_sp<SkImage> image_;
SkRect logical_rect_;
};
struct PrerollContext;
class RasterCache {
public:
// The default max number of picture raster caches to be generated per frame.
// Generating too many caches in one frame may cause jank on that frame. This
// limit allows us to throttle the cache and distribute the work across
// multiple frames.
static constexpr int kDefaultPictureCacheLimitPerFrame = 3;
explicit RasterCache(
size_t access_threshold = 3,
size_t picture_cache_limit_per_frame = kDefaultPictureCacheLimitPerFrame);
static SkIRect GetDeviceBounds(const SkRect& rect, const SkMatrix& ctm) {
SkRect device_rect;
ctm.mapRect(&device_rect, rect);
SkIRect bounds;
device_rect.roundOut(&bounds);
return bounds;
}
static SkMatrix GetIntegralTransCTM(const SkMatrix& ctm) {
SkMatrix result = ctm;
result[SkMatrix::kMTransX] = SkScalarRoundToScalar(ctm.getTranslateX());
result[SkMatrix::kMTransY] = SkScalarRoundToScalar(ctm.getTranslateY());
return result;
}
// Return true if the cache is generated.
//
// We may return false and not generate the cache if
// 1. The picture is not worth rasterizing
// 2. The matrix is singular
// 3. The picture is accessed too few times
// 4. There are too many pictures to be cached in the current frame.
// (See also kDefaultPictureCacheLimitPerFrame.)
bool Prepare(GrContext* context, SkPicture* picture,
const SkMatrix& transformation_matrix,
SkColorSpace* dst_color_space, bool is_complex,
bool will_change);
void Prepare(PrerollContext* context, Layer* layer, const SkMatrix& ctm);
RasterCacheResult Get(const SkPicture& picture, const SkMatrix& ctm) const;
RasterCacheResult Get(Layer* layer, const SkMatrix& ctm) const;
void SweepAfterFrame();
void Clear();
void SetCheckboardCacheImages(bool checkerboard);
size_t GetCachedEntriesCount() const;
private:
struct Entry {
bool used_this_frame = false;
size_t access_count = 0;
RasterCacheResult image;
};
template <class Cache>
static void SweepOneCacheAfterFrame(Cache& cache) {
std::vector<typename Cache::iterator> dead;
for (auto it = cache.begin(); it != cache.end(); ++it) {
Entry& entry = it->second;
if (!entry.used_this_frame) {
dead.push_back(it);
}
entry.used_this_frame = false;
}
for (auto it : dead) {
cache.erase(it);
}
}
const size_t access_threshold_;
const size_t picture_cache_limit_per_frame_;
size_t picture_cached_this_frame_ = 0;
mutable PictureRasterCacheKey::Map<Entry> picture_cache_;
mutable LayerRasterCacheKey::Map<Entry> layer_cache_;
bool checkerboard_images_;
void TraceStatsToTimeline() const;
FML_DISALLOW_COPY_AND_ASSIGN(RasterCache);
};
} // namespace uiwidgets

7
engine/src/flow/raster_cache_key.cc


#include "flow/raster_cache_key.h"
namespace uiwidgets {
//
} // namespace uiwidgets

58
engine/src/flow/raster_cache_key.h


#pragma once
#include <unordered_map>
#include "flow/matrix_decomposition.h"
#include "flutter/fml/logging.h"
namespace uiwidgets {
template <typename ID>
class RasterCacheKey {
public:
RasterCacheKey(ID id, const SkMatrix& ctm) : id_(id), matrix_(ctm) {
matrix_[SkMatrix::kMTransX] = SkScalarFraction(ctm.getTranslateX());
matrix_[SkMatrix::kMTransY] = SkScalarFraction(ctm.getTranslateY());
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
FML_DCHECK(matrix_.getTranslateX() == 0 && matrix_.getTranslateY() == 0);
#endif
}
ID id() const { return id_; }
const SkMatrix& matrix() const { return matrix_; }
struct Hash {
uint32_t operator()(RasterCacheKey const& key) const {
return std::hash<ID>()(key.id_);
}
};
struct Equal {
constexpr bool operator()(const RasterCacheKey& lhs,
const RasterCacheKey& rhs) const {
return lhs.id_ == rhs.id_ && lhs.matrix_ == rhs.matrix_;
}
};
template <class Value>
using Map = std::unordered_map<RasterCacheKey, Value, Hash, Equal>;
private:
ID id_;
// ctm where only fractional (0-1) translations are preserved:
// matrix_ = ctm;
// matrix_[SkMatrix::kMTransX] = SkScalarFraction(ctm.getTranslateX());
// matrix_[SkMatrix::kMTransY] = SkScalarFraction(ctm.getTranslateY());
SkMatrix matrix_;
};
// The ID is the uint32_t picture uniqueID
using PictureRasterCacheKey = RasterCacheKey<uint32_t>;
class Layer;
// The ID is the uint64_t layer unique_id
using LayerRasterCacheKey = RasterCacheKey<uint64_t>;
} // namespace uiwidgets

87
engine/src/flow/rtree.cc


#include "rtree.h"
#include <list>
#include "flutter/fml/logging.h"
#include "include/core/SkBBHFactory.h"
namespace uiwidgets {
RTree::RTree() : bbh_{SkRTreeFactory{}()}, all_ops_count_(0) {}
void RTree::insert(const SkRect boundsArray[],
const SkBBoxHierarchy::Metadata metadata[], int N) {
FML_DCHECK(0 == all_ops_count_);
bbh_->insert(boundsArray, metadata, N);
for (int i = 0; i < N; i++) {
if (metadata != nullptr && metadata[i].isDraw) {
draw_op_[i] = boundsArray[i];
}
}
all_ops_count_ = N;
}
void RTree::insert(const SkRect boundsArray[], int N) {
insert(boundsArray, nullptr, N);
}
void RTree::search(const SkRect& query, std::vector<int>* results) const {
bbh_->search(query, results);
}
std::list<SkRect> RTree::searchNonOverlappingDrawnRects(
const SkRect& query) const {
// Get the indexes for the operations that intersect with the query rect.
std::vector<int> intermediary_results;
search(query, &intermediary_results);
std::list<SkRect> final_results;
for (int index : intermediary_results) {
auto draw_op = draw_op_.find(index);
// Ignore records that don't draw anything.
if (draw_op == draw_op_.end()) {
continue;
}
auto current_record_rect = draw_op->second;
auto replaced_existing_rect = false;
// // If the current record rect intersects with any of the rects in the
// // result list, then join them, and update the rect in final_results.
std::list<SkRect>::iterator curr_rect_itr = final_results.begin();
std::list<SkRect>::iterator first_intersecting_rect_itr;
while (!replaced_existing_rect && curr_rect_itr != final_results.end()) {
if (SkRect::Intersects(*curr_rect_itr, current_record_rect)) {
replaced_existing_rect = true;
first_intersecting_rect_itr = curr_rect_itr;
curr_rect_itr->join(current_record_rect);
}
curr_rect_itr++;
}
// It's possible that the result contains duplicated rects at this point.
// For example, consider a result list that contains rects A, B. If a
// new rect C is a superset of A and B, then A and B are the same set after
// the merge. As a result, find such cases and remove them from the result
// list.
while (replaced_existing_rect && curr_rect_itr != final_results.end()) {
if (SkRect::Intersects(*curr_rect_itr, *first_intersecting_rect_itr)) {
first_intersecting_rect_itr->join(*curr_rect_itr);
curr_rect_itr = final_results.erase(curr_rect_itr);
} else {
curr_rect_itr++;
}
}
if (!replaced_existing_rect) {
final_results.push_back(current_record_rect);
}
}
return final_results;
}
size_t RTree::bytesUsed() const { return bbh_->bytesUsed(); }
RTreeFactory::RTreeFactory() { r_tree_ = sk_make_sp<RTree>(); }
sk_sp<RTree> RTreeFactory::getInstance() { return r_tree_; }
sk_sp<SkBBoxHierarchy> RTreeFactory::operator()() const { return r_tree_; }
} // namespace uiwidgets

57
engine/src/flow/rtree.h


#pragma once
#include <list>
#include <map>
#include "include/core/SkBBHFactory.h"
#include "include/core/SkTypes.h"
namespace uiwidgets {
/**
* An R-Tree implementation that forwards calls to an SkRTree.
*
* This implementation provides a searchNonOverlappingDrawnRects method,
* which can be used to query the rects for the operations recorded in the tree.
*/
class RTree : public SkBBoxHierarchy {
public:
RTree();
void insert(const SkRect[], const SkBBoxHierarchy::Metadata[],
int N) override;
void insert(const SkRect[], int N) override;
void search(const SkRect& query, std::vector<int>* results) const override;
size_t bytesUsed() const override;
// Finds the rects in the tree that represent drawing operations and intersect
// with the query rect.
//
// When two rects intersect with each other, they are joined into a single
// rect which also intersects with the query rect. In other words, the bounds
// of each rect in the result list are mutually exclusive.
std::list<SkRect> searchNonOverlappingDrawnRects(const SkRect& query) const;
// Insertion count (not overall node count, which may be greater).
int getCount() const { return all_ops_count_; }
private:
// A map containing the draw operation rects keyed off the operation index
// in the insert call.
std::map<int, SkRect> draw_op_;
sk_sp<SkBBoxHierarchy> bbh_;
int all_ops_count_;
};
class RTreeFactory : public SkBBHFactory {
public:
RTreeFactory();
// Gets the instance to the R-tree.
sk_sp<RTree> getInstance();
sk_sp<SkBBoxHierarchy> operator()() const override;
private:
sk_sp<RTree> r_tree_;
};
} // namespace uiwidgets

46
engine/src/flow/skia_gpu_object.cc


#include "flow/skia_gpu_object.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/trace_event.h"
namespace uiwidgets {
SkiaUnrefQueue::SkiaUnrefQueue(fml::RefPtr<fml::TaskRunner> task_runner,
fml::TimeDelta delay,
fml::WeakPtr<GrContext> context)
: task_runner_(std::move(task_runner)),
drain_delay_(delay),
drain_pending_(false),
context_(context) {}
SkiaUnrefQueue::~SkiaUnrefQueue() { FML_DCHECK(objects_.empty()); }
void SkiaUnrefQueue::Unref(SkRefCnt* object) {
std::scoped_lock lock(mutex_);
objects_.push_back(object);
if (!drain_pending_) {
drain_pending_ = true;
task_runner_->PostDelayedTask(
[strong = fml::Ref(this)]() { strong->Drain(); }, drain_delay_);
}
}
void SkiaUnrefQueue::Drain() {
TRACE_EVENT0("uiwidgets", "SkiaUnrefQueue::Drain");
std::deque<SkRefCnt*> skia_objects;
{
std::scoped_lock lock(mutex_);
objects_.swap(skia_objects);
drain_pending_ = false;
}
for (SkRefCnt* skia_object : skia_objects) {
skia_object->unref();
}
if (context_ && skia_objects.size() > 0) {
context_->performDeferredCleanup(std::chrono::milliseconds(0));
}
}
} // namespace uiwidgets

83
engine/src/flow/skia_gpu_object.h


#pragma once
#include <mutex>
#include <queue>
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/task_runner.h"
#include "include/core/SkRefCnt.h"
#include "include/gpu/GrContext.h"
namespace uiwidgets {
// A queue that holds Skia objects that must be destructed on the given task
// runner.
class SkiaUnrefQueue : public fml::RefCountedThreadSafe<SkiaUnrefQueue> {
public:
void Unref(SkRefCnt* object);
// Usually, the drain is called automatically. However, during IO manager
// shutdown (when the platform side reference to the OpenGL context is about
// to go away), we may need to pre-emptively drain the unref queue. It is the
// responsibility of the caller to ensure that no further unrefs are queued
// after this call.
void Drain();
private:
const fml::RefPtr<fml::TaskRunner> task_runner_;
const fml::TimeDelta drain_delay_;
std::mutex mutex_;
std::deque<SkRefCnt*> objects_;
bool drain_pending_;
fml::WeakPtr<GrContext> context_;
// The `GrContext* context` is only used for signaling Skia to
// performDeferredCleanup. It can be nullptr when such signaling is not needed
// (e.g., in unit tests).
SkiaUnrefQueue(fml::RefPtr<fml::TaskRunner> task_runner, fml::TimeDelta delay,
fml::WeakPtr<GrContext> context = {});
~SkiaUnrefQueue();
FML_FRIEND_REF_COUNTED_THREAD_SAFE(SkiaUnrefQueue);
FML_FRIEND_MAKE_REF_COUNTED(SkiaUnrefQueue);
FML_DISALLOW_COPY_AND_ASSIGN(SkiaUnrefQueue);
};
/// An object whose deallocation needs to be performed on an specific unref
/// queue. The template argument U need to have a call operator that returns
/// that unref queue.
template <class T>
class SkiaGPUObject {
public:
using SkiaObjectType = T;
SkiaGPUObject() = default;
SkiaGPUObject(sk_sp<SkiaObjectType> object, fml::RefPtr<SkiaUnrefQueue> queue)
: object_(std::move(object)), queue_(std::move(queue)) {
FML_DCHECK(object_);
}
SkiaGPUObject(SkiaGPUObject&&) = default;
~SkiaGPUObject() { reset(); }
SkiaGPUObject& operator=(SkiaGPUObject&&) = default;
sk_sp<SkiaObjectType> get() const { return object_; }
void reset() {
if (object_ && queue_) {
queue_->Unref(object_.release());
}
queue_ = nullptr;
FML_DCHECK(object_ == nullptr);
}
private:
sk_sp<SkiaObjectType> object_;
fml::RefPtr<SkiaUnrefQueue> queue_;
FML_DISALLOW_COPY_AND_ASSIGN(SkiaGPUObject);
};
} // namespace uiwidgets

44
engine/src/flow/texture.cc


#include "flow/texture.h"
namespace uiwidgets {
Texture::Texture(int64_t id) : id_(id) {}
Texture::~Texture() = default;
TextureRegistry::TextureRegistry() = default;
void TextureRegistry::RegisterTexture(std::shared_ptr<Texture> texture) {
if (!texture) {
return;
}
mapping_[texture->Id()] = texture;
}
void TextureRegistry::UnregisterTexture(int64_t id) {
auto found = mapping_.find(id);
if (found == mapping_.end()) {
return;
}
found->second->OnTextureUnregistered();
mapping_.erase(found);
}
void TextureRegistry::OnGrContextCreated() {
for (auto& it : mapping_) {
it.second->OnGrContextCreated();
}
}
void TextureRegistry::OnGrContextDestroyed() {
for (auto& it : mapping_) {
it.second->OnGrContextDestroyed();
}
}
std::shared_ptr<Texture> TextureRegistry::GetTexture(int64_t id) {
auto it = mapping_.find(id);
return it != mapping_.end() ? it->second : nullptr;
}
} // namespace uiwidgets

65
engine/src/flow/texture.h


#pragma once
#include <map>
#include "flutter/fml/macros.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "include/core/SkCanvas.h"
namespace uiwidgets {
class Texture {
public:
Texture(int64_t id); // Called from UI or raster thread.
virtual ~Texture(); // Called from raster thread.
// Called from raster thread.
virtual void Paint(SkCanvas& canvas, const SkRect& bounds, bool freeze,
GrContext* context) = 0;
// Called from raster thread.
virtual void OnGrContextCreated() = 0;
// Called from raster thread.
virtual void OnGrContextDestroyed() = 0;
// Called on raster thread.
virtual void MarkNewFrameAvailable() = 0;
// Called on raster thread.
virtual void OnTextureUnregistered() = 0;
int64_t Id() { return id_; }
private:
int64_t id_;
FML_DISALLOW_COPY_AND_ASSIGN(Texture);
};
class TextureRegistry {
public:
TextureRegistry();
// Called from raster thread.
void RegisterTexture(std::shared_ptr<Texture> texture);
// Called from raster thread.
void UnregisterTexture(int64_t id);
// Called from raster thread.
std::shared_ptr<Texture> GetTexture(int64_t id);
// Called from raster thread.
void OnGrContextCreated();
// Called from raster thread.
void OnGrContextDestroyed();
private:
std::map<int64_t, std::shared_ptr<Texture>> mapping_;
FML_DISALLOW_COPY_AND_ASSIGN(TextureRegistry);
};
} // namespace uiwidgets

23
engine/src/lib/ui/io_manager.h


#pragma once
#include "flow/skia_gpu_object.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/synchronization/sync_switch.h"
#include "include/gpu/GrContext.h"
namespace uiwidgets {
class IOManager {
public:
virtual ~IOManager() = default;
virtual fml::WeakPtr<IOManager> GetWeakIOManager() const = 0;
virtual fml::WeakPtr<GrContext> GetResourceContext() const = 0;
virtual fml::RefPtr<SkiaUnrefQueue> GetSkiaUnrefQueue() const = 0;
virtual std::shared_ptr<fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() = 0;
};
} // namespace uiwidgets

33
engine/src/lib/ui/painting/canvas.cc


#include "canvas.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include "flow/layers/physical_shape_layer.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkRSXform.h"
#include "lib/ui/painting/image.h"
#include "lib/ui/window/window.h"
#include "picture_recorder.h"
namespace uiwidgets {
fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder, double left,
double top, double right, double bottom) {
if (!recorder)
Mono_ThrowException(
"Canvas constructor called with non-genuine PictureRecorder.");
FML_DCHECK(!recorder->isRecording()); // verified by Dart code
fml::RefPtr<Canvas> canvas = fml::MakeRefCounted<Canvas>(
recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
recorder->set_canvas(canvas);
return canvas;
}
Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}
Canvas::~Canvas() {}
} // namespace uiwidgets

29
engine/src/lib/ui/painting/canvas.h


#pragma once
#include <flutter/fml/memory/ref_counted.h>
#include <flutter/fml/memory/ref_ptr.h>
#include "include/core/SkCanvas.h"
#include "lib/ui/painting/picture.h"
#include "lib/ui/painting/picture_recorder.h"
namespace uiwidgets {
class PictureRecorder;
class CanvasImage;
class Canvas : public fml::RefCountedThreadSafe<Canvas> {
FML_FRIEND_MAKE_REF_COUNTED(Canvas);
public:
static fml::RefPtr<Canvas> Create(PictureRecorder* recorder, double left,
double top, double right, double bottom);
~Canvas();
private:
explicit Canvas(SkCanvas* canvas);
SkCanvas* canvas_;
};
} // namespace uiwidgets

31
engine/src/lib/ui/painting/image.cc


#include "image.h"
#include "image_encoding.h"
namespace uiwidgets {
typedef CanvasImage Image;
CanvasImage::CanvasImage() = default;
CanvasImage::~CanvasImage() = default;
const char* CanvasImage::toByteData(int format, EncodeImageCallback callback,
Mono_Handle callback_handle) {
return EncodeImage(this, format, callback, callback_handle);
}
void CanvasImage::dispose() {}
size_t CanvasImage::GetAllocationSize() {
if (auto image = image_.get()) {
const auto& info = image->imageInfo();
const auto kMipmapOverhead = 4.0 / 3.0;
const size_t image_byte_size = info.computeMinByteSize() * kMipmapOverhead;
return image_byte_size + sizeof(this);
} else {
return sizeof(CanvasImage);
}
}
} // namespace uiwidgets

38
engine/src/lib/ui/painting/image.h


#pragma once
#include "flow/skia_gpu_object.h"
#include "image_encoding.h"
#include "include/core/SkImage.h"
namespace uiwidgets {
class CanvasImage final : public fml::RefCountedThreadSafe<CanvasImage> {
FML_FRIEND_MAKE_REF_COUNTED(CanvasImage);
public:
~CanvasImage();
static fml::RefPtr<CanvasImage> Create() {
return fml::MakeRefCounted<CanvasImage>();
}
int width() { return image_.get()->width(); }
int height() { return image_.get()->height(); }
const char* toByteData(int format, EncodeImageCallback callback,
Mono_Handle callback_handle);
void dispose();
sk_sp<SkImage> image() const { return image_.get(); }
void set_image(SkiaGPUObject<SkImage> image) { image_ = std::move(image); }
size_t GetAllocationSize();
private:
CanvasImage();
SkiaGPUObject<SkImage> image_;
};
} // namespace uiwidgets

11
engine/src/lib/ui/painting/image_decoder.cc


#include "image_decoder.h"
#include <algorithm>
#include "flutter/fml/make_copyable.h"
#include "include/codec/SkCodec.h"
#include "src/codec/SkCodecImageGenerator.h"
namespace uiwidgets {
} // namespace uiwidgets

8
engine/src/lib/ui/painting/image_decoder.h


#pragma once
namespace uiwidgets {
class ImageDecoder {
};
} // namespace uiwidgets

234
engine/src/lib/ui/painting/image_encoding.cc


#include "image_encoding.h"
#include <memory>
#include <utility>
#include "flutter/fml/build_config.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkEncodedImageFormat.h"
#include "include/core/SkImage.h"
#include "include/core/SkSurface.h"
#include "lib/ui/painting/image.h"
#include "lib/ui/ui_mono_state.h"
namespace uiwidgets {
namespace {
// This must be kept in sync with the enum in painting.dart
enum ImageByteFormat {
kRawRGBA,
kRawUnmodified,
kPNG,
};
sk_sp<SkImage> ConvertToRasterUsingResourceContext(
sk_sp<SkImage> image, GrContext* resource_context) {
sk_sp<SkSurface> surface;
SkImageInfo surface_info = SkImageInfo::MakeN32Premul(image->dimensions());
if (resource_context) {
surface = SkSurface::MakeRenderTarget(resource_context, SkBudgeted::kNo,
surface_info);
} else {
surface = SkSurface::MakeRaster(surface_info);
}
if (surface == nullptr || surface->getCanvas() == nullptr) {
FML_LOG(ERROR) << "Could not create a surface to copy the texture into.";
return nullptr;
}
surface->getCanvas()->drawImage(image, 0, 0);
surface->getCanvas()->flush();
auto snapshot = surface->makeImageSnapshot();
if (snapshot == nullptr) {
FML_LOG(ERROR) << "Could not snapshot image to encode.";
return nullptr;
}
return snapshot->makeRasterImage();
}
void ConvertImageToRaster(sk_sp<SkImage> image,
std::function<void(sk_sp<SkImage>)> encode_task,
fml::RefPtr<fml::TaskRunner> raster_task_runner,
fml::RefPtr<fml::TaskRunner> io_task_runner,
GrContext* resource_context,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate) {
// Check validity of the image.
if (image == nullptr) {
FML_LOG(ERROR) << "Image was null.";
encode_task(nullptr);
return;
}
auto dimensions = image->dimensions();
if (dimensions.isEmpty()) {
FML_LOG(ERROR) << "Image dimensions were empty.";
encode_task(nullptr);
return;
}
SkPixmap pixmap;
if (image->peekPixels(&pixmap)) {
// This is already a raster image.
encode_task(image);
return;
}
if (sk_sp<SkImage> raster_image = image->makeRasterImage()) {
// The image can be converted to a raster image.
encode_task(raster_image);
return;
}
// Cross-context images do not support makeRasterImage. Convert these images
// by drawing them into a surface. This must be done on the raster thread
// to prevent concurrent usage of the image on both the IO and raster threads.
raster_task_runner->PostTask([image, encode_task = std::move(encode_task),
resource_context, snapshot_delegate,
io_task_runner]() {
sk_sp<SkImage> raster_image =
snapshot_delegate->ConvertToRasterImage(image);
io_task_runner->PostTask([image, encode_task = std::move(encode_task),
raster_image = std::move(raster_image),
resource_context]() mutable {
if (!raster_image) {
// The rasterizer was unable to render the cross-context image
// (presumably because it does not have a GrContext). In that case,
// convert the image on the IO thread using the resource context.
raster_image =
ConvertToRasterUsingResourceContext(image, resource_context);
}
encode_task(raster_image);
});
});
}
sk_sp<SkData> CopyImageByteData(sk_sp<SkImage> raster_image,
SkColorType color_type) {
FML_DCHECK(raster_image);
SkPixmap pixmap;
if (!raster_image->peekPixels(&pixmap)) {
FML_LOG(ERROR) << "Could not copy pixels from the raster image.";
return nullptr;
}
// The color types already match. No need to swizzle. Return early.
if (pixmap.colorType() == color_type) {
return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());
}
// Perform swizzle if the type doesnt match the specification.
auto surface = SkSurface::MakeRaster(
SkImageInfo::Make(raster_image->width(), raster_image->height(),
color_type, kPremul_SkAlphaType, nullptr));
if (!surface) {
FML_LOG(ERROR) << "Could not setup the surface for swizzle.";
return nullptr;
}
surface->writePixels(pixmap, 0, 0);
if (!surface->peekPixels(&pixmap)) {
FML_LOG(ERROR) << "Pixel address is not available.";
return nullptr;
}
return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());
}
sk_sp<SkData> EncodeImage(sk_sp<SkImage> raster_image, ImageByteFormat format) {
TRACE_EVENT0("flutter", __FUNCTION__);
if (!raster_image) {
return nullptr;
}
switch (format) {
case kPNG: {
auto png_image =
raster_image->encodeToData(SkEncodedImageFormat::kPNG, 0);
if (png_image == nullptr) {
FML_LOG(ERROR) << "Could not convert raster image to PNG.";
return nullptr;
};
return png_image;
} break;
case kRawRGBA: {
return CopyImageByteData(raster_image, kRGBA_8888_SkColorType);
} break;
case kRawUnmodified: {
return CopyImageByteData(raster_image, raster_image->colorType());
} break;
}
FML_LOG(ERROR) << "Unknown error encoding image.";
return nullptr;
}
void EncodeImageAndInvokeDataCallback(
sk_sp<SkImage> image, EncodeImageCallback callback,
Mono_Handle callback_handle, ImageByteFormat format,
fml::RefPtr<fml::TaskRunner> ui_task_runner,
fml::RefPtr<fml::TaskRunner> raster_task_runner,
fml::RefPtr<fml::TaskRunner> io_task_runner, GrContext* resource_context,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate) {
auto callback_task = fml::MakeCopyable(
[callback, callback_handle](sk_sp<SkData> encoded) mutable {
callback(callback_handle, encoded->bytes(), encoded->size());
});
auto encode_task = [callback_task = std::move(callback_task), format,
ui_task_runner](sk_sp<SkImage> raster_image) {
sk_sp<SkData> encoded = EncodeImage(std::move(raster_image), format);
ui_task_runner->PostTask(
[callback_task = std::move(callback_task),
encoded = std::move(encoded)] { callback_task(encoded); });
};
ConvertImageToRaster(std::move(image), encode_task, raster_task_runner,
io_task_runner, resource_context, snapshot_delegate);
}
} // namespace
const char* EncodeImage(CanvasImage* canvas_image, int format,
EncodeImageCallback callback,
Mono_Handle callback_handle) {
if (!canvas_image) return "encode called with non-genuine Image.";
if (!callback || !callback_handle) return "Callback must be a function.";
ImageByteFormat image_format = static_cast<ImageByteFormat>(format);
const auto& task_runners = UIMonoState::Current()->GetTaskRunners();
task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
[callback, callback_handle, image = canvas_image->image(), image_format,
ui_task_runner = task_runners.GetUITaskRunner(),
raster_task_runner = task_runners.GetRasterTaskRunner(),
io_task_runner = task_runners.GetIOTaskRunner(),
io_manager = UIMonoState::Current()->GetIOManager(),
snapshot_delegate =
UIMonoState::Current()->GetSnapshotDelegate()]() mutable {
EncodeImageAndInvokeDataCallback(
std::move(image), callback, callback_handle, image_format,
std::move(ui_task_runner), std::move(raster_task_runner),
std::move(io_task_runner), io_manager->GetResourceContext().get(),
std::move(snapshot_delegate));
}));
return nullptr;
}
} // namespace uiwidgets

16
engine/src/lib/ui/painting/image_encoding.h


#pragma once
#include "runtime/mono_api.h"
namespace uiwidgets {
class CanvasImage;
typedef void (*EncodeImageCallback)(Mono_Handle callback_handle,
const uint8_t* data, size_t length);
const char* EncodeImage(CanvasImage* canvas_image, int format,
EncodeImageCallback callback,
Mono_Handle callback_handle);
} // namespace uiwidgets

98
engine/src/lib/ui/painting/picture.cc


#include "picture.h"
#include "flutter/fml/make_copyable.h"
#include "image.h"
#include "include/core/SkImage.h"
#include "lib/ui/painting/canvas.h"
#include "lib/ui/ui_mono_state.h"
namespace uiwidgets {
fml::RefPtr<Picture> Picture::Create(SkiaGPUObject<SkPicture> picture) {
return fml::MakeRefCounted<Picture>(std::move(picture));
}
Picture::Picture(SkiaGPUObject<SkPicture> picture)
: picture_(std::move(picture)) {}
Picture::~Picture() = default;
const char* Picture::toImage(uint32_t width, uint32_t height,
RawImageCallback raw_image_callback,
Mono_Handle callback_handle) {
if (!picture_.get()) {
return "Picture is null";
}
return RasterizeToImage(picture_.get(), width, height, raw_image_callback,
callback_handle);
}
void Picture::dispose() {}
const char* Picture::RasterizeToImage(sk_sp<SkPicture> picture, uint32_t width,
uint32_t height,
RawImageCallback raw_image_callback,
Mono_Handle callback_handle) {
if (!raw_image_callback || !callback_handle) {
return "Image callback was invalid";
}
if (width == 0 || height == 0) {
return "Image dimensions for scene were invalid.";
}
auto* dart_state = UIMonoState::Current();
auto dart_state_weak = dart_state->GetWeakPtr();
auto unref_queue = dart_state->GetSkiaUnrefQueue();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner();
auto snapshot_delegate = dart_state->GetSnapshotDelegate();
// We can't create an image on this task runner because we don't have a
// graphics context. Even if we did, it would be slow anyway. Also, this
// thread owns the sole reference to the layer tree. So we flatten the layer
// tree into a picture and use that as the thread transport mechanism.
auto picture_bounds = SkISize::Make(width, height);
auto ui_task = fml::MakeCopyable([dart_state_weak, raw_image_callback,
callback_handle, unref_queue](
sk_sp<SkImage> raster_image) mutable {
auto dart_state = dart_state_weak.lock();
if (!dart_state) {
// The root isolate could have died in the meantime.
return;
}
MonoState::Scope scope(dart_state);
if (!raster_image) {
raw_image_callback(callback_handle, nullptr);
return;
}
auto dart_image = CanvasImage::Create();
dart_image->set_image({std::move(raster_image), std::move(unref_queue)});
auto* raw_dart_image = dart_image.get();
// All done!
raw_image_callback(callback_handle, raw_dart_image);
});
// Kick things off on the raster rask runner.
fml::TaskRunner::RunNowOrPostTask(
raster_task_runner,
[ui_task_runner, snapshot_delegate, picture, picture_bounds, ui_task] {
sk_sp<SkImage> raster_image =
snapshot_delegate->MakeRasterSnapshot(picture, picture_bounds);
fml::TaskRunner::RunNowOrPostTask(
ui_task_runner,
[ui_task, raster_image]() { ui_task(raster_image); });
});
return nullptr;
}
} // namespace uiwidgets

39
engine/src/lib/ui/painting/picture.h


#pragma once
#include "flow/skia_gpu_object.h"
#include "image.h"
#include "include/core/SkPicture.h"
namespace uiwidgets {
class Canvas;
class Picture : public fml::RefCountedThreadSafe<Picture> {
FML_FRIEND_MAKE_REF_COUNTED(Picture);
public:
~Picture();
static fml::RefPtr<Picture> Create(SkiaGPUObject<SkPicture> picture);
sk_sp<SkPicture> picture() const { return picture_.get(); }
typedef void (*RawImageCallback)(Mono_Handle callback_handle,
CanvasImage* image);
const char* toImage(uint32_t width, uint32_t height,
RawImageCallback raw_image_callback,
Mono_Handle callback_handle);
void dispose();
static const char* RasterizeToImage(sk_sp<SkPicture> picture, uint32_t width,
uint32_t height,
RawImageCallback raw_image_callback,
Mono_Handle callback_handle);
private:
explicit Picture(SkiaGPUObject<SkPicture> picture);
SkiaGPUObject<SkPicture> picture_;
};
} // namespace uiwidgets

63
engine/src/lib/ui/painting/picture_recorder.cc


#include "picture_recorder.h"
#include <Unity/IUnityInterface.h>
#include "lib/ui/painting/canvas.h"
#include "lib/ui/painting/picture.h"
#include "lib/ui/ui_mono_state.h"
namespace uiwidgets {
fml::RefPtr<PictureRecorder> PictureRecorder::Create() {
return fml::MakeRefCounted<PictureRecorder>();
}
PictureRecorder::PictureRecorder() {}
PictureRecorder::~PictureRecorder() {}
bool PictureRecorder::isRecording() {
//return canvas_ && canvas_->IsRecording();
return true;
}
SkCanvas* PictureRecorder::BeginRecording(SkRect bounds) {
return picture_recorder_.beginRecording(bounds, &rtree_factory_);
}
fml::RefPtr<Picture> PictureRecorder::endRecording() {
if (!isRecording()) return nullptr;
fml::RefPtr<Picture> picture = Picture::Create(UIMonoState::CreateGPUObject(
picture_recorder_.finishRecordingAsPicture()));
//canvas_->Clear();
canvas_ = nullptr;
return picture;
}
extern "C" UNITY_INTERFACE_EXPORT PictureRecorder* UNITY_INTERFACE_API
PictureRecorder_constructor() {
const auto recorder = PictureRecorder::Create();
recorder->AddRef();
return recorder.get();
}
extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API
PictureRecorder_dispose(PictureRecorder* ptr) {
ptr->Release();
}
extern "C" UNITY_INTERFACE_EXPORT bool UNITY_INTERFACE_API
PictureRecorder_isRecording(PictureRecorder* ptr) {
return ptr->isRecording();
}
extern "C" UNITY_INTERFACE_EXPORT void* UNITY_INTERFACE_API
PictureRecorder_endRecording(PictureRecorder* ptr) {
const auto picture = ptr->endRecording();
picture->AddRef();
return picture.get();
}
} // namespace uiwidgets

32
engine/src/lib/ui/painting/picture_recorder.h


#pragma once
#include <flutter/fml/memory/ref_counted.h>
#include "include/core/SkPictureRecorder.h"
namespace uiwidgets {
class Canvas;
class Picture;
class PictureRecorder : public fml::RefCountedThreadSafe<PictureRecorder> {
FML_FRIEND_MAKE_REF_COUNTED(PictureRecorder);
public:
static fml::RefPtr<PictureRecorder> Create();
~PictureRecorder();
SkCanvas* BeginRecording(SkRect bounds);
fml::RefPtr<Picture> endRecording();
bool isRecording();
void set_canvas(fml::RefPtr<Canvas> canvas) { canvas_ = std::move(canvas); }
private:
PictureRecorder();
SkRTreeFactory rtree_factory_;
SkPictureRecorder picture_recorder_;
fml::RefPtr<Canvas> canvas_;
};
} // namespace uiwidgets

16
engine/src/lib/ui/snapshot_delegate.h


#pragma once
#include "include/core/SkImage.h"
#include "include/core/SkPicture.h"
namespace uiwidgets {
class SnapshotDelegate {
public:
virtual sk_sp<SkImage> MakeRasterSnapshot(sk_sp<SkPicture> picture,
SkISize picture_size) = 0;
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) = 0;
};
} // namespace uiwidgets

83
engine/src/lib/ui/ui_mono_state.cc


#include "ui_mono_state.h"
#include "common/settings.h"
#include "common/task_runners.h"
#include "flutter/fml/message_loop.h"
namespace uiwidgets {
UIMonoState::UIMonoState(TaskRunners task_runners, TaskObserverAdd add_callback,
TaskObserverRemove remove_callback,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate,
fml::WeakPtr<IOManager> io_manager,
fml::RefPtr<SkiaUnrefQueue> skia_unref_queue,
fml::WeakPtr<ImageDecoder> image_decoder)
: task_runners_(std::move(task_runners)),
add_callback_(std::move(add_callback)),
remove_callback_(std::move(remove_callback)),
snapshot_delegate_(std::move(snapshot_delegate)),
io_manager_(std::move(io_manager)),
skia_unref_queue_(std::move(skia_unref_queue)),
image_decoder_(std::move(image_decoder)) {
AddOrRemoveTaskObserver(true /* add */);
}
UIMonoState::~UIMonoState() { AddOrRemoveTaskObserver(false /* remove */); }
UIMonoState* UIMonoState::Current() {
return static_cast<UIMonoState*>(MonoState::Current());
}
void UIMonoState::SetWindow(std::unique_ptr<Window> window) {
window_ = std::move(window);
}
const TaskRunners& UIMonoState::GetTaskRunners() const { return task_runners_; }
fml::WeakPtr<IOManager> UIMonoState::GetIOManager() const {
return io_manager_;
}
fml::RefPtr<SkiaUnrefQueue> UIMonoState::GetSkiaUnrefQueue() const {
return skia_unref_queue_;
}
void UIMonoState::ScheduleMicrotask(MonoMicrotaskQueue::CallbackFunc callback,
Mono_Handle handle) {
microtask_queue_.ScheduleMicrotask(callback, handle);
}
void UIMonoState::FlushMicrotasksNow() { microtask_queue_.RunMicrotasks(); }
void UIMonoState::AddOrRemoveTaskObserver(bool add) {
auto task_runner = task_runners_.GetUITaskRunner();
if (!task_runner) {
// This may happen in case the isolate has no thread affinity (for example,
// the service isolate).
return;
}
FML_DCHECK(add_callback_ && remove_callback_);
if (add) {
add_callback_(reinterpret_cast<intptr_t>(this),
[this]() { this->FlushMicrotasksNow(); });
} else {
remove_callback_(reinterpret_cast<intptr_t>(this));
}
}
fml::WeakPtr<SnapshotDelegate> UIMonoState::GetSnapshotDelegate() const {
return snapshot_delegate_;
}
fml::WeakPtr<GrContext> UIMonoState::GetResourceContext() const {
if (!io_manager_) {
return {};
}
return io_manager_->GetResourceContext();
}
fml::WeakPtr<ImageDecoder> UIMonoState::GetImageDecoder() const {
return image_decoder_;
}
} // namespace uiwidgets

75
engine/src/lib/ui/ui_mono_state.h


#pragma once
#include <flutter/fml/memory/weak_ptr.h>
#include "common/settings.h"
#include "common/task_runners.h"
#include "io_manager.h"
#include "runtime/mono_microtask_queue.h"
#include "runtime/mono_state.h"
#include "snapshot_delegate.h"
#include "lib/ui/window/window.h"
#include "lib/ui/painting/image_decoder.h"
namespace uiwidgets {
class UIMonoState : public MonoState {
public:
static UIMonoState* Current();
Window* window() const { return window_.get(); }
const TaskRunners& GetTaskRunners() const;
void ScheduleMicrotask(MonoMicrotaskQueue::CallbackFunc callback,
Mono_Handle handle);
void FlushMicrotasksNow();
fml::WeakPtr<IOManager> GetIOManager() const;
fml::RefPtr<SkiaUnrefQueue> GetSkiaUnrefQueue() const;
fml::WeakPtr<SnapshotDelegate> GetSnapshotDelegate() const;
fml::WeakPtr<GrContext> GetResourceContext() const;
fml::WeakPtr<ImageDecoder> GetImageDecoder() const;
template <class T>
static SkiaGPUObject<T> CreateGPUObject(sk_sp<T> object) {
if (!object) {
return {};
}
auto* state = UIMonoState::Current();
FML_DCHECK(state);
auto queue = state->GetSkiaUnrefQueue();
return {std::move(object), std::move(queue)};
};
protected:
UIMonoState(TaskRunners task_runners, TaskObserverAdd add_callback,
TaskObserverRemove remove_callback,
fml::WeakPtr<SnapshotDelegate> snapshot_delegate,
fml::WeakPtr<IOManager> io_manager,
fml::RefPtr<SkiaUnrefQueue> skia_unref_queue,
fml::WeakPtr<ImageDecoder> image_decoder);
~UIMonoState();
void SetWindow(std::unique_ptr<Window> window);
private:
const TaskRunners task_runners_;
const TaskObserverAdd add_callback_;
const TaskObserverRemove remove_callback_;
fml::WeakPtr<SnapshotDelegate> snapshot_delegate_;
fml::WeakPtr<IOManager> io_manager_;
fml::RefPtr<SkiaUnrefQueue> skia_unref_queue_;
fml::WeakPtr<ImageDecoder> image_decoder_;
std::unique_ptr<Window> window_;
MonoMicrotaskQueue microtask_queue_;
void AddOrRemoveTaskObserver(bool add);
};
} // namespace uiwidgets

27
engine/src/lib/ui/window/platform_message.cc


// 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/lib/ui/window/platform_message.h"
#include <utility>
namespace flutter {
PlatformMessage::PlatformMessage(std::string channel,
std::vector<uint8_t> data,
fml::RefPtr<PlatformMessageResponse> response)
: channel_(std::move(channel)),
data_(std::move(data)),
hasData_(true),
response_(std::move(response)) {}
PlatformMessage::PlatformMessage(std::string channel,
fml::RefPtr<PlatformMessageResponse> response)
: channel_(std::move(channel)),
data_(),
hasData_(false),
response_(std::move(response)) {}
PlatformMessage::~PlatformMessage() = default;
} // namespace flutter

39
engine/src/lib/ui/window/platform_message.h


#pragma once
#include <string>
#include <vector>
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/memory/ref_ptr.h"
#include "platform_message_response.h"
namespace uiwidgets {
class PlatformMessage : public fml::RefCountedThreadSafe<PlatformMessage> {
FML_FRIEND_REF_COUNTED_THREAD_SAFE(PlatformMessage);
FML_FRIEND_MAKE_REF_COUNTED(PlatformMessage);
public:
const std::string& channel() const { return channel_; }
const std::vector<uint8_t>& data() const { return data_; }
bool hasData() { return hasData_; }
const fml::RefPtr<PlatformMessageResponse>& response() const {
return response_;
}
private:
PlatformMessage(std::string channel,
std::vector<uint8_t> data,
fml::RefPtr<PlatformMessageResponse> response);
PlatformMessage(std::string channel,
fml::RefPtr<PlatformMessageResponse> response);
~PlatformMessage();
std::string channel_;
std::vector<uint8_t> data_;
bool hasData_;
fml::RefPtr<PlatformMessageResponse> response_;
};
} // namespace uiwidgets

9
engine/src/lib/ui/window/platform_message_response.cc


#include "platform_message_response.h"
namespace uiwidgets {
PlatformMessageResponse::PlatformMessageResponse() = default;
PlatformMessageResponse::~PlatformMessageResponse() = default;
} // namespace uiwidgets

29
engine/src/lib/ui/window/platform_message_response.h


#pragma once
#include <vector>
#include "flutter/fml/mapping.h"
#include "flutter/fml/memory/ref_counted.h"
#include "flutter/fml/memory/ref_ptr.h"
namespace uiwidgets {
class PlatformMessageResponse
: public fml::RefCountedThreadSafe<PlatformMessageResponse> {
FML_FRIEND_REF_COUNTED_THREAD_SAFE(PlatformMessageResponse);
public:
// Callable on any thread.
virtual void Complete(std::unique_ptr<fml::Mapping> data) = 0;
virtual void CompleteEmpty() = 0;
bool is_complete() const { return is_complete_; }
protected:
PlatformMessageResponse();
virtual ~PlatformMessageResponse();
bool is_complete_ = false;
};
} // namespace uiwidgets

部分文件因为文件数量过多而无法显示

正在加载...
取消
保存