Flutter Rendering Pipeline - Phân tích chuyên sâu
Bài viết này đi sâu vào từng giai đoạn của Rendering Pipeline trong Flutter - quy trình chuyển đổi widgets thành pixels hiển thị trên màn hình.
1. Tổng quan Pipeline
Flutter rendering pipeline bao gồm 7 giai đoạn chính, thực hiện tuần tự trong mỗi frame:
┌─────────────────────────────────────────────────────────────────────────────────┐
│ FLUTTER RENDERING PIPELINE │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────┐ ┌────────┐ ┌───────┐ │
│ │ User │ │Animation │ │ Build │ │ Layout │ │ Paint │ │
│ │ Input │──▶│ Tick │──▶│ Phase │──▶│ Phase │──▶│ Phase │──▶ │
│ └──────────┘ └───────────┘ └───────┘ └────────┘ └───────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ ──▶ │ Compositing │──▶│ Rasterize │──▶ GPU ──▶ Display │
│ │ Phase │ │ Phase │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ◀────────────────── Dart Framework ──────────────────▶│◀── Engine/GPU ──▶ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘Thời gian mục tiêu:
- 60 FPS: 16.67ms per frame
- 120 FPS: 8.33ms per frame
Nếu pipeline mất quá lâu → frame drop → jank.
2. User Input Phase
2.1 Luồng xử lý
Khi người dùng tương tác (chạm, vuốt, gõ), hệ điều hành gửi raw events đến Flutter Engine thông qua Platform Embedder.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ USER INPUT FLOW │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ Hardware │ │
│ │ (Touchscreen) │ │
│ └───────┬───────┘ │
│ │ Raw touch data (x, y, pressure, timestamp) │
│ ▼ │
│ ┌───────────────┐ │
│ │ OS Layer │ │
│ │ (Android/iOS) │ │
│ └───────┬───────┘ │
│ │ MotionEvent / UITouch │
│ ▼ │
│ ┌───────────────┐ │
│ │ Platform │ │
│ │ Embedder │ │
│ └───────┬───────┘ │
│ │ flutter::PointerDataPacket │
│ ▼ │
│ ┌───────────────┐ │
│ │ Flutter Engine│ │
│ │ (dart:ui) │ │
│ └───────┬───────┘ │
│ │ PointerEvent (Dart object) │
│ ▼ │
│ ┌───────────────┐ │
│ │ Gesture │ │
│ │ Recognition │ ← GestureDetector, Listener, etc. │
│ └───────┬───────┘ │
│ │ onTap, onPan, onDrag, etc. │
│ ▼ │
│ ┌───────────────┐ │
│ │ setState() │ ← Trigger rebuild │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘2.2 Hit Testing
Flutter thực hiện hit testing để xác định widget nào nhận event:
┌─────────────────────────────────────────────────────────────────────────────────┐
│ HIT TESTING │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Tap at (150, 200) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ RenderView │ ← Bắt đầu từ root │
│ │ hitTest(result, position) │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ▼ (150, 200) có trong bounds? │
│ ┌─────────────────────────────────┐ │
│ │ RenderPositionedBox │ │
│ │ hitTestChildren → hitTestSelf │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ▼ Tiếp tục đệ quy │
│ ┌─────────────────────────────────┐ │
│ │ RenderFlex │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ RenderDecoratedBox (Button) │ ← HIT! Thêm vào HitTestResult │
│ └─────────────────────────────────┘ │
│ │
│ Kết quả: HitTestResult chứa danh sách RenderObjects │
│ theo thứ tự từ innermost → outermost │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘3. Animation Phase
3.1 Vsync và SchedulerBinding
Animations được driven bởi vsync signal từ display:
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ANIMATION TICK │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Display Hardware │
│ │ │
│ │ VSync signal (every 16.67ms @ 60Hz) │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ SchedulerBinding │ │
│ │ handleBeginFrame(timestamp) │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Transient Callbacks │ ← Ticker.tick() │
│ │ (Animation frame callbacks) │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ Cho mỗi AnimationController đang active: │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Calculate new animation value │ │
│ │ based on: │ │
│ │ • elapsed time │ │
│ │ • duration │ │
│ │ • curve (easing function) │ │
│ └───────────────┬─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Notify listeners │ │
│ │ → AnimatedBuilder.build() │ │
│ │ → setState() in some cases │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘3.2 Curves và Interpolation
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ANIMATION VALUE CALCULATION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ t = elapsedTime / totalDuration (0.0 → 1.0) │
│ │
│ Linear: value = t │
│ │
│ EaseIn: value = t² │
│ │
│ EaseOut: value = 1 - (1-t)² │
│ │
│ EaseInOut: value = t < 0.5 ? 2t² : 1 - (-2t+2)²/2 │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1.0 ─────────────────────────────────★ │ │
│ │ │ ★ │ ← EaseOut │
│ │ │ ★ │ │
│ │ │ ★ │ │
│ │ 0.5 │ ★ │ │
│ │ │ ★ │ │
│ │ │ ★ │ │
│ │ │ ★ │ │
│ │ 0.0 ★───────────────────────────────── │ │
│ │ 0.0 1.0 │ │
│ │ t │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Tween interpolation: │
│ finalValue = begin + (end - begin) * curvedValue │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘4. Build Phase - Chi tiết
4.1 Khi nào Build được trigger?
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BUILD TRIGGERS │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ StatefulWidget: │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. setState() called │ │
│ │ └── Element được đánh dấu dirty │ │
│ │ └── Scheduled trong next frame │ │
│ │ │ │
│ │ 2. didUpdateWidget() - parent rebuild với config mới │ │
│ │ │ │
│ │ 3. didChangeDependencies() - InheritedWidget thay đổi │ │
│ │ │ │
│ │ 4. Widget được reinsert sau khi deactivate │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ StatelessWidget: │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Lần đầu insert vào tree │ │
│ │ │ │
│ │ 2. Parent rebuild và tạo widget mới với config khác │ │
│ │ (canUpdate() returns false → tạo Element mới) │ │
│ │ │ │
│ │ 3. InheritedWidget dependency thay đổi │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘4.2 Dirty Elements và BuildOwner
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BUILD OWNER MECHANISM │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Khi setState() được gọi: │
│ │
│ 1. Element.markNeedsBuild() │
│ │ │
│ ▼ │
│ 2. BuildOwner.scheduleBuildFor(element) │
│ │ │
│ │ ┌─────────────────────────────────────────┐ │
│ │ │ _dirtyElements List │ │
│ │ ├─────────────────────────────────────────┤ │
│ │ │ [Element A, Element C, Element G, ...] │ │
│ │ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. SchedulerBinding.ensureVisualUpdate() │
│ └── Request new frame nếu chưa scheduled │
│ │
│ Trong frame tiếp theo: │
│ │
│ 4. BuildOwner.buildScope() │
│ │ │
│ │ Sort _dirtyElements theo DEPTH (shallow → deep) │
│ │ │
│ │ Tại sao sort theo depth? │
│ │ → Parent build trước có thể thay đổi children │
│ │ → Tránh build child 2 lần │
│ │ │
│ ▼ │
│ 5. Lặp qua từng dirty element: │
│ │ │
│ │ for element in _dirtyElements: │
│ │ if element.dirty: │
│ │ element.rebuild() │
│ │ │
│ ▼ │
│ 6. Clear _dirtyElements │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘4.3 Widget → Element Reconciliation
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ELEMENT RECONCILIATION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Khi element.rebuild() được gọi: │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Gọi widget.build(context) → newWidget │ │
│ │ │ │
│ │ 2. So sánh oldWidget vs newWidget │ │
│ │ │ │
│ │ Widget.canUpdate(oldWidget, newWidget): │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ return oldWidget.runtimeType == newWidget.runtimeType │ │ │
│ │ │ && oldWidget.key == newWidget.key; │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 3. Nếu canUpdate == true: │ │
│ │ └── Reuse existing Element │ │
│ │ └── element.update(newWidget) │ │
│ │ └── Đệ quy xuống children │ │
│ │ │ │
│ │ 4. Nếu canUpdate == false: │ │
│ │ └── Deactivate old Element │ │
│ │ └── Create new Element từ newWidget │ │
│ │ └── Mount new Element │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Ví dụ: │
│ │
│ Old: Container(color: red) New: Container(color: blue) │
│ │ │ │
│ └── Same type, same key (null) │ │
│ └── canUpdate = TRUE ──────────┘ │
│ └── Element REUSED, chỉ update color property │
│ │
│ Old: Container(...) New: Text('...') │
│ │ │ │
│ └── Different type ────────────┘ │
│ └── canUpdate = FALSE │
│ └── Old Element DESTROYED, new Element CREATED │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘4.4 Widget Explosion
Một điều quan trọng: code bạn viết không phải widget tree thật!
┌─────────────────────────────────────────────────────────────────────────────────┐
│ WIDGET EXPANSION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Code bạn viết: │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Container( │ │
│ │ color: Colors.blue, │ │
│ │ child: Text('Hello'), │ │
│ │ ) │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Widget tree thực tế sau khi build(): │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ Container │ │
│ │ └── Align (nếu có alignment) │ │
│ │ └── Padding (nếu có padding) │ │
│ │ └── DecoratedBox (nếu có decoration) │ │
│ │ └── ColoredBox (color != null) │ │
│ │ └── ConstrainedBox (nếu có constraints) │ │
│ │ └── Text │ │
│ │ └── RichText │ │
│ │ └── ... (more) │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ → Dev Tools sẽ hiển thị tree sâu hơn code bạn viết │
│ → Đây là normal behavior, không phải bug │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘5. Layout Phase - Chi tiết
5.1 Constraints Protocol
┌─────────────────────────────────────────────────────────────────────────────────┐
│ LAYOUT PROTOCOL │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ "Constraints go down. Sizes go up. Parent sets position." │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ PARENT │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 1. Gọi child.layout(constraints, parentUsesSize: true) │ │ │
│ │ │ Constraints: minW=0, maxW=300, minH=0, maxH=∞ │ │ │
│ │ └────────────────────────────┬────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ constraints đi XUỐNG │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ CHILD │ │ │
│ │ │ 2. Nhận constraints │ │ │
│ │ │ 3. Tính toán size của mình (phải trong constraints) │ │ │
│ │ │ 4. Layout children của mình (đệ quy) │ │ │
│ │ │ 5. Set size = Size(200, 100) │ │ │
│ │ └────────────────────────────┬────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ size đi LÊN │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 6. Parent nhận size của child │ │ │
│ │ │ 7. Parent set position cho child (offset) │ │ │
│ │ │ child.parentData.offset = Offset(50, 50) │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘5.2 BoxConstraints Chi tiết
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BOX CONSTRAINTS │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ BoxConstraints { │
│ double minWidth; │
│ double maxWidth; │
│ double minHeight; │
│ double maxHeight; │
│ } │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ CONSTRAINT TYPES │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ TIGHT (min == max) │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ BoxConstraints.tight(Size(100, 100)) │ │ │
│ │ │ → minW=100, maxW=100, minH=100, maxH=100 │ │ │
│ │ │ → Child PHẢI có size 100x100 │ │ │
│ │ │ → Ví dụ: SizedBox(width: 100, height: 100, child: ...) │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ LOOSE (min == 0) │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ BoxConstraints.loose(Size(100, 100)) │ │ │
│ │ │ → minW=0, maxW=100, minH=0, maxH=100 │ │ │
│ │ │ → Child có thể từ 0 đến 100 │ │ │
│ │ │ → Ví dụ: Center loosens constraints cho child │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ UNBOUNDED (max == infinity) │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ BoxConstraints(minW: 0, maxW: ∞, minH: 0, maxH: ∞) │ │ │
│ │ │ → Không giới hạn │ │ │
│ │ │ → Ví dụ: Trong ListView, height là unbounded │ │ │
│ │ │ → ⚠️ Gây lỗi nếu child muốn expand! │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘5.3 Layout Complexity: O(n)
┌─────────────────────────────────────────────────────────────────────────────────┐
│ LINEAR TIME LAYOUT │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Flutter layout đạt O(n) time complexity nhờ: │
│ │
│ 1. SINGLE PASS - Mỗi node được visit tối đa 2 lần: │
│ • Lần 1: Nhận constraints, tính size │
│ • (Không có lần quay lại để "thương lượng") │
│ │
│ 2. CONSTRAINTS LÀ FINAL: │
│ • Parent pass constraints → Child PHẢI tuân thủ │
│ • Child không thể yêu cầu parent thay đổi constraints │
│ │
│ 3. SUBTREE ISOLATION: │
│ • Child chỉ biết constraints của mình │
│ • Không thể nhìn siblings hoặc parent's size │
│ │
│ Trade-off: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ [-] Không thể: "Làm tôi to bằng sibling lớn nhất" │ │
│ │ [-] Cần IntrinsicHeight/Width cho một số cases (tốn performance) │ │
│ │ │ │
│ │ [+] Layout cực nhanh │ │
│ │ [+] Predictable │ │
│ │ [+] Không infinite loops │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘5.4 relayoutBoundary
┌─────────────────────────────────────────────────────────────────────────────────┐
│ RELAYOUT BOUNDARY │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Khi RenderObject thay đổi size, parent cần re-layout. │
│ Nhưng propagation có thể dừng lại tại RELAYOUT BOUNDARY. │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ RenderObject A │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ RenderObject B ← relayoutBoundary = true │ │
│ │ │ (constraints là TIGHT, size cố định) │ │
│ │ ▼ │ │
│ │ RenderObject C ← thay đổi size │ │
│ │ │ │
│ │ Khi C thay đổi: │ │
│ │ • C.markNeedsLayout() │ │
│ │ • Propagate lên... và DỪNG tại B (boundary) │ │
│ │ • A KHÔNG bị re-layout │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ relayoutBoundary = true khi: │
│ • constraints.isTight (min == max cho cả w và h) │
│ • sizedByParent = true │
│ • parentUsesSize = false │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘6. Paint Phase - Chi tiết
6.1 Paint Method
┌─────────────────────────────────────────────────────────────────────────────────┐
│ PAINT PHASE │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Mỗi RenderObject có method paint(): │
│ │
│ void paint(PaintingContext context, Offset offset) { │
│ // 1. Vẽ background │
│ context.canvas.drawRect( │
│ Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height), │
│ Paint()..color = Colors.blue, │
│ ); │
│ │
│ // 2. Vẽ children │
│ context.paintChild(child, offset + Offset(10, 10)); │
│ │
│ // 3. Vẽ foreground decorations │
│ context.canvas.drawLine(...); │
│ } │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ PaintingContext cung cấp: │ │
│ │ • canvas: để vẽ trực tiếp │ │
│ │ • paintChild(): để vẽ child với compositing │ │
│ │ • pushLayer(): để tạo layer mới │ │
│ │ • pushClipRect(): clip region │ │
│ │ • pushOpacity(): apply opacity │ │
│ │ • pushTransform(): apply transform │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘6.2 repaintBoundary
┌─────────────────────────────────────────────────────────────────────────────────┐
│ REPAINT BOUNDARY │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Khi RenderObject cần repaint, thông thường cả subtree của parent │
│ cũng phải repaint. repaintBoundary giới hạn điều này. │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ KHÔNG có RepaintBoundary: CÓ RepaintBoundary: │ │
│ │ │ │
│ │ Parent Parent │ │
│ │ │ │ │ │
│ │ ┌───┴───┐ ┌───┴───┐ │ │
│ │ │ │ │ │ │ │
│ │ Child A Child B ← repaint Child A RepaintBoundary │ │
│ │ │ │ │
│ │ → Cả Parent phải repaint! Child B ← repaint │ │
│ │ │ │
│ │ → Chỉ Child B repaint │ │
│ │ → Parent dùng cached layer │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Dùng RepaintBoundary widget: │
│ • Animations thường xuyên │
│ • Scrolling content │
│ • Video playback │
│ • Spinning loaders │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘7. Compositing Phase
7.1 Layer Tree
┌─────────────────────────────────────────────────────────────────────────────────┐
│ LAYER TREE │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Paint phase tạo ra LAYER TREE song song với Render Tree: │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ TransformLayer (root) │ │
│ │ │ │ │
│ │ ├── OffsetLayer (main content) │ │
│ │ │ │ │ │
│ │ │ ├── PictureLayer (static background) │ │
│ │ │ │ │ │
│ │ │ └── PictureLayer (text, icons) │ │
│ │ │ │ │
│ │ ├── OpacityLayer (fading element) │ │
│ │ │ │ │ │
│ │ │ └── PictureLayer │ │
│ │ │ │ │
│ │ └── ClipRectLayer (clipped area) │ │
│ │ │ │ │
│ │ └── PictureLayer (scrollable content) │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer types: │
│ • PictureLayer: Chứa drawing commands │
│ • ContainerLayer: Chứa child layers │
│ • TransformLayer: Apply matrix transform │
│ • OpacityLayer: Apply opacity │
│ • ClipRectLayer, ClipRRectLayer, ClipPathLayer: Clipping │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘7.2 Scene Building
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SCENE BUILDING │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ RenderView.compositeFrame() được gọi khi platform yêu cầu frame mới: │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Tạo SceneBuilder │ │
│ │ final builder = SceneBuilder(); │ │
│ │ │ │
│ │ 2. Layer tree add vào builder │ │
│ │ layer.addToScene(builder); │ │
│ │ │ │
│ │ builder.pushTransform(...) │ │
│ │ builder.pushOffset(...) │ │
│ │ builder.addPicture(...) │ │
│ │ builder.pop() │ │
│ │ ... │ │
│ │ │ │
│ │ 3. Build Scene │ │
│ │ final scene = builder.build(); │ │
│ │ │ │
│ │ 4. Gửi đến Engine │ │
│ │ window.render(scene); │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Scene là một opaque object chứa tất cả instructions để render UI │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘8. Rasterization Phase
8.1 GPU Rendering
┌─────────────────────────────────────────────────────────────────────────────────┐
│ RASTERIZATION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Scene được chuyển từ Dart Framework → Flutter Engine → GPU: │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Dart (UI Thread) │ │
│ │ │ │ │
│ │ │ window.render(scene) │ │
│ │ ▼ │ │
│ │ Flutter Engine │ │
│ │ │ │ │
│ │ │ Scene → Display List │ │
│ │ ▼ │ │
│ │ Raster Thread │ │
│ │ │ │ │
│ │ │ Impeller/Skia processes display list │ │
│ │ │ Creates GPU commands │ │
│ │ ▼ │ │
│ │ GPU │ │
│ │ │ │ │
│ │ │ Execute shaders │ │
│ │ │ Rasterize to framebuffer │ │
│ │ ▼ │ │
│ │ Display Hardware │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ │
│ UI Thread và Raster Thread chạy SONG SONG: │
│ • UI Thread chuẩn bị frame N+1 │
│ • Raster Thread render frame N │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘9. Performance Considerations
9.1 Pipeline Jank Points
┌─────────────────────────────────────────────────────────────────────────────────┐
│ COMMON JANK CAUSES │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ BUILD PHASE: │
│ • Quá nhiều widgets rebuild không cần thiết │
│ • build() method phức tạp, tốn thời gian │
│ • Không dùng const widgets │
│ │
│ LAYOUT PHASE: │
│ • IntrinsicHeight/Width (O(n²) thay vì O(n)) │
│ • Layout triggers từ deep in tree propagate lên root │
│ │
│ PAINT PHASE: │
│ • Không có RepaintBoundary cho animations │
│ • saveLayer() unnecessary (opacity, clip) │
│ │
│ RASTERIZATION: │
│ • Shader compilation (Skia) - lần đầu gặp effect mới │
│ • Too many layers │
│ • Large images │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘9.2 Optimization Strategies
┌─────────────────────────────────────────────────────────────────────────────────┐
│ OPTIMIZATION STRATEGIES │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. MINIMIZE REBUILDS: │
│ • Sử dụng const constructors │
│ • Tách widgets nhỏ với state riêng │
│ • Dùng Provider/Riverpod select() để chỉ listen cần thiết │
│ │
│ 2. OPTIMIZE LAYOUT: │
│ • Tránh IntrinsicHeight/Width │
│ • Sử dụng ListView.builder thay vì ListView với children │
│ • Sử dụng const SizedBox thay vì Container trống │
│ │
│ 3. OPTIMIZE PAINT: │
│ • Wrap animations trong RepaintBoundary │
│ • Tránh Opacity widget cho complex trees (dùng AnimatedOpacity) │
│ • Cache images với precacheImage() │
│ │
│ 4. DEBUG TOOLS: │
│ • flutter run --profile │
│ • DevTools → Performance tab │
│ • debugPaintSizeEnabled = true │
│ • debugRepaintRainbowEnabled = true │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘📝 Tóm tắt
| Phase | Input | Output | Thread |
|---|---|---|---|
| User Input | Raw events | Gesture callbacks | UI |
| Animation | VSync signal | Updated values | UI |
| Build | Dirty elements | Widget/Element trees | UI |
| Layout | Constraints | Sizes, positions | UI |
| Paint | Render tree | Layer tree | UI |
| Compositing | Layer tree | Scene | UI |
| Rasterization | Scene | Pixels | Raster |
Điểm quan trọng:
- Pipeline chạy mỗi frame (16.67ms @ 60fps)
- Build/Layout/Paint trên UI Thread - không block!
- Rasterization trên Raster Thread - parallel
- Tối ưu: const widgets, RepaintBoundary, relayout boundary
Last updated on