Kevin Gu
4 年前
当前提交
eea80dd6
共有 136 个文件被更改,包括 6609 次插入 和 14 次删除
-
16com.unity.uiwidgets/Runtime/ui/geometry.cs
-
131engine/Build.bee.cs
-
3com.unity.uiwidgets/Runtime/ui2.meta
-
6engine/.gitignore
-
3com.unity.uiwidgets/Runtime/ui2/painting.meta
-
3com.unity.uiwidgets/Runtime/ui2/painting/Isolate.cs.meta
-
49com.unity.uiwidgets/Runtime/ui2/painting/isolate.cs
-
203com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs
-
3com.unity.uiwidgets/Runtime/ui2/painting/native_bindings.cs.meta
-
1001com.unity.uiwidgets/Runtime/ui2/painting/painting.cs
-
3com.unity.uiwidgets/Runtime/ui2/painting/painting.cs.meta
-
61engine/src/common/settings.cc
-
204engine/src/common/settings.h
-
46engine/src/common/task_runners.cc
-
42engine/src/common/task_runners.h
-
104engine/src/flow/compositor_context.cc
-
114engine/src/flow/compositor_context.h
-
49engine/src/flow/embedded_views.cc
-
253engine/src/flow/embedded_views.h
-
312engine/src/flow/instrumentation.cc
-
94engine/src/flow/instrumentation.h
-
25engine/src/flow/layers/backdrop_filter_layer.cc
-
20engine/src/flow/layers/backdrop_filter_layer.h
-
56engine/src/flow/layers/clip_path_layer.cc
-
25engine/src/flow/layers/clip_path_layer.h
-
55engine/src/flow/layers/clip_rect_layer.cc
-
26engine/src/flow/layers/clip_rect_layer.h
-
56engine/src/flow/layers/clip_rrect_layer.cc
-
25engine/src/flow/layers/clip_rrect_layer.h
-
27engine/src/flow/layers/color_filter_layer.cc
-
21engine/src/flow/layers/color_filter_layer.h
-
64engine/src/flow/layers/container_layer.cc
-
34engine/src/flow/layers/container_layer.h
-
69engine/src/flow/layers/image_filter_layer.cc
-
23engine/src/flow/layers/image_filter_layer.h
-
83engine/src/flow/layers/layer.cc
-
172engine/src/flow/layers/layer.h
-
158engine/src/flow/layers/layer_tree.cc
-
88engine/src/flow/layers/layer_tree.h
-
108engine/src/flow/layers/opacity_layer.cc
-
41engine/src/flow/layers/opacity_layer.h
-
90engine/src/flow/layers/performance_overlay_layer.cc
-
36engine/src/flow/layers/performance_overlay_layer.h
-
179engine/src/flow/layers/physical_shape_layer.cc
-
39engine/src/flow/layers/physical_shape_layer.h
-
60engine/src/flow/layers/picture_layer.cc
-
33engine/src/flow/layers/picture_layer.h
-
39engine/src/flow/layers/platform_view_layer.cc
-
24engine/src/flow/layers/platform_view_layer.h
-
32engine/src/flow/layers/shader_mask_layer.cc
-
25engine/src/flow/layers/shader_mask_layer.h
-
31engine/src/flow/layers/texture_layer.cc
-
26engine/src/flow/layers/texture_layer.h
-
59engine/src/flow/layers/transform_layer.cc
-
23engine/src/flow/layers/transform_layer.h
-
146engine/src/flow/matrix_decomposition.cc
-
44engine/src/flow/matrix_decomposition.h
-
49engine/src/flow/paint_utils.cc
-
13engine/src/flow/paint_utils.h
-
289engine/src/flow/raster_cache.cc
-
130engine/src/flow/raster_cache.h
-
7engine/src/flow/raster_cache_key.cc
-
58engine/src/flow/raster_cache_key.h
-
87engine/src/flow/rtree.cc
-
57engine/src/flow/rtree.h
-
46engine/src/flow/skia_gpu_object.cc
-
83engine/src/flow/skia_gpu_object.h
-
44engine/src/flow/texture.cc
-
65engine/src/flow/texture.h
-
23engine/src/lib/ui/io_manager.h
-
33engine/src/lib/ui/painting/canvas.cc
-
29engine/src/lib/ui/painting/canvas.h
-
31engine/src/lib/ui/painting/image.cc
-
38engine/src/lib/ui/painting/image.h
-
11engine/src/lib/ui/painting/image_decoder.cc
-
8engine/src/lib/ui/painting/image_decoder.h
-
234engine/src/lib/ui/painting/image_encoding.cc
-
16engine/src/lib/ui/painting/image_encoding.h
-
98engine/src/lib/ui/painting/picture.cc
-
39engine/src/lib/ui/painting/picture.h
-
63engine/src/lib/ui/painting/picture_recorder.cc
-
32engine/src/lib/ui/painting/picture_recorder.h
-
16engine/src/lib/ui/snapshot_delegate.h
-
83engine/src/lib/ui/ui_mono_state.cc
-
75engine/src/lib/ui/ui_mono_state.h
-
27engine/src/lib/ui/window/platform_message.cc
-
39engine/src/lib/ui/window/platform_message.h
-
9engine/src/lib/ui/window/platform_message_response.cc
-
29engine/src/lib/ui/window/platform_message_response.h
|
|||
fileFormatVersion: 2 |
|||
guid: d9f439c3630548cbada41ef026dfa6ed |
|||
timeCreated: 1595649299 |
|
|||
.idea |
|||
.vs |
|||
artifacts |
|||
build |
|||
obj |
|||
*.gen.* |
|
|||
fileFormatVersion: 2 |
|||
guid: e01e51b924af484789f4f45614020d99 |
|||
timeCreated: 1595649311 |
|
|||
fileFormatVersion: 2 |
|||
guid: 62a9411e3050488c8642ba3d1b8bb595 |
|||
timeCreated: 1595927572 |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
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); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 0e40c1ae020d48bdac96e08bda834742 |
|||
timeCreated: 1595649319 |
1001
com.unity.uiwidgets/Runtime/ui2/painting/painting.cs
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
fileFormatVersion: 2 |
|||
guid: 11d086e8a07d458fbaaacf137fe9ff60 |
|||
timeCreated: 1595649319 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#include "flow/raster_cache_key.h"
|
|||
|
|||
namespace uiwidgets { |
|||
|
|||
//
|
|||
|
|||
} // namespace uiwidgets
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#pragma once |
|||
|
|||
namespace uiwidgets { |
|||
|
|||
class ImageDecoder { |
|||
}; |
|||
|
|||
} // namespace uiwidgets |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
#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 |
|
|||
#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
|
|
|||
#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 |
|
|||
// 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
|
|
|||
#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 |
|
|||
#include "platform_message_response.h"
|
|||
|
|||
namespace uiwidgets { |
|||
|
|||
PlatformMessageResponse::PlatformMessageResponse() = default; |
|||
|
|||
PlatformMessageResponse::~PlatformMessageResponse() = default; |
|||
|
|||
} // namespace uiwidgets
|
|
|||
#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 |
部分文件因为文件数量过多而无法显示
撰写
预览
正在加载...
取消
保存
Reference in new issue