1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef FLUTTER_DISPLAY_LIST_DL_OP_RECORDS_H_
6#define FLUTTER_DISPLAY_LIST_DL_OP_RECORDS_H_
7
8#include "flutter/display_list/display_list.h"
9#include "flutter/display_list/dl_blend_mode.h"
10#include "flutter/display_list/dl_op_receiver.h"
11#include "flutter/display_list/dl_sampling_options.h"
12#include "flutter/display_list/effects/dl_color_source.h"
13#include "flutter/fml/macros.h"
14
15#include "third_party/skia/include/core/SkRSXform.h"
16
17namespace flutter {
18
19// Structure holding the information necessary to dispatch and
20// potentially cull the DLOps during playback.
21//
22// Generally drawing ops will execute as long as |cur_index|
23// is at or after |next_render_index|, so setting the latter
24// to 0 will render all primitives and setting it to MAX_INT
25// will skip all remaining rendering primitives.
26//
27// Save and saveLayer ops will execute as long as the next
28// rendering index is before their closing restore index.
29// They will also store their own restore index into the
30// |next_restore_index| field for use by clip and transform ops.
31//
32// Clip and transform ops will only execute if the next
33// render index is before the next restore index. Otherwise
34// their modified state will not be used before it gets
35// restored.
36//
37// Attribute ops always execute as they are too numerous and
38// cheap to deal with a complicated "lifetime" tracking to
39// determine if they will be used.
40struct DispatchContext {
41 DlOpReceiver& receiver;
42
43 int cur_index;
44 int next_render_index;
45
46 int next_restore_index;
47
48 struct SaveInfo {
49 SaveInfo(int previous_restore_index, bool save_was_needed)
50 : previous_restore_index(previous_restore_index),
51 save_was_needed(save_was_needed) {}
52
53 int previous_restore_index;
54 bool save_was_needed;
55 };
56
57 std::vector<SaveInfo> save_infos;
58};
59
60// Most Ops can be bulk compared using memcmp because they contain
61// only numeric values or constructs that are constructed from numeric
62// values.
63//
64// Some contain sk_sp<> references which can also be bulk compared
65// to see if they are pointing to the same reference. (Note that
66// two sk_sp<> that refer to the same object are themselves ==.)
67//
68// Only a DLOp that wants to do a deep compare needs to override the
69// DLOp::equals() method and return a value of kEqual or kNotEqual.
70enum class DisplayListCompare {
71 // The Op is deferring comparisons to a bulk memcmp performed lazily
72 // across all bulk-comparable ops.
73 kUseBulkCompare,
74
75 // The Op provided a specific equals method that spotted a difference
76 kNotEqual,
77
78 // The Op provided a specific equals method that saw no differences
79 kEqual,
80};
81
82// "DLOpPackLabel" is just a label for the pack pragma so it can be popped
83// later.
84#pragma pack(push, DLOpPackLabel, 8)
85
86// Assuming a 64-bit platform (most of our platforms at this time?)
87// the following comments are a "worst case" assessment of how well
88// these structures pack into memory. They may be packed more tightly
89// on some of the 32-bit platforms that we see in older phones.
90//
91// Struct allocation in the DL memory is aligned to a void* boundary
92// which means that the minimum (aligned) struct size will be 8 bytes.
93// The DLOp base uses 4 bytes so each Op-specific struct gets 4 bytes
94// of data for "free" and works best when it packs well into an 8-byte
95// aligned size.
96struct DLOp {
97 DisplayListOpType type : 8;
98 uint32_t size : 24;
99
100 DisplayListCompare equals(const DLOp* other) const {
101 return DisplayListCompare::kUseBulkCompare;
102 }
103};
104
105// 4 byte header + 4 byte payload packs into minimum 8 bytes
106#define DEFINE_SET_BOOL_OP(name) \
107 struct Set##name##Op final : DLOp { \
108 static const auto kType = DisplayListOpType::kSet##name; \
109 \
110 explicit Set##name##Op(bool value) : value(value) {} \
111 \
112 const bool value; \
113 \
114 void dispatch(DispatchContext& ctx) const { \
115 ctx.receiver.set##name(value); \
116 } \
117 };
118DEFINE_SET_BOOL_OP(AntiAlias)
119DEFINE_SET_BOOL_OP(Dither)
120DEFINE_SET_BOOL_OP(InvertColors)
121#undef DEFINE_SET_BOOL_OP
122
123// 4 byte header + 4 byte payload packs into minimum 8 bytes
124#define DEFINE_SET_ENUM_OP(name) \
125 struct SetStroke##name##Op final : DLOp { \
126 static const auto kType = DisplayListOpType::kSetStroke##name; \
127 \
128 explicit SetStroke##name##Op(DlStroke##name value) : value(value) {} \
129 \
130 const DlStroke##name value; \
131 \
132 void dispatch(DispatchContext& ctx) const { \
133 ctx.receiver.setStroke##name(value); \
134 } \
135 };
136DEFINE_SET_ENUM_OP(Cap)
137DEFINE_SET_ENUM_OP(Join)
138#undef DEFINE_SET_ENUM_OP
139
140// 4 byte header + 4 byte payload packs into minimum 8 bytes
141struct SetStyleOp final : DLOp {
142 static const auto kType = DisplayListOpType::kSetStyle;
143
144 explicit SetStyleOp(DlDrawStyle style) : style(style) {}
145
146 const DlDrawStyle style;
147
148 void dispatch(DispatchContext& ctx) const {
149 ctx.receiver.setDrawStyle(style);
150 }
151};
152// 4 byte header + 4 byte payload packs into minimum 8 bytes
153struct SetStrokeWidthOp final : DLOp {
154 static const auto kType = DisplayListOpType::kSetStrokeWidth;
155
156 explicit SetStrokeWidthOp(float width) : width(width) {}
157
158 const float width;
159
160 void dispatch(DispatchContext& ctx) const {
161 ctx.receiver.setStrokeWidth(width);
162 }
163};
164// 4 byte header + 4 byte payload packs into minimum 8 bytes
165struct SetStrokeMiterOp final : DLOp {
166 static const auto kType = DisplayListOpType::kSetStrokeMiter;
167
168 explicit SetStrokeMiterOp(float limit) : limit(limit) {}
169
170 const float limit;
171
172 void dispatch(DispatchContext& ctx) const {
173 ctx.receiver.setStrokeMiter(limit);
174 }
175};
176
177// 4 byte header + 4 byte payload packs into minimum 8 bytes
178struct SetColorOp final : DLOp {
179 static const auto kType = DisplayListOpType::kSetColor;
180
181 explicit SetColorOp(DlColor color) : color(color) {}
182
183 const DlColor color;
184
185 void dispatch(DispatchContext& ctx) const { ctx.receiver.setColor(color); }
186};
187// 4 byte header + 4 byte payload packs into minimum 8 bytes
188struct SetBlendModeOp final : DLOp {
189 static const auto kType = DisplayListOpType::kSetBlendMode;
190
191 explicit SetBlendModeOp(DlBlendMode mode) : mode(mode) {}
192
193 const DlBlendMode mode;
194
195 void dispatch(DispatchContext& ctx) const { //
196 ctx.receiver.setBlendMode(mode);
197 }
198};
199
200// Clear: 4 byte header + unused 4 byte payload uses 8 bytes
201// (4 bytes unused)
202// Set: 4 byte header + unused 4 byte struct padding + Dl<name>
203// instance copied to the memory following the record
204// yields a size and efficiency that has somewhere between
205// 4 and 8 bytes unused
206#define DEFINE_SET_CLEAR_DLATTR_OP(name, sk_name, field) \
207 struct Clear##name##Op final : DLOp { \
208 static const auto kType = DisplayListOpType::kClear##name; \
209 \
210 Clear##name##Op() {} \
211 \
212 void dispatch(DispatchContext& ctx) const { \
213 ctx.receiver.set##name(nullptr); \
214 } \
215 }; \
216 struct SetPod##name##Op final : DLOp { \
217 static const auto kType = DisplayListOpType::kSetPod##name; \
218 \
219 SetPod##name##Op() {} \
220 \
221 void dispatch(DispatchContext& ctx) const { \
222 const Dl##name* filter = reinterpret_cast<const Dl##name*>(this + 1); \
223 ctx.receiver.set##name(filter); \
224 } \
225 };
226DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter)
227DEFINE_SET_CLEAR_DLATTR_OP(ImageFilter, ImageFilter, filter)
228DEFINE_SET_CLEAR_DLATTR_OP(MaskFilter, MaskFilter, filter)
229DEFINE_SET_CLEAR_DLATTR_OP(ColorSource, Shader, source)
230DEFINE_SET_CLEAR_DLATTR_OP(PathEffect, PathEffect, effect)
231#undef DEFINE_SET_CLEAR_DLATTR_OP
232
233// 4 byte header + 80 bytes for the embedded DlImageColorSource
234// uses 84 total bytes (4 bytes unused)
235struct SetImageColorSourceOp : DLOp {
236 static const auto kType = DisplayListOpType::kSetImageColorSource;
237
238 SetImageColorSourceOp(const DlImageColorSource* source)
239 : source(source->image(),
240 source->horizontal_tile_mode(),
241 source->vertical_tile_mode(),
242 source->sampling(),
243 source->matrix_ptr()) {}
244
245 const DlImageColorSource source;
246
247 void dispatch(DispatchContext& ctx) const {
248 ctx.receiver.setColorSource(&source);
249 }
250};
251
252// 56 bytes: 4 byte header, 4 byte padding, 8 for vtable, 8 * 2 for sk_sps, 24
253// for the std::vector.
254struct SetRuntimeEffectColorSourceOp : DLOp {
255 static const auto kType = DisplayListOpType::kSetRuntimeEffectColorSource;
256
257 SetRuntimeEffectColorSourceOp(const DlRuntimeEffectColorSource* source)
258 : source(source->runtime_effect(),
259 source->samplers(),
260 source->uniform_data()) {}
261
262 const DlRuntimeEffectColorSource source;
263
264 void dispatch(DispatchContext& ctx) const {
265 ctx.receiver.setColorSource(&source);
266 }
267
268 DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const {
269 return (source == other->source) ? DisplayListCompare::kEqual
270 : DisplayListCompare::kNotEqual;
271 }
272};
273
274#ifdef IMPELLER_ENABLE_3D
275struct SetSceneColorSourceOp : DLOp {
276 static const auto kType = DisplayListOpType::kSetSceneColorSource;
277
278 SetSceneColorSourceOp(const DlSceneColorSource* source)
279 : source(source->scene_node(), source->camera_matrix()) {}
280
281 const DlSceneColorSource source;
282
283 void dispatch(DispatchContext& ctx) const {
284 ctx.receiver.setColorSource(&source);
285 }
286
287 DisplayListCompare equals(const SetSceneColorSourceOp* other) const {
288 return (source == other->source) ? DisplayListCompare::kEqual
289 : DisplayListCompare::kNotEqual;
290 }
291};
292#endif // IMPELLER_ENABLE_3D
293
294// 4 byte header + 16 byte payload uses 24 total bytes (4 bytes unused)
295struct SetSharedImageFilterOp : DLOp {
296 static const auto kType = DisplayListOpType::kSetSharedImageFilter;
297
298 SetSharedImageFilterOp(const DlImageFilter* filter)
299 : filter(filter->shared()) {}
300
301 const std::shared_ptr<DlImageFilter> filter;
302
303 void dispatch(DispatchContext& ctx) const {
304 ctx.receiver.setImageFilter(filter.get());
305 }
306
307 DisplayListCompare equals(const SetSharedImageFilterOp* other) const {
308 return Equals(a: filter, b: other->filter) ? DisplayListCompare::kEqual
309 : DisplayListCompare::kNotEqual;
310 }
311};
312
313// The base object for all save() and saveLayer() ops
314// 4 byte header + 8 byte payload packs neatly into 16 bytes (4 bytes unused)
315struct SaveOpBase : DLOp {
316 SaveOpBase() : options(), restore_index(0) {}
317
318 SaveOpBase(const SaveLayerOptions options)
319 : options(options), restore_index(0) {}
320
321 // options parameter is only used by saveLayer operations, but since
322 // it packs neatly into the empty space created by laying out the 64-bit
323 // offsets, it can be stored for free and defaulted to 0 for save operations.
324 SaveLayerOptions options;
325 int restore_index;
326
327 inline bool save_needed(DispatchContext& ctx) const {
328 bool needed = ctx.next_render_index <= restore_index;
329 ctx.save_infos.emplace_back(args&: ctx.next_restore_index, args&: needed);
330 ctx.next_restore_index = restore_index;
331 return needed;
332 }
333};
334// 24 byte SaveOpBase with no additional data (options is unsed here)
335struct SaveOp final : SaveOpBase {
336 static const auto kType = DisplayListOpType::kSave;
337
338 SaveOp() : SaveOpBase() {}
339
340 void dispatch(DispatchContext& ctx) const {
341 if (save_needed(ctx)) {
342 ctx.receiver.save();
343 }
344 }
345};
346// 16 byte SaveOpBase with no additional data
347struct SaveLayerOp final : SaveOpBase {
348 static const auto kType = DisplayListOpType::kSaveLayer;
349
350 explicit SaveLayerOp(const SaveLayerOptions options) : SaveOpBase(options) {}
351
352 void dispatch(DispatchContext& ctx) const {
353 if (save_needed(ctx)) {
354 ctx.receiver.saveLayer(bounds: nullptr, options);
355 }
356 }
357};
358// 24 byte SaveOpBase + 16 byte payload packs evenly into 40 bytes
359struct SaveLayerBoundsOp final : SaveOpBase {
360 static const auto kType = DisplayListOpType::kSaveLayerBounds;
361
362 SaveLayerBoundsOp(const SaveLayerOptions options, const SkRect& rect)
363 : SaveOpBase(options), rect(rect) {}
364
365 const SkRect rect;
366
367 void dispatch(DispatchContext& ctx) const {
368 if (save_needed(ctx)) {
369 ctx.receiver.saveLayer(bounds: &rect, options);
370 }
371 }
372};
373// 24 byte SaveOpBase + 16 byte payload packs into minimum 40 bytes
374struct SaveLayerBackdropOp final : SaveOpBase {
375 static const auto kType = DisplayListOpType::kSaveLayerBackdrop;
376
377 explicit SaveLayerBackdropOp(const SaveLayerOptions options,
378 const DlImageFilter* backdrop)
379 : SaveOpBase(options), backdrop(backdrop->shared()) {}
380
381 const std::shared_ptr<DlImageFilter> backdrop;
382
383 void dispatch(DispatchContext& ctx) const {
384 if (save_needed(ctx)) {
385 ctx.receiver.saveLayer(bounds: nullptr, options, backdrop: backdrop.get());
386 }
387 }
388
389 DisplayListCompare equals(const SaveLayerBackdropOp* other) const {
390 return options == other->options && Equals(a: backdrop, b: other->backdrop)
391 ? DisplayListCompare::kEqual
392 : DisplayListCompare::kNotEqual;
393 }
394};
395// 24 byte SaveOpBase + 32 byte payload packs into minimum 56 bytes
396struct SaveLayerBackdropBoundsOp final : SaveOpBase {
397 static const auto kType = DisplayListOpType::kSaveLayerBackdropBounds;
398
399 SaveLayerBackdropBoundsOp(const SaveLayerOptions options,
400 const SkRect& rect,
401 const DlImageFilter* backdrop)
402 : SaveOpBase(options), rect(rect), backdrop(backdrop->shared()) {}
403
404 const SkRect rect;
405 const std::shared_ptr<DlImageFilter> backdrop;
406
407 void dispatch(DispatchContext& ctx) const {
408 if (save_needed(ctx)) {
409 ctx.receiver.saveLayer(bounds: &rect, options, backdrop: backdrop.get());
410 }
411 }
412
413 DisplayListCompare equals(const SaveLayerBackdropBoundsOp* other) const {
414 return (options == other->options && rect == other->rect &&
415 Equals(a: backdrop, b: other->backdrop))
416 ? DisplayListCompare::kEqual
417 : DisplayListCompare::kNotEqual;
418 }
419};
420// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
421struct RestoreOp final : DLOp {
422 static const auto kType = DisplayListOpType::kRestore;
423
424 RestoreOp() {}
425
426 void dispatch(DispatchContext& ctx) const {
427 DispatchContext::SaveInfo& info = ctx.save_infos.back();
428 if (info.save_was_needed) {
429 ctx.receiver.restore();
430 }
431 ctx.next_restore_index = info.previous_restore_index;
432 ctx.save_infos.pop_back();
433 }
434};
435
436struct TransformClipOpBase : DLOp {
437 inline bool op_needed(const DispatchContext& context) const {
438 return context.next_render_index <= context.next_restore_index;
439 }
440};
441// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
442// (4 bytes unused)
443struct TranslateOp final : TransformClipOpBase {
444 static const auto kType = DisplayListOpType::kTranslate;
445
446 TranslateOp(SkScalar tx, SkScalar ty) : tx(tx), ty(ty) {}
447
448 const SkScalar tx;
449 const SkScalar ty;
450
451 void dispatch(DispatchContext& ctx) const {
452 if (op_needed(context: ctx)) {
453 ctx.receiver.translate(tx, ty);
454 }
455 }
456};
457// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
458// (4 bytes unused)
459struct ScaleOp final : TransformClipOpBase {
460 static const auto kType = DisplayListOpType::kScale;
461
462 ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
463
464 const SkScalar sx;
465 const SkScalar sy;
466
467 void dispatch(DispatchContext& ctx) const {
468 if (op_needed(context: ctx)) {
469 ctx.receiver.scale(sx, sy);
470 }
471 }
472};
473// 4 byte header + 4 byte payload packs into minimum 8 bytes
474struct RotateOp final : TransformClipOpBase {
475 static const auto kType = DisplayListOpType::kRotate;
476
477 explicit RotateOp(SkScalar degrees) : degrees(degrees) {}
478
479 const SkScalar degrees;
480
481 void dispatch(DispatchContext& ctx) const {
482 if (op_needed(context: ctx)) {
483 ctx.receiver.rotate(degrees);
484 }
485 }
486};
487// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
488// (4 bytes unused)
489struct SkewOp final : TransformClipOpBase {
490 static const auto kType = DisplayListOpType::kSkew;
491
492 SkewOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
493
494 const SkScalar sx;
495 const SkScalar sy;
496
497 void dispatch(DispatchContext& ctx) const {
498 if (op_needed(context: ctx)) {
499 ctx.receiver.skew(sx, sy);
500 }
501 }
502};
503// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes
504// (4 bytes unused)
505struct Transform2DAffineOp final : TransformClipOpBase {
506 static const auto kType = DisplayListOpType::kTransform2DAffine;
507
508 // clang-format off
509 Transform2DAffineOp(SkScalar mxx, SkScalar mxy, SkScalar mxt,
510 SkScalar myx, SkScalar myy, SkScalar myt)
511 : mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {}
512 // clang-format on
513
514 const SkScalar mxx, mxy, mxt;
515 const SkScalar myx, myy, myt;
516
517 void dispatch(DispatchContext& ctx) const {
518 if (op_needed(context: ctx)) {
519 ctx.receiver.transform2DAffine(mxx, mxy, mxt, //
520 myx, myy, myt);
521 }
522 }
523};
524// 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes
525// (4 bytes unused)
526struct TransformFullPerspectiveOp final : TransformClipOpBase {
527 static const auto kType = DisplayListOpType::kTransformFullPerspective;
528
529 // clang-format off
530 TransformFullPerspectiveOp(
531 SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
532 SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
533 SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
534 SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt)
535 : mxx(mxx), mxy(mxy), mxz(mxz), mxt(mxt),
536 myx(myx), myy(myy), myz(myz), myt(myt),
537 mzx(mzx), mzy(mzy), mzz(mzz), mzt(mzt),
538 mwx(mwx), mwy(mwy), mwz(mwz), mwt(mwt) {}
539 // clang-format on
540
541 const SkScalar mxx, mxy, mxz, mxt;
542 const SkScalar myx, myy, myz, myt;
543 const SkScalar mzx, mzy, mzz, mzt;
544 const SkScalar mwx, mwy, mwz, mwt;
545
546 void dispatch(DispatchContext& ctx) const {
547 if (op_needed(context: ctx)) {
548 ctx.receiver.transformFullPerspective(mxx, mxy, mxz, mxt, //
549 myx, myy, myz, myt, //
550 mzx, mzy, mzz, mzt, //
551 mwx, mwy, mwz, mwt);
552 }
553 }
554};
555
556// 4 byte header with no payload.
557struct TransformResetOp final : TransformClipOpBase {
558 static const auto kType = DisplayListOpType::kTransformReset;
559
560 TransformResetOp() = default;
561
562 void dispatch(DispatchContext& ctx) const {
563 if (op_needed(context: ctx)) {
564 ctx.receiver.transformReset();
565 }
566 }
567};
568
569// 4 byte header + 4 byte common payload packs into minimum 8 bytes
570// SkRect is 16 more bytes, which packs efficiently into 24 bytes total
571// SkRRect is 52 more bytes, which rounds up to 56 bytes (4 bytes unused)
572// which packs into 64 bytes total
573// SkPath is 16 more bytes, which packs efficiently into 24 bytes total
574//
575// We could pack the clip_op and the bool both into the free 4 bytes after
576// the header, but the Windows compiler keeps wanting to expand that
577// packing into more bytes than needed (even when they are declared as
578// packed bit fields!)
579#define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \
580 struct Clip##clipop##shapetype##Op final : TransformClipOpBase { \
581 static const auto kType = DisplayListOpType::kClip##clipop##shapetype; \
582 \
583 Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \
584 : is_aa(is_aa), shape(shape) {} \
585 \
586 const bool is_aa; \
587 const Sk##shapetype shape; \
588 \
589 void dispatch(DispatchContext& ctx) const { \
590 if (op_needed(ctx)) { \
591 ctx.receiver.clip##shapetype(shape, DlCanvas::ClipOp::k##clipop, \
592 is_aa); \
593 } \
594 } \
595 };
596DEFINE_CLIP_SHAPE_OP(Rect, Intersect)
597DEFINE_CLIP_SHAPE_OP(RRect, Intersect)
598DEFINE_CLIP_SHAPE_OP(Rect, Difference)
599DEFINE_CLIP_SHAPE_OP(RRect, Difference)
600#undef DEFINE_CLIP_SHAPE_OP
601
602#define DEFINE_CLIP_PATH_OP(clipop) \
603 struct Clip##clipop##PathOp final : TransformClipOpBase { \
604 static const auto kType = DisplayListOpType::kClip##clipop##Path; \
605 \
606 Clip##clipop##PathOp(SkPath path, bool is_aa) \
607 : is_aa(is_aa), path(path) {} \
608 \
609 const bool is_aa; \
610 const SkPath path; \
611 \
612 void dispatch(DispatchContext& ctx) const { \
613 if (op_needed(ctx)) { \
614 ctx.receiver.clipPath(path, DlCanvas::ClipOp::k##clipop, is_aa); \
615 } \
616 } \
617 \
618 DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \
619 return is_aa == other->is_aa && path == other->path \
620 ? DisplayListCompare::kEqual \
621 : DisplayListCompare::kNotEqual; \
622 } \
623 };
624DEFINE_CLIP_PATH_OP(Intersect)
625DEFINE_CLIP_PATH_OP(Difference)
626#undef DEFINE_CLIP_PATH_OP
627
628struct DrawOpBase : DLOp {
629 inline bool op_needed(const DispatchContext& ctx) const {
630 return ctx.cur_index >= ctx.next_render_index;
631 }
632};
633
634// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
635struct DrawPaintOp final : DrawOpBase {
636 static const auto kType = DisplayListOpType::kDrawPaint;
637
638 DrawPaintOp() {}
639
640 void dispatch(DispatchContext& ctx) const {
641 if (op_needed(ctx)) {
642 ctx.receiver.drawPaint();
643 }
644 }
645};
646// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
647// (4 bytes unused)
648struct DrawColorOp final : DrawOpBase {
649 static const auto kType = DisplayListOpType::kDrawColor;
650
651 DrawColorOp(DlColor color, DlBlendMode mode) : color(color), mode(mode) {}
652
653 const DlColor color;
654 const DlBlendMode mode;
655
656 void dispatch(DispatchContext& ctx) const {
657 if (op_needed(ctx)) {
658 ctx.receiver.drawColor(color, mode);
659 }
660 }
661};
662
663// The common data is a 4 byte header with an unused 4 bytes
664// SkRect is 16 more bytes, using 20 bytes which rounds up to 24 bytes total
665// (4 bytes unused)
666// SkOval is same as SkRect
667// SkRRect is 52 more bytes, which packs efficiently into 56 bytes total
668#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \
669 struct Draw##op_name##Op final : DrawOpBase { \
670 static const auto kType = DisplayListOpType::kDraw##op_name; \
671 \
672 explicit Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \
673 \
674 const arg_type arg_name; \
675 \
676 void dispatch(DispatchContext& ctx) const { \
677 if (op_needed(ctx)) { \
678 ctx.receiver.draw##op_name(arg_name); \
679 } \
680 } \
681 };
682DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect)
683DEFINE_DRAW_1ARG_OP(Oval, SkRect, oval)
684DEFINE_DRAW_1ARG_OP(RRect, SkRRect, rrect)
685#undef DEFINE_DRAW_1ARG_OP
686
687// 4 byte header + 16 byte payload uses 20 bytes but is rounded up to 24 bytes
688// (4 bytes unused)
689struct DrawPathOp final : DrawOpBase {
690 static const auto kType = DisplayListOpType::kDrawPath;
691
692 explicit DrawPathOp(SkPath path) : path(path) {}
693
694 const SkPath path;
695
696 void dispatch(DispatchContext& ctx) const {
697 if (op_needed(ctx)) {
698 ctx.receiver.drawPath(path);
699 }
700 }
701
702 DisplayListCompare equals(const DrawPathOp* other) const {
703 return path == other->path ? DisplayListCompare::kEqual
704 : DisplayListCompare::kNotEqual;
705 }
706};
707
708// The common data is a 4 byte header with an unused 4 bytes
709// 2 x SkPoint is 16 more bytes, using 20 bytes rounding up to 24 bytes total
710// (4 bytes unused)
711// SkPoint + SkScalar is 12 more bytes, packing efficiently into 16 bytes total
712// 2 x SkRRect is 104 more bytes, using 108 and rounding up to 112 bytes total
713// (4 bytes unused)
714#define DEFINE_DRAW_2ARG_OP(op_name, type1, name1, type2, name2) \
715 struct Draw##op_name##Op final : DrawOpBase { \
716 static const auto kType = DisplayListOpType::kDraw##op_name; \
717 \
718 Draw##op_name##Op(type1 name1, type2 name2) \
719 : name1(name1), name2(name2) {} \
720 \
721 const type1 name1; \
722 const type2 name2; \
723 \
724 void dispatch(DispatchContext& ctx) const { \
725 if (op_needed(ctx)) { \
726 ctx.receiver.draw##op_name(name1, name2); \
727 } \
728 } \
729 };
730DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1)
731DEFINE_DRAW_2ARG_OP(Circle, SkPoint, center, SkScalar, radius)
732DEFINE_DRAW_2ARG_OP(DRRect, SkRRect, outer, SkRRect, inner)
733#undef DEFINE_DRAW_2ARG_OP
734
735// 4 byte header + 28 byte payload packs efficiently into 32 bytes
736struct DrawArcOp final : DrawOpBase {
737 static const auto kType = DisplayListOpType::kDrawArc;
738
739 DrawArcOp(SkRect bounds, SkScalar start, SkScalar sweep, bool center)
740 : bounds(bounds), start(start), sweep(sweep), center(center) {}
741
742 const SkRect bounds;
743 const SkScalar start;
744 const SkScalar sweep;
745 const bool center;
746
747 void dispatch(DispatchContext& ctx) const {
748 if (op_needed(ctx)) {
749 ctx.receiver.drawArc(oval_bounds: bounds, start_degrees: start, sweep_degrees: sweep, use_center: center);
750 }
751 }
752};
753
754// 4 byte header + 4 byte fixed payload packs efficiently into 8 bytes
755// But then there is a list of points following the structure which
756// is guaranteed to be a multiple of 8 bytes (SkPoint is 8 bytes)
757// so this op will always pack efficiently
758// The point type is packed into 3 different OpTypes to avoid expanding
759// the fixed payload beyond the 8 bytes
760#define DEFINE_DRAW_POINTS_OP(name, mode) \
761 struct Draw##name##Op final : DrawOpBase { \
762 static const auto kType = DisplayListOpType::kDraw##name; \
763 \
764 explicit Draw##name##Op(uint32_t count) : count(count) {} \
765 \
766 const uint32_t count; \
767 \
768 void dispatch(DispatchContext& ctx) const { \
769 if (op_needed(ctx)) { \
770 const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1); \
771 ctx.receiver.drawPoints(DlCanvas::PointMode::mode, count, pts); \
772 } \
773 } \
774 };
775DEFINE_DRAW_POINTS_OP(Points, kPoints);
776DEFINE_DRAW_POINTS_OP(Lines, kLines);
777DEFINE_DRAW_POINTS_OP(Polygon, kPolygon);
778#undef DEFINE_DRAW_POINTS_OP
779
780// 4 byte header + 4 byte payload packs efficiently into 8 bytes
781// The DlVertices object will be pod-allocated after this structure
782// and can take any number of bytes so the final efficiency will
783// depend on the size of the DlVertices.
784// Note that the DlVertices object ends with an array of 16-bit
785// indices so the alignment can be up to 6 bytes off leading to
786// up to 6 bytes of overhead
787struct DrawVerticesOp final : DrawOpBase {
788 static const auto kType = DisplayListOpType::kDrawVertices;
789
790 DrawVerticesOp(DlBlendMode mode) : mode(mode) {}
791
792 const DlBlendMode mode;
793
794 void dispatch(DispatchContext& ctx) const {
795 if (op_needed(ctx)) {
796 const DlVertices* vertices =
797 reinterpret_cast<const DlVertices*>(this + 1);
798 ctx.receiver.drawVertices(vertices, mode);
799 }
800 }
801};
802
803// 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes
804// (4 bytes unused)
805#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \
806 struct name##Op final : DrawOpBase { \
807 static const auto kType = DisplayListOpType::k##name; \
808 \
809 name##Op(const sk_sp<DlImage> image, \
810 const SkPoint& point, \
811 DlImageSampling sampling) \
812 : point(point), sampling(sampling), image(std::move(image)) {} \
813 \
814 const SkPoint point; \
815 const DlImageSampling sampling; \
816 const sk_sp<DlImage> image; \
817 \
818 void dispatch(DispatchContext& ctx) const { \
819 if (op_needed(ctx)) { \
820 ctx.receiver.drawImage(image, point, sampling, with_attributes); \
821 } \
822 } \
823 \
824 DisplayListCompare equals(const name##Op* other) const { \
825 return (point == other->point && sampling == other->sampling && \
826 image->Equals(other->image)) \
827 ? DisplayListCompare::kEqual \
828 : DisplayListCompare::kNotEqual; \
829 } \
830 };
831DEFINE_DRAW_IMAGE_OP(DrawImage, false)
832DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true)
833#undef DEFINE_DRAW_IMAGE_OP
834
835// 4 byte header + 72 byte payload uses 76 bytes but is rounded up to 80 bytes
836// (4 bytes unused)
837struct DrawImageRectOp final : DrawOpBase {
838 static const auto kType = DisplayListOpType::kDrawImageRect;
839
840 DrawImageRectOp(const sk_sp<DlImage> image,
841 const SkRect& src,
842 const SkRect& dst,
843 DlImageSampling sampling,
844 bool render_with_attributes,
845 DlCanvas::SrcRectConstraint constraint)
846 : src(src),
847 dst(dst),
848 sampling(sampling),
849 render_with_attributes(render_with_attributes),
850 constraint(constraint),
851 image(std::move(image)) {}
852
853 const SkRect src;
854 const SkRect dst;
855 const DlImageSampling sampling;
856 const bool render_with_attributes;
857 const DlCanvas::SrcRectConstraint constraint;
858 const sk_sp<DlImage> image;
859
860 void dispatch(DispatchContext& ctx) const {
861 if (op_needed(ctx)) {
862 ctx.receiver.drawImageRect(image, src, dst, sampling,
863 render_with_attributes, constraint);
864 }
865 }
866
867 DisplayListCompare equals(const DrawImageRectOp* other) const {
868 return (src == other->src && dst == other->dst &&
869 sampling == other->sampling &&
870 render_with_attributes == other->render_with_attributes &&
871 constraint == other->constraint && image->Equals(other: other->image))
872 ? DisplayListCompare::kEqual
873 : DisplayListCompare::kNotEqual;
874 }
875};
876
877// 4 byte header + 44 byte payload packs efficiently into 48 bytes
878#define DEFINE_DRAW_IMAGE_NINE_OP(name, render_with_attributes) \
879 struct name##Op final : DrawOpBase { \
880 static const auto kType = DisplayListOpType::k##name; \
881 \
882 name##Op(const sk_sp<DlImage> image, \
883 const SkIRect& center, \
884 const SkRect& dst, \
885 DlFilterMode mode) \
886 : center(center), dst(dst), mode(mode), image(std::move(image)) {} \
887 \
888 const SkIRect center; \
889 const SkRect dst; \
890 const DlFilterMode mode; \
891 const sk_sp<DlImage> image; \
892 \
893 void dispatch(DispatchContext& ctx) const { \
894 if (op_needed(ctx)) { \
895 ctx.receiver.drawImageNine(image, center, dst, mode, \
896 render_with_attributes); \
897 } \
898 } \
899 \
900 DisplayListCompare equals(const name##Op* other) const { \
901 return (center == other->center && dst == other->dst && \
902 mode == other->mode && image->Equals(other->image)) \
903 ? DisplayListCompare::kEqual \
904 : DisplayListCompare::kNotEqual; \
905 } \
906 };
907DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNine, false)
908DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNineWithAttr, true)
909#undef DEFINE_DRAW_IMAGE_NINE_OP
910
911// 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes
912// (4 bytes unused)
913// Each of these is then followed by a number of lists.
914// SkRSXform list is a multiple of 16 bytes so it is always packed well
915// SkRect list is also a multiple of 16 bytes so it also packs well
916// DlColor list only packs well if the count is even, otherwise there
917// can be 4 unusued bytes at the end.
918struct DrawAtlasBaseOp : DrawOpBase {
919 DrawAtlasBaseOp(const sk_sp<DlImage> atlas,
920 int count,
921 DlBlendMode mode,
922 DlImageSampling sampling,
923 bool has_colors,
924 bool render_with_attributes)
925 : count(count),
926 mode_index(static_cast<uint16_t>(mode)),
927 has_colors(has_colors),
928 render_with_attributes(render_with_attributes),
929 sampling(sampling),
930 atlas(std::move(atlas)) {}
931
932 const int count;
933 const uint16_t mode_index;
934 const uint8_t has_colors;
935 const uint8_t render_with_attributes;
936 const DlImageSampling sampling;
937 const sk_sp<DlImage> atlas;
938
939 bool equals(const DrawAtlasBaseOp* other,
940 const void* pod_this,
941 const void* pod_other) const {
942 bool ret = (count == other->count && mode_index == other->mode_index &&
943 has_colors == other->has_colors &&
944 render_with_attributes == other->render_with_attributes &&
945 sampling == other->sampling && atlas->Equals(other: other->atlas));
946 if (ret) {
947 size_t bytes = count * (sizeof(SkRSXform) + sizeof(SkRect));
948 if (has_colors) {
949 bytes += count * sizeof(DlColor);
950 }
951 ret = (memcmp(s1: pod_this, s2: pod_other, n: bytes) == 0);
952 }
953 return ret;
954 }
955};
956
957// Packs into 48 bytes as per DrawAtlasBaseOp
958// with array data following the struct also as per DrawAtlasBaseOp
959struct DrawAtlasOp final : DrawAtlasBaseOp {
960 static const auto kType = DisplayListOpType::kDrawAtlas;
961
962 DrawAtlasOp(const sk_sp<DlImage> atlas,
963 int count,
964 DlBlendMode mode,
965 DlImageSampling sampling,
966 bool has_colors,
967 bool render_with_attributes)
968 : DrawAtlasBaseOp(atlas,
969 count,
970 mode,
971 sampling,
972 has_colors,
973 render_with_attributes) {}
974
975 void dispatch(DispatchContext& ctx) const {
976 if (op_needed(ctx)) {
977 const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
978 const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
979 const DlColor* colors =
980 has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
981 const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
982 ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
983 cull_rect: nullptr, render_with_attributes);
984 }
985 }
986
987 DisplayListCompare equals(const DrawAtlasOp* other) const {
988 const void* pod_this = reinterpret_cast<const void*>(this + 1);
989 const void* pod_other = reinterpret_cast<const void*>(other + 1);
990 return (DrawAtlasBaseOp::equals(other, pod_this, pod_other))
991 ? DisplayListCompare::kEqual
992 : DisplayListCompare::kNotEqual;
993 }
994};
995
996// Packs into 48 bytes as per DrawAtlasBaseOp plus
997// an additional 16 bytes for the cull rect resulting in a total
998// of 56 bytes for the Culled drawAtlas.
999// Also with array data following the struct as per DrawAtlasBaseOp
1000struct DrawAtlasCulledOp final : DrawAtlasBaseOp {
1001 static const auto kType = DisplayListOpType::kDrawAtlasCulled;
1002
1003 DrawAtlasCulledOp(const sk_sp<DlImage> atlas,
1004 int count,
1005 DlBlendMode mode,
1006 DlImageSampling sampling,
1007 bool has_colors,
1008 const SkRect& cull_rect,
1009 bool render_with_attributes)
1010 : DrawAtlasBaseOp(atlas,
1011 count,
1012 mode,
1013 sampling,
1014 has_colors,
1015 render_with_attributes),
1016 cull_rect(cull_rect) {}
1017
1018 const SkRect cull_rect;
1019
1020 void dispatch(DispatchContext& ctx) const {
1021 if (op_needed(ctx)) {
1022 const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
1023 const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
1024 const DlColor* colors =
1025 has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
1026 const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
1027 ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
1028 cull_rect: &cull_rect, render_with_attributes);
1029 }
1030 }
1031
1032 DisplayListCompare equals(const DrawAtlasCulledOp* other) const {
1033 const void* pod_this = reinterpret_cast<const void*>(this + 1);
1034 const void* pod_other = reinterpret_cast<const void*>(other + 1);
1035 return (cull_rect == other->cull_rect &&
1036 DrawAtlasBaseOp::equals(other, pod_this, pod_other))
1037 ? DisplayListCompare::kEqual
1038 : DisplayListCompare::kNotEqual;
1039 }
1040};
1041
1042// 4 byte header + ptr aligned payload uses 12 bytes round up to 16
1043// (4 bytes unused)
1044struct DrawDisplayListOp final : DrawOpBase {
1045 static const auto kType = DisplayListOpType::kDrawDisplayList;
1046
1047 explicit DrawDisplayListOp(const sk_sp<DisplayList> display_list,
1048 SkScalar opacity)
1049 : opacity(opacity), display_list(std::move(display_list)) {}
1050
1051 SkScalar opacity;
1052 const sk_sp<DisplayList> display_list;
1053
1054 void dispatch(DispatchContext& ctx) const {
1055 if (op_needed(ctx)) {
1056 ctx.receiver.drawDisplayList(display_list, opacity);
1057 }
1058 }
1059
1060 DisplayListCompare equals(const DrawDisplayListOp* other) const {
1061 return (opacity == other->opacity &&
1062 display_list->Equals(other: other->display_list))
1063 ? DisplayListCompare::kEqual
1064 : DisplayListCompare::kNotEqual;
1065 }
1066};
1067
1068// 4 byte header + 8 payload bytes + an aligned pointer take 24 bytes
1069// (4 unused to align the pointer)
1070struct DrawTextBlobOp final : DrawOpBase {
1071 static const auto kType = DisplayListOpType::kDrawTextBlob;
1072
1073 DrawTextBlobOp(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y)
1074 : x(x), y(y), blob(std::move(blob)) {}
1075
1076 const SkScalar x;
1077 const SkScalar y;
1078 const sk_sp<SkTextBlob> blob;
1079
1080 void dispatch(DispatchContext& ctx) const {
1081 if (op_needed(ctx)) {
1082 ctx.receiver.drawTextBlob(blob, x, y);
1083 }
1084 }
1085};
1086
1087// 4 byte header + 28 byte payload packs evenly into 32 bytes
1088#define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder) \
1089 struct Draw##name##Op final : DrawOpBase { \
1090 static const auto kType = DisplayListOpType::kDraw##name; \
1091 \
1092 Draw##name##Op(const SkPath& path, \
1093 DlColor color, \
1094 SkScalar elevation, \
1095 SkScalar dpr) \
1096 : color(color), elevation(elevation), dpr(dpr), path(path) {} \
1097 \
1098 const DlColor color; \
1099 const SkScalar elevation; \
1100 const SkScalar dpr; \
1101 const SkPath path; \
1102 \
1103 void dispatch(DispatchContext& ctx) const { \
1104 if (op_needed(ctx)) { \
1105 ctx.receiver.drawShadow(path, color, elevation, transparent_occluder, \
1106 dpr); \
1107 } \
1108 } \
1109 };
1110DEFINE_DRAW_SHADOW_OP(Shadow, false)
1111DEFINE_DRAW_SHADOW_OP(ShadowTransparentOccluder, true)
1112#undef DEFINE_DRAW_SHADOW_OP
1113
1114#pragma pack(pop, DLOpPackLabel)
1115
1116} // namespace flutter
1117
1118#endif // FLUTTER_DISPLAY_LIST_DL_OP_RECORDS_H_
1119

source code of flutter_engine/flutter/display_list/dl_op_records.h