# 渲染管线

> 一个像素如何抵达屏幕 —— Paint → 按图元的渲染器 → OpenGL —— 以及什么是 batching。

Source: https://crossfly.dev/zh/docs/concepts/rendering-pipeline — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 渲染管线

理解绘制如何变成像素，就能解释框架的多条性能规则。

<div class="cf-diagram">
<svg role="img" id="my-svg" width="627" height="128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 626.781px; background-color: transparent;" viewBox="0 0 626.78125 128" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:Manrope,system-ui,sans-serif;font-size:14px;fill:var(--text);}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:var(--bg-elev);}#my-svg .error-text{fill:var(--text-dim);stroke:var(--text-dim);}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:var(--text-dim);stroke:var(--text-dim);}#my-svg .marker.cross{stroke:var(--text-dim);}#my-svg svg{font-family:Manrope,system-ui,sans-serif;font-size:14px;}#my-svg p{margin:0;}#my-svg .label{font-family:Manrope,system-ui,sans-serif;color:var(--text);}#my-svg .cluster-label text{fill:var(--text-dim);}#my-svg .cluster-label span{color:var(--text-dim);}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:var(--text);color:var(--text);}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:var(--bg-soft);stroke:var(--border-strong);stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:var(--text-dim);stroke:var(--text-dim);stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:var(--text-dim)!important;stroke-width:0;stroke:var(--text-dim);}#my-svg .arrowheadPath{fill:rgba(255, 255, 255, 0);}#my-svg .edgePath .path{stroke:var(--text-dim);stroke-width:1px;}#my-svg .flowchart-link{stroke:var(--text-dim);fill:none;}#my-svg .edgeLabel{background-color:var(--bg-elev);text-align:center;}#my-svg .edgeLabel p{background-color:var(--bg-elev);}#my-svg .edgeLabel rect{opacity:0.5;background-color:var(--bg-elev);fill:var(--bg-elev);}#my-svg .labelBkg{background-color:rgba(255, 255, 255, 0.5);}#my-svg .cluster rect{fill:var(--bg-elev);stroke:hsl(0, 0%, 90%);stroke-width:1px;}#my-svg .cluster text{fill:var(--text-dim);}#my-svg .cluster span{color:var(--text-dim);}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Manrope,system-ui,sans-serif;font-size:12px;background:var(--bg-elev);border:1px solid hsl(0, 0%, 90%);border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:var(--text);}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:var(--bg-elev);text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:var(--bg-elev);padding:2px;}#my-svg .icon-shape .label rect,#my-svg .image-shape .label rect{opacity:0.5;background-color:var(--bg-elev);fill:var(--bg-elev);}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg .node .neo-node{stroke:var(--border-strong);}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].swimlane.cluster rect{filter:none;}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:1px;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node .neo-line path{stroke:var(--border-strong);filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node circle .state-start{fill:var(--text-dim);}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg :root{--mermaid-font-family:Manrope,system-ui,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5;"/></marker><marker id="my-svg_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M209.594,64L213.76,64C217.927,64,226.26,64,233.927,64C241.594,64,248.594,64,252.094,64L255.594,64" id="my-svg-L_Paint_GL_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_Paint_GL_0" data-points="W3sieCI6MjA5LjU5Mzc1LCJ5Ijo2NH0seyJ4IjoyMzQuNTkzNzUsInkiOjY0fSx7IngiOjI1OS41OTM3NSwieSI6NjR9XQ==" data-look="classic" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M457.438,64L461.604,64C465.771,64,474.104,64,481.771,64C489.438,64,496.438,64,499.938,64L503.438,64" id="my-svg-L_GL_OpenGL_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_GL_OpenGL_0" data-points="W3sieCI6NDU3LjQzNzUsInkiOjY0fSx7IngiOjQ4Mi40Mzc1LCJ5Ijo2NH0seyJ4Ijo1MDcuNDM3NSwieSI6NjR9XQ==" data-look="classic" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" data-id="L_Paint_GL_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_GL_OpenGL_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="my-svg-flowchart-Paint-0" data-look="classic" transform="translate(108.796875, 64)"><rect class="basic label-container" style="" x="-100.796875" y="-56" width="201.59375" height="112"/><g class="label" style="" transform="translate(-72.796875, -42)"><rect/><foreignObject width="145.59375" height="84"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p><b>Paint</b><br />高级门面<br />(rect, text, line, image)<br />局部坐标 + clip</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-GL-1" data-look="classic" transform="translate(358.515625, 64)"><rect class="basic label-container" style="" x="-98.921875" y="-45.5" width="197.84375" height="91"/><g class="label" style="" transform="translate(-70.921875, -31.5)"><rect/><foreignObject width="141.84375" height="63"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p><b>gl / Renderers</b><br />每个图元一个渲染器<br />(每个都是一个 shader)</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-OpenGL-2" data-look="classic" transform="translate(563.109375, 64)"><rect class="basic label-container" style="" x="-55.671875" y="-24.5" width="111.34375" height="49"/><g class="label" style="" transform="translate(-27.671875, -10.5)"><rect/><foreignObject width="55.34375" height="21"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p><b>OpenGL</b></p></span></div></foreignObject></g></g></g></g></g><defs><filter id="my-svg-drop-shadow" height="130%" width="130%"><feDropShadow dx="4" dy="4" stdDeviation="0" flood-opacity="0.06" flood-color="var(--text-dim)"/></filter></defs><defs><filter id="my-svg-drop-shadow-small" height="150%" width="150%"><feDropShadow dx="2" dy="2" stdDeviation="0" flood-opacity="0.06" flood-color="var(--text-dim)"/></filter></defs><linearGradient id="my-svg-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="var(--border-strong)" stop-opacity="1"/><stop offset="100%" stop-color="hsl(0, 0%, 90%)" stop-opacity="1"/></linearGradient></svg>
</div>

## 绘制的各个层次
- **`Paint`** 是你在 `onPaint(...)` 中用来绘制的门面。它以**本地坐标**、
  **clip** 和**平移**来思考 —— 并且**完全不了解 shader** 或 OpenGL。你说"在这里
  画这个矩形"；至于它如何变成三角形和 GPU 调用，那不是你要操心的。
- **`gl` / 渲染器** —— **每种图元都有一个专用渲染器**（矩形、圆角
  矩形、线、文本、图像……），每一个本质上都是一个 shader。`Paint` 把工作委派给
  正确的渲染器。
- **OpenGL** —— 最终的 *draw calls*。

## 核心概念：batching
渲染器会把**同一类型**的绘制批量（batch）在一起，以减少 GPU 调用。由此
推导出一条规则：

> **切换图元类型会"关闭"前一个 batch。** 绘制顺序始终被保留，但
> 交替类型（矩形 → 文本 → 矩形 → 文本）会强制多次 flush；**把同一类型的调用
> 归拢在一起**才是带来性能的关键。

实践中，在繁重的自定义绘制里，把所有矩形一起画完再画所有文本，比交替
绘制的性能更好。

## 按需渲染 + 整树重绘
渲染模型的两个特性会影响你编写 `onPaint` 的方式：

- **按需：** 屏幕**只有在有人请求时才重绘**（`repaint()` → `requestRendering()`）。没有
  请求，就不会生成新帧 —— 这能省电。（其线程相关的视角见
  [paint thread](/docs/concepts/the-paint-thread)。）
- **没有*脏矩形*：** 当一帧发生时，**整个可见树都会被重绘**，而不只是
  改变的那部分。不存在局部重绘。

其实际后果是一条原则：

> **让 `onPaint` 保持廉价。** 它在每个渲染帧都会对整棵树运行一遍 —— 不要在其中
> 分配对象、加载资源或做昂贵计算。

## 一帧如何组装（高层视角）
在每个渲染帧，`EventsProcessor` 绘制**窗口栈**（通过 `paintWindows(...)`）：栈顶
窗口在上，下方窗口在后（弹窗会让前一个窗口仍然可见）。每个可见组件
都会收到它的 `onPaint(Paint)`。

```java
// onPaint 最小且廉价：复用临时对象，在本地坐标中绘制（0,0 = 左上角）
@Override public void onPaint(Paint paint) throws Exception {
   paint.rect(scratch.bounds(0, 0, width, height).fill(bg));   // 'scratch' 是一个可复用的 GRect
}
```

## 参见
- 应用：[自定义绘制](/docs/ui/images-and-graphics/custom-drawing)
- 下一篇：[paint thread](/docs/concepts/the-paint-thread)
