普通视图

发现新文章,点击刷新页面。
昨天 — 2026年2月7日首页

使用github copliot chat的源码学习之Chromium Compositor

作者 吴仰晖
2026年2月7日 18:24

github推出了 copilot chat,里面比较方便的是它可以很快速地搜索和获取各开源项目的上下文。除了单纯的Ask,其实还有执行任务的Agent(不过要开copilot pro),似乎目前是限制了每个月50次request。

image.png

因此我利用这个来学习源码,让AI回答关于源码的东西。下面的内容是出自copilot的回答(我认为比较好的),只是个人进行了整理。 这个博客的营养价值并不高,最大的价值可能就是告诉你有这个方便的途径去了解和学习源码。可以直接跳到完整示例,前面大抵只是一些铺垫。

Chromium 页面渲染的核心架构

1. Blink 渲染引擎 (核心组件)

根据官方文档,Blink 是 Chromium 的渲染引擎,负责页面内容的最终展示:

## Blink (Rendering Engine)

Blink 实现了所有在浏览器标签页内渲染内容的功能:

* 实现网络平台规范(如HTML标准),包括DOM、CSS和Web IDL
* 嵌入V8引擎并运行JavaScript
* 从底层网络栈请求资源
* **构建DOM树**
* **计算样式和布局**
* **嵌入Chrome Compositor并绘制图形**

2. 多进程渲染管道

Chromium 采用多进程架构:

  • Browser Process (浏览器进程):管理用户交互、网络请求、打印等
  • Renderer Process (渲染进程):在沙箱中运行,执行 Blink 引擎进行页面渲染
  • Compositor Process:负责最终的图形合成和显示

3. 渲染流程的关键步骤

a. 页面加载与DOM构建

// PrintRenderFrameHelper 处理渲染相关的工作
class PrintRenderFrameHelper
    : public blink::WebPrintClient,
      public content::RenderFrameObserver {
  // 为打印准备页面框架
  void OnFramePreparedForPrintPages();
  void PrintPages();
  bool RenderPagesForPrint(blink::WebLocalFrame* frame,
                           const blink::WebNode& node);
};

b. 计算布局与样式

Blink 引擎在 DOM 树构建后,计算每个元素的样式(CSS)并确定其在页面中的位置和大小。

c. 绘制(Paint)与栅格化(Rasterization)

// 页面内容渲染到画布
void RenderPageContent(blink::WebLocalFrame* frame,
                       uint32_t page_index,
                       cc::PaintCanvas* canvas) {
  TRACE_EVENT1("print", "RenderPageContent", "page_index", page_index);
  frame->PrintPage(page_index, canvas);
}

d. 图形合成(Compositing)

Chrome Compositor 负责合成不同的图层,最终生成帧(Frame)。

4. Compositor 的作用

Compositor 是 Chromium 的图形合成器,用于:

  • 合成多个渲染层
  • 处理复杂的变换和效果
  • 优化渲染性能
  • 管理硬件加速渲染(GPU)

5. 渲染周期(Frame Lifecycle)

一个完整的渲染周期包括:

输入事件 → DOM 更新 → 样式计算 → 布局计算 → 绘制操作 → 栅格化 → 合成 → 显示

6. 优化策略

  • 增量渲染:只重新渲染受影响的区域(Dirty Regions)
  • 分层渲染:使用多个渲染层提高效率
  • GPU 加速:利用硬件加速加快渲染速度
  • 异步渲染:多个进程并行处理不同的渲染任务

7. 性能监控

Chromium 内置性能监控:

// PDF 渲染的性能测量
if (!first_paint_metric_reported_ && !client_->IsPrintPreview()) {
  first_paint_metric_reported_ = true;
  base::UmaHistogramMediumTimes("PDF.FirstPaintTime",
                                begin_time - engine_creation_time_);
}

总结

Chromium 的页面渲染是一个复杂的分阶段过程:

  1. 解析阶段:解析 HTML/CSS/JavaScript,构建 DOM 和样式树
  2. 计算阶段:计算布局(Layout)和样式(Style)
  3. 绘制阶段:生成绘制指令(Paint Commands)
  4. 栅格化阶段:将绘制指令转换为像素数据
  5. 合成阶段:合成多个层并送到显示设备
  6. 显示阶段:在屏幕上展示最终画面

Compositor 的完整分层架构和数据流

根据官方文档 life_of_a_frame.md 和代码分析,Compositor 分为以下主要部分:

1. BeginFrame 协调层(Frame Coordination)

输入:来自 Display Compositor 的 BeginFrame 信号
↓
输出:BeginFrameArgs(包含时间戳、帧号等)

职责:

  • 同步主线程和合成线程的帧生成
  • 管理帧率和垂直同步
  • 分发 BeginFrame 通知

2. 主线程阶段(Main Thread Pipeline)

BeginFrame
    ↓
[1] BeginMainFrame - 合成线程请求主线程更新
    ↓
    输入:LayerTreeHost 配置、用户输入、动画状态
    输出:BeginMainFrameAndCommitState
    ↓
[2] Animate - 主线程更新动画
    ↓
    输入:当前时间、动画状态
    输出:更新后的属性值
    ↓
[3] UpdateLayers - Blink 执行布局和绘制
    ↓
    输入:DOM 树、样式树
    输出:DisplayItemList(绘制操作)
    ↓
[4] Commit - 推送改变到合成线程
    ↓
    输入:Layer 树、DisplayItemList、属性
    输出:CommitState(原子性提交状态)

详细步骤:

1. Animate Phase
   输入:LayerAnimationController (cc/animation/*)
   输出:变换、不透明度等属性的新值

2. UpdateLayers Phase  
   输入:Layer 树,由 Blink 通过 LayerTreeHostClient 接口触发
   - client_->UpdateLayerTreeHost()
   - Blink 执行布局计算
   - Blink 执行绘制,产生 DisplayItemList
   输出:更新后的 Layer 树和 DisplayItemList

3. Commit Phase
   - PushPropertiesTo():每个 Layer 推送属性到 LayerImpl
   - 交换 Pending Tree(待定树)和 Active Tree(活动树)
   - 同步动画状态、滚动状态等

3. 合成线程阶段(Impl Thread Pipeline)

这是 Compositor 的核心,分为五个关键阶段:

阶段 1:Commit 完成(Finish Commit)

输入:CommitState
    - Layer 树结构和属性
    - DisplayItemList
    - Scroll 状态
    - 动画数据
    
处理:
    - FinishCommit():合成线程接收提交的状态
    - 更新 LayerImpl 树
    - 更新属性树(Transform Tree、Clip Tree 等)
    
输出:LayerTreeImpl(合成线程的活动树)

阶段 2:Prepare Tiles(准备瓦片)

输入:LayerTreeImpl、显示视口、缩放因子

处理:
    - CalculateRasterScales():计算每个层的栅格化缩放
    - PrepareTiles():
        ├─ CalculateLiveRects():计算可见瓦片范围
        ├─ AssignGpuMemoryToTiles():分配 GPU 内存预算
        └─ ScheduleRasterTasks():安排栅格化任务队列
    
    - TileManager 的职责:
        ├─ 优先级排序(视口内 > 视口外)
        ├─ 管理软件和 GPU 栅格化
        ├─ 管理图像解码
        └─ 管理蛋糕层(cake layers)

输出:Raster Tasks(栅格化任务队列)
    - 每个任务是:(Tile, RasterSource, Priority)

阶段 3:Rasterization(栅格化)

输入:Raster Tasks
    - RasterSource(DisplayItemList 的包装)
    - 目标��片大小
    - 缩放因子
    - 栅格化位置

处理过程(在工作线程执行):

    1. PaintOpBuffer Playback
       输入:DisplayItemList 中的 PaintOps
       处理:
           - GetOffsetsOfOpsToRaster():使用 R-Tree 查询需要的操作
           - 创建 SkCanvas(CPU 或 GPU)
           - 遍历相关 PaintOps,调用 Raster()
       输出:像素数据或 GPU 命令
    
    2. Software Raster(软件栅格化)
       输入:PaintOps,输出大小
       处理:
           - 在内存中创建位图
           - 使用 Skia 的 CPU 后端绘制
           - 使用 SIMD 优化
       输出:SkBitmap(CPU 内存中的像素)
    
    3. GPU Raster(GPU 栅格化)
       输入:PaintOps,GPU 上下文
       处理:
           - 序列化 PaintOps
           - 通过 RasterInterface 发送到 GPU
           - GPU 命令缓冲区执行绘制
           - Skia 的 GPU 后端(Ganesh)处理
       输出:GPU 纹理资源

输出:Rasterized Tiles
    - 包含像素数据(软件)或 GPU 纹理(GPU)

关键类和函数:

// RasterSource:DisplayItemList 的包装,提供栅格化接口
class RasterSource {
    void PlaybackToCanvas(
        SkCanvas* raster_canvas,
        const gfx::Rect& canvas_bitmap_rect,  // 目标位置
        const gfx::AxisTransform2d& raster_transform,  // 缩放
        const PlaybackSettings& settings);
};

// TileManager:协调栅格化
class TileManager {
    void PrepareTiles(const PrepareTilesParams& params);
    void ScheduleRasterTasks();
};

// DisplayItemList:包含 PaintOps
class DisplayItemList {
    void Raster(SkCanvas* canvas, const PlaybackParams& params);
    std::vector<size_t> OffsetsOfOpsToRaster(SkCanvas* canvas);
};

阶段 4:Activation(激活)

输入:Pending Tree(待定树,已栅格化)

处理:
    - WaitForAllTilesToRasterize():等待所有关键瓦片栅格化完成
    - ActivatePendingTree():
        ├─ Pending Tree → Active Tree
        ├─ 更新动画状态
        ├─ 更新页面缩放
        └─ 清空已完成的栅格化队列
    
输出:Active Tree(已激活的树,可用于绘制)

阶段 5:Draw Frame(绘制帧)

输入:Active Tree、BeginFrameArgs

处理:
    1. CalculateDrawProperties()
       输入:Active Tree、视口、设备缩放因子
       计算:
           - 每个层的最终变换矩阵
           - 剪裁区域
           - 可见范围
       输���:DrawProperties(每个 LayerImpl 都有)
    
    2. GenerateCompositorFrame()
       输入:Active Tree with DrawProperties
       处理:
           - 遍历 Layer 树,生成 Quads(四边形)
           - 每个 Quad 包含:
               ├─ 纹理/资源 ID
               ├─ 变换矩阵
               ├─ 剪裁区域
               ├─ 不透明度
               └─ 混合模式
           - 为每个 Quad 创建 RenderPass
           - 设置组合帧元数据
       输出:CompositorFrame
    
    3. CompositorFrame 结构
       viz::CompositorFrame {
           vector<RenderPass>:
               - RenderPass[0]:第一个离屏渲染目标
               - RenderPass[1]:第二个离屏渲染目标
               - ...
               - RenderPass[N]:最后一个输出到显示器的渲染通道
           
           vector<TransferableResource>:引用的 GPU 纹理
           CompositorFrameMetadata:元数据
               - frame_token:帧标识符
               - device_scale_factor:设备像素比
               - latency_info:性能指标
               - presentation_token:展示令牌
       }

输出:CompositorFrame(包含所有绘制指令)

4. Frame Sink & Viz(显示合成器)

输入:CompositorFrame

处理:
    1. SubmitCompositorFrame()
       - 验证资源有效性
       - 同步令牌处理
       - 帧令牌生成
    
    2. Viz 处理(viz/service/display/)
       - AggregateSurfaces():合成多个 Surface
       - ApplyFilters():应用滤镜效果
       - Rasterize():最后栅格化(如果需要)
       - GenerateDamageRect():计算脏区域
    
    3. Display 合成
       - 将多个来源的帧合成到最终输出表面
       - 应用变换和效果

输出:最终的屏幕显示内容

整体数据流图

┌─────────────────────────────────────────────────────────────────┐
│                    MAIN THREAD (主线程)                          │
│                                                                   │
│  Layer Tree    BeginMainFrame                                   │
│      │         Animate                                          │
│      │         UpdateLayers (Blink Layout & Paint)              │
│      │         → DisplayItemList (PaintOps)                     │
│      │                                                           │
│      └──→ Commit (PushPropertiesTo)                            │
│           CommitState ─────┐                                    │
│                            │                                    │
└────────────────────────────┼────────────────────────────────────┘
                             │
                    ┌────────▼──────────┐
                    │  ProxyImpl Bridge  │
                    │  (Main ↔ Impl)    │
                    └────────┬──────────┘
                             │
┌────────────────────────────▼────────────────────────────────────┐
│               COMPOSITOR THREAD (合成线程)                       │
│                                                                   │
│  1. FinishCommit()                                              │
│     CommitState → LayerTreeImpl                                  │
│     更新 LayerImpl 树、属性树                                      │
│                                                                   │
│  2. PrepareTiles()  ◄─ Scheduler 调度                           │
│     ├─ 计算栅格化缩放                                             │
│     ├─ 分配 GPU 内存                                             │
│     └─ 生成 RasterTasks 队列                                    │
│            │                                                    │
│            ▼ (Post to Worker Threads)                          │
│
│  3. Rasterization (Worker Threads)                             │
│     ├─ Software: DisplayItemList → SkBitmap                    │
│     └─ GPU: PaintOps → GPU Textures                            │
│            │                                                    │
│            ▼                                                    │
│
│  4. Activation()  ◄─ Scheduler 调度                            │
│     Pending Tree (Rasterized) → Active Tree                    │
│                                                                   │
│  5. DrawFrame() / GenerateCompositorFrame()                     │
│     ├─ CalculateDrawProperties()                               │
│     ├─ BuildQuads()                                            │
│     ├─ 生成 RenderPass 列表                                     │
│     └─ 输出 CompositorFrame                                    │
│            │                                                    │
│            ▼                                                    │
│
│  SubmitCompositorFrame(frame)                                  │
│            │                                                    │
└────────────┼──────────────────────────────────────────────────┘
             │
    ┌────────▼───────────┐
    │  LayerTreeFrameSink │
    │  (GPU Channel)      │
    └────────┬────────────┘
             │
┌────────────▼──────────────────────────────────────────────────┐
│               VIZ DISPLAY COMPOSITOR (显示合成器)              │
│                                                                  │
│  1. MaybeSubmitCompositorFrame()                               │
│     - 验证帧                                                    │
│     - 应用同步令牌                                               │
│                                                                  │
│  2. AggregateSurfaces()                                        │
│     - 多个 Surface 合成                                         │
│     - Z-order 排序                                             │
│                                                                  │
│  3. Display::Draw()                                            │
│     - GPU 驱动程序指令                                          │
│     - 应用滤镜和效果                                             │
│                                                                  │
│  4. Swap & Present                                            │
│     - 缓冲交换 (Backbuffer → Frontbuffer)                      │
│     - VSync 同步                                               │
│     - 显示器显示                                                 │
│                                                                  │
└──────────────────────────────────────────────────────────────┘

关键概念总结表

阶段 输入 处理器 输出 执行线程
Animate AnimationState LayerAnimationController 属性值 Main
UpdateLayers DOM 树、样式 Blink DisplayItemList Main
Commit Layer 树 LayerTreeHost CommitState Main→Impl
FinishCommit CommitState LayerTreeHostImpl LayerTreeImpl Impl
PrepareTiles LayerTreeImpl TileManager RasterTasks Impl
Rasterization RasterTasks RasterWorkerPool GPU 纹理/位图 Worker
Activation Pending Tree LayerTreeHostImpl Active Tree Impl
DrawFrame Active Tree LayerTreeHostImpl CompositorFrame Impl
Display CompositorFrame DisplayCompositor 屏幕输出 Display

ProxyMain/ProxyImpl 的角色

ProxyMain (主线程)
├─ 负责与主线程通信
├─ 接收 BeginMainFrame 信号
├─ 管理 CommitPipelineStage
└─ 回调主线程结果

ProxyImpl (合成线程)
├─ 负责与合成线程通信
├─ 控制 Scheduler
├─ 管理 LayerTreeHostImpl
└─ 提交 CompositorFrame 到 LayerTreeFrameSink

Compositor 完整示例:绘制一个蓝色正方形

让我们假设要在网页上绘制一个蓝色的 200×200 像素正方形,位置在 (100, 100)

第1步:主线程 - Blink 记录绘制操作

用户在 HTML/CSS 中写了:

<div style="
  width: 200px;
  height: 200px;
  background-color: blue;
  position: absolute;
  top: 100px;
  left: 100px;
"></div>

Blink 布局完成后,知道要绘制:

  • 位置:(100, 100)
  • 大小:200×200
  • 颜色:蓝色 RGB(0, 0, 255)

然后它创建一个 DisplayItemList 来记录这个绘制操作:

// Blink 在主线程上执行
auto display_list = base::MakeRefCounted<cc::DisplayItemList>();

display_list->StartPaint();

// 1. 保存图形状态
display_list->push<cc::SaveOp>();

// 2. 绘制蓝色正方形
cc::PaintFlags blue_flags;
blue_flags.setColor(SK_ColorBLUE);  // RGB(0, 0, 255)
blue_flags.setStyle(SkPaint::kFill_Style);

display_list->push<cc::DrawRectOp>(
    SkRect::MakeXYWH(100, 100, 200, 200),  // x, y, width, height
    blue_flags
);

// 3. 恢复图形状态
display_list->push<cc::RestoreOp>();

display_list->EndPaintOfUnpaired(gfx::Rect(100, 100, 200, 200));

// 完成记录
display_list->Finalize();

// 此时 DisplayItemList 内部包含:
// 
// paint_op_buffer_ = [
//   SaveOp,
//   DrawRectOp(x=100, y=100, w=200, h=200, color=blue),
//   RestoreOp
// ]
//
// visual_rects_ = [
//   {100, 100, 200, 200},  // SaveOp 的可视范围
//   {100, 100, 200, 200},  // DrawRectOp 的可视范围
//   {100, 100, 200, 200}   // RestoreOp 的可视范围
// ]

这个阶段的输出: 一个包含"绘制指令"的对象,说明要在 (100,100) 位置绘制一个 200×200 的蓝色矩形。但这只是指令,还没有真正的像素数据!


第2步:提交到合成线程

主线程把 DisplayItemList 包装在 CommitState 中,发送给合成线程:

// 主线程创建 CommitState
auto commit_state = std::make_unique<CommitState>();

// 把 DisplayItemList 分配给对应的 Layer
// (在实际代码中,DisplayItemList 被存储在 PictureLayerImpl 中)
commit_state->source_frame_number = 120;
commit_state->device_viewport_rect = gfx::Size(1920, 1080);
commit_state->device_scale_factor = 1.0f;  // 假设普通屏幕
commit_state->background_color = SK_ColorWHITE;

// 推送到合成线程
layer_tree_host_->WillCommit(...)
// ...
layer_tree_host_->ActivateCommitState()  // 原子性推送

这个阶段的输出: CommitState 对象,包含所有需要的信息,包括 DisplayItemList。


第3步:合成线程 - 准备栅格化

合成线程收到 CommitState,开始准备栅格化:

// 合成线程上
LayerTreeHostImpl* host_impl = ...;

// 1. 接收 CommitState,更新 LayerTreeImpl
host_impl->FinishCommit(commit_state);

// 2. 准备瓦片(Tiles)
TileManager* tile_manager = host_impl->tile_manager();
tile_manager->PrepareTiles();

// 这会为 PictureLayerImpl 创建 Tiles
// 因为我们的正方形只有 200×200,可能只需要 1 个 Tile
// 假设 Tile 大小是 256×256

struct Tile {
    gfx::Rect rect;           // 瓦片在页面上的位置
    RasterSource* source;     // 指向 DisplayItemList (包装后)
    float scale;              // 栅格化缩放因子
    int x, y;                 // 瓦片的网格坐标
};

// 创建一个瓦片
Tile tile0{
    .rect = gfx::Rect(0, 0, 256, 256),      // 页面坐标
    .source = raster_source,                 // 包含我们的 DisplayItemList
    .scale = 1.0f,
    .x = 0,
    .y = 0
};

// TileManager 会创建栅格化任务
struct RasterTask {
    Tile* tile;
    RasterSource* raster_source;
    gfx::Rect target_rect;  // 在瓦片中的位置
    Priority priority;
};

RasterTask task{
    .tile = &tile0,
    .raster_source = raster_source,
    .target_rect = gfx::Rect(0, 0, 256, 256),
    .priority = PRIORITY_NOW  // 视口内,需要立即栅格化
};

// 提交栅格化任务给工作线程
tile_manager->ScheduleRasterTasks(&task);

这个阶段的输出: 栅格化任务队列,告诉工作线程要栅格化哪些区域。


第4步:工作线程 - 栅格化(最关键的部分!)

这是把绘制指令转换成实际像素的地方:

// 工作线程上执行栅格化任务
void RasterWorkerPool::RasterizeTask(const RasterTask& task) {
    // 创建一个绘制目标(画布)
    // 这是一个 256×256 的缓冲区,用来存放栅格化后的像素
    
    // 方案 A:CPU 栅格化(软件)
    if (use_software_raster) {
        // 创建 CPU 内存中的位图
        SkBitmap bitmap;
        bitmap.allocN32Pixels(256, 256);  // 256×256 的 32 位 RGBA 像素
        
        // 创建 Skia ��布,指向这个位图
        SkCanvas canvas(bitmap);
        
        // 现在我们要回放 DisplayItemList 中的绘制指令
        RasterSource* source = task.raster_source;
        
        // 关键步骤:回放绘制操作!
        source->PlaybackToCanvas(
            &canvas,                    // 目标画布
            gfx::Size(1920, 1080),      // 内容大小
            gfx::Rect(0, 0, 256, 256),  // 瓦片在内容中的位置
            gfx::Rect(0, 0, 256, 256),  // 需要栅格化的区域
            gfx::AxisTransform2d(1.0f), // 无缩放
            settings
        );
        
        // 现在 bitmap 中包含了栅格化后的像素!
        // 具体来说:
        // - 位置 (100, 100) 到 (300, 300) 的像素被设为蓝色
        // - 其他像素是白色(背景色)
        
        // bitmap 的内存布局示意:
        // 
        // 位置 (0,0) ──────────────────────→ (256,0)
        //    │ FFFFFF FFFFFF FFFFFF ...
        //    │ FFFFFF FFFFFF FFFFFF ...
        //    │ ...
        //  (100,100) 开始
        //    │ FFFFFF FFFFFF ...
        //    │ ...
        //    │ FFFFFF 0000FF 0000FF 0000FF ...  ← 蓝色像素!
        //    │ FFFFFF 0000FF 0000FF 0000FF ...
        //    │ FFFFFF 0000FF 0000FF 0000FF ...
        //    │ ...
        //  (300,300) 结束
        //    │ FFFFFF FFFFFF FFFFFF ...
        //    ↓
        // (256,256)
        
        // 上传到 GPU
        UploadBitmapToGPU(&bitmap, tile);
    }
    
    // 方案 B:GPU 栅格化
    else {
        // 创建 GPU 纹理作为渲染目标
        // 尺寸:256×256,格式:RGBA_8888
        GLuint texture = gl::CreateTexture(256, 256, GL_RGBA8);
        
        // 绑定为渲染目标
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                               GL_TEXTURE_2D, texture, 0);
        
        // 清除背景(白色)
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        
        // 创建 Skia 画布(绘制到 GPU)
        SkSurface* surface = SkSurface::MakeFromBackendTexture(...);
        SkCanvas* canvas = surface->getCanvas();
        
        // 回放 DisplayItemList 的绘制操作
        // 这次是发送 GPU 命令
        DisplayItemList::Raster(canvas, ...);
        
        // GPU 执行指令,在纹理中画出蓝色正方形
        // 纹理内容现在是:位置 (100, 100) 到 (300, 300) 是蓝色像素
    }
}

这个阶段发生了什么:

  1. 创建一个 256×256 的缓冲区(可以是 CPU 内存或 GPU 纹理)
  2. 清除背景为白色
  3. 回放 DisplayItemList 中的绘制指令:
    • SaveOp:保存状态
    • DrawRectOp(x=100, y=100, w=200, h=200, color=blue):在像素 (100,100) 到 (300,300) 之间填充蓝色
    • RestoreOp:恢复状态

具体的像素数据示意:

CPU 内存(SkBitmap)或 GPU 纹理的内容
(每个方块代表一个像素,用 16 进制表示 RGBA 颜色值)

y=0    FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ... (全是白色)
y=1    FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ...
...
y=99   FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ...
y=100  FFFFFFFF ... FFFFFFFF 0000FFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFFFF ...
       ^                      ^ (100,100) 蓝色开始      ^ (300,100) 蓝色结束
y=101  FFFFFFFF ... FFFFFFFF 0000FFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFFFF ...
y=102  FFFFFFFF ... FFFFFFFF 0000FFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFFFF ...
...
y=299  FFFFFFFF ... FFFFFFFF 0000FFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFFFF ...
y=300  FFFFFFFF ... FFFFFFFF 0000FFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFFFF ...
       ^                      ^ (100,300) 蓝色最后一行  ^ (300,300) 蓝色结束
y=301  FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ...
...
y=255  FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ...

其中:
- FFFFFFFF = 白色 (RGBA: 255,255,255,255)
- 0000FFFF = 蓝色 (RGBA: 0,0,255,255)

这个阶段的输出: GPU 纹理或位图,包含 256×256 个像素的实际颜色数据。


第5步:合成线程 - 生成 CompositorFrame

现在我们有了栅格化后的纹理,合成线程创建最终的合成帧:

// 合成线程
void LayerTreeHostImpl::GenerateCompositorFrame() {
    // 创建 CompositorFrame
    viz::CompositorFrame frame;
    
    // 设置元数据
    frame.metadata.frame_token = 0x12345;
    frame.metadata.device_scale_factor = 1.0f;
    frame.metadata.size_in_pixels = gfx::Size(1920, 1080);
    frame.metadata.begin_frame_ack = viz::BeginFrameAck(...);
    
    // 创建渲染通道
    auto render_pass = viz::CompositorRenderPass::Create();
    render_pass->SetNew(
        viz::CompositorRenderPassId(1),
        gfx::Rect(0, 0, 1920, 1080),      // 输出矩形(整个屏幕)
        gfx::Rect(100, 100, 200, 200),    // 脏区域(只有正方形区域需要重绘)
        gfx::Transform()                   // 无变换
    );
    
    // 添加 Quad(纹理四边形)
    auto quad = std::make_unique<viz::TextureDrawQuad>();
    
    // 从栅格化任务中获取纹理 ID
    ResourceId texture_id = 0x9999;  // GPU 纹理的句柄
    
    quad->SetNew(
        nullptr,  // shared_quad_state (共享状态)
        gfx::Rect(0, 0, 256, 256),         // Quad 在屏幕上的位置(注意:这是以瓦片的 (0,0) 开始)
        gfx::Rect(0, 0, 256, 256),         // 可见区域
        false,                              // 不需要混合
        texture_id,                         // 纹理 ID(指向我们栅格化的蓝色正方形纹理)
        true,                               // 预乘 alpha
        gfx::PointF(0, 0),                 // UV 左上角
        gfx::PointF(1, 1),                 // UV 右下角
        SK_ColorWHITE,                      // 背景色
        {1.0f, 1.0f, 1.0f, 1.0f}           // 混合颜色
    );
    
    // 重要!实际位置需要从瓦片坐标转换
    // 瓦片 (0,0) 对应屏幕位置 (0,0)
    // 但我们的正方形在 DisplayItemList 中是 (100, 100)
    // 所以最终 Quad 的 rect 应该是 (100, 100, 200, 200)
    
    // 更正:
    quad->rect = gfx::Rect(100, 100, 200, 200);  // 正确的屏幕位置
    quad->visible_rect = gfx::Rect(100, 100, 200, 200);
    quad->opacity = 1.0f;
    
    render_pass->quad_list.push_back(std::move(quad));
    
    // 添加资源
    viz::TransferableResource resource;
    resource.id = texture_id;
    resource.mailbox_holder = gpu::MailboxHolder(mailbox, sync_token, target);
    frame.resource_list.push_back(resource);
    
    // 添加渲染通道
    frame.render_pass_list.push_back(std::move(render_pass));
    
    return frame;
}

// CompositorFrame 现在包含:
// {
//   metadata: {
//     frame_token: 0x12345,
//     device_scale_factor: 1.0,
//     size_in_pixels: (1920, 1080),
//     ...
//   },
//   render_pass_list: [
//     {
//       output_rect: (0, 0, 1920, 1080),
//       damage_rect: (100, 100, 200, 200),
//       quad_list: [
//         TextureDrawQuad {
//           rect: (100, 100, 200, 200),
//           texture_id: 0x9999,
//           opacity: 1.0,
//           ...
//         }
//       ]
//     }
//   ],
//   resource_list: [
//     {
//       id: 0x9999,
//       mailbox: ...  (指向包含栅格化像素的 GPU 纹理)
//     }
//   ]
// }

这个阶段的输出: CompositorFrame,包含:

  • 要绘制的 Quads(四边形)
  • Quad 的位置和大小
  • 指向纹理的资源 ID
  • 纹理中的实际像素数据

第6步:Viz Display Compositor - 最终合成和显示

// Viz Display Compositor(显示合成器)
void Display::DrawFrame(...) {
    // 1. 接收 CompositorFrame
    viz::CompositorFrame frame = ...;
    
    // 2. 遍历所有 Quads
    for (auto& render_pass : frame.render_pass_list) {
        for (auto& quad : render_pass->quad_list) {
            if (auto texture_quad = quad.As<viz::TextureDrawQuad>()) {
                // 3. 获取纹理(包含栅格化的蓝色正方形像素)
                GLuint texture = GetTextureFromResourceId(texture_quad->resource_id);
                
                // 4. 在屏幕上绘制这个纹理
                // 位置:(100, 100)
                // 大小:200×200
                // 内容:我们栅格化的蓝色正方形纹理
                
                glBindTexture(GL_TEXTURE_2D, texture);
                glUniform2f(position_uniform, 100.0f, 100.0f);
                glUniform2f(size_uniform, 200.0f, 200.0f);
                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);  // 绘制四边形
            }
        }
    }
    
    // 5. 交换缓冲区
    SwapBuffers();
    // GPU 把缓冲区中的像素显示到屏幕上
}

这个阶段发生了什么:

  1. Display Compositor 收到 CompositorFrame
  2. 读取 Quad 的信息:位置 (100, 100),大小 200×200
  3. 发送 GPU 指令:在屏幕上的 (100, 100) 位置绘制纹理
  4. GPU 执行指令,把纹理中的像素(蓝色正方形)显示到屏幕缓冲区
  5. 交换缓冲区,屏幕显示最终结果

完整的数据变换总结

┌─────────────────────────────────────────────────────────────┐
│ 1. HTML/CSS 描述                                            │
│    <div style="width:200px; height:200px;                  │
│                background-color:blue; top:100px; left:100px;">
└─────────────────────────────────────────────────────────────┘
                           ↓ (Blink 布局)
┌─────────────────────────────────────────────────────────────┐
│ 2. DisplayItemList (绘制指令,不是像素)                      │
│    [SaveOp,                                                 ││     DrawRectOp(x=100, y=100, w=200, h=200, color=blue),   ││     RestoreOp]                                              │
└─────────────────────────────────────────────────────────────┘
                           ↓ (提交)
┌─────────────────────────────────────────────────────────────┐
│ 3. CommitState                                              │
│    (包含 DisplayItemList 和其他属性)                        │
└─────────────────────────────────────────────────────────────┘
                           ↓ (栅格化)
┌─────────────────────────────────────────────────────────────┐
│ 4. GPU 纹理(实际像素)256×256                              │
│    FFFFFF... FFFFFF... 蓝色(0000FF)... 蓝色... FFFFFF...   │
│    (100,100) 到 (300,300) 的像素是蓝色,其他是白色         │
└─────────────────────────────────────────────────────────────┘
                           ↓ (生成帧)
┌─────────────────────────────────────────────────────────────┐
│ 5. CompositorFrame                                          │
│    quad: {                                                  │
│      position: (100, 100),                                  │
│      size: (200, 200),                                      │
│      texture_id: 0x9999 → 指向包含蓝色像素的纹理           │
│    }                                                        │
└─────────────────────────────────────────────────────────────┘
                           ↓ (GPU 绘制)
┌─────────────────────────────────────────────────────────────┐
│ 6. 屏幕帧缓冲(1920×1080)                                   │
│    (100, 100) 到 (300, 300) 的像素现在是蓝色               │
│    其他像素是白色                                            │
│                                                              │
│    ┌──────────────────────────────────┐                     │
│    │ 白白白白白... 白白               │                     │
│    │ 白白白白白... 白白               │                     │
│    │ ...                              │                     │
│    │ 白白蓝蓝蓝... 蓝白               │ ← (100,100)         │
│    │ 白白蓝蓝蓝... 蓝白               │                     │
│    │ 白白蓝蓝蓝... 蓝白               │                     │
│    │ ...                              │                     │
│    │ 白白蓝蓝蓝... 蓝白               │ ← (300,300)         │
│    │ 白白白白白... 白白               │                     │
│    └──────────────────────────────────┘                     │
│                                                              │
│    ✓ 正方形显示在屏幕上!                                  │
└─────────────────────────────────────────────────────────────┘

关键概念总结

  1. DisplayItemList = 绘制指令的列表(文本格式)
  2. 栅格化 = 把绘制指令转换成像素的过程
  3. 纹理 = GPU 内存中的像素缓冲区
  4. CompositorFrame = 包含 Quads(四边形)的合成帧,每个 Quad 指向一个纹理
  5. 最终像素 = 由 GPU 把纹理中的像素通过 Quad 信息绘制到屏幕上

整个过程就像:

  • 指令像素纹理Quads屏幕显示

好问题!栅格化是整个过程中最有趣的部分。让我用最直观的方式解释它。太好了!现在我可以用实际的代码来解释栅格化的原理。让我用一个最简单的蓝色正方形例子详细讲解:

栅格化的本质:将指令转换成像素

核心思想:使用 Skia 库

栅格化的秘密就是:Chromium 使用一个叫 Skia 的图形库,它能把绘制指令转换成像素

想象 Skia 就像一个虚拟画家

  • 你给它一个空白画布(内存缓冲区)
  • 你给它绘制指令("画蓝色矩形")
  • 它就会一条条指令地执行,最后得到一个画满了颜色的画布

完整的栅格化过程(以蓝色正方形为例)

步骤 1:创建空白画布

// 工作线程上执行
// 假设我们要栅格化一个 256×256 的瓦片

// 方案 A:CPU 栅格化(软件方式)
// ====================================

// 创建一个 256×256 的位图(像素缓冲区)
SkBitmap bitmap;
bitmap.allocN32Pixels(256, 256);
// 现在 bitmap 中有 256*256 = 65536 个像素
// 每个像素 4 字节(RGBA),共 262,144 字节的内存

// 内存布局示意:
// bitmap.getPixels() 返回一个指向这块内存的指针
// 内存中的数据:
// [像素(0,0)的RGBA] [像素(1,0)的RGBA] [像素(2,0)的RGBA] ...
// [像素(0,1)的RGBA] [像素(1,1)的RGBA] [像素(2,1)的RGBA] ...
// ...
// [像素(255,255)的RGBA]

// 创建一个 Skia 画布,指向这个位图
SkCanvas canvas(bitmap);

// 方案 B:GPU 栅格化
// ====================================
// 创建一个 GPU 纹理作为渲染目标
GLuint framebuffer = CreateFramebuffer();
GLuint texture = CreateTexture(256, 256, GL_RGBA8);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                       GL_TEXTURE_2D, texture, 0);

// 创建指向这个 GPU 纹理的 Skia 画布
SkSurface* surface = SkSurface::MakeFromBackendTexture(...);
SkCanvas* canvas = surface->getCanvas();

现在我们有了一个空白画布,所有像素都是未初始化的。


步骤 2:清除背景

// 把所有像素设置为白色(背景色)
canvas->clear(SK_ColorWHITE);

// 底层发生了什么:
// Skia 填充整个 256×256 的像素区域
// 对于每一个像素 (x, y),设置它的值为:
// [R=255, G=255, B=255, A=255]  // 白色 RGBA

// 清除后的内存示意:
// [FFFFFFFF] [FFFFFFFF] [FFFFFFFF] ... (全是 0xFFFFFFFF = 白色)
// 总共 65536 个这样的 4 字节值

现在所有像素都是白色。


步骤 3:遍历 DisplayItemList 中的绘制指令并执行

这是最关键的部分!

// RasterSource::PlaybackDisplayListToCanvas()
void PlaybackDisplayListToCanvas(SkCanvas* raster_canvas,
                                 const PlaybackSettings& settings) {
    // 获取 DisplayItemList
    DisplayItemList* display_list = ...;  // 包含我们的绘制指令
    
    // 创建回放参数
    PlaybackParams params(settings.image_provider, SkM44());
    
    // 关键步骤:回放 DisplayItemList
    display_list->Raster(raster_canvas, params);
}

// DisplayItemList::Raster() 的实现
void DisplayItemList::Raster(SkCanvas* canvas,
                             const PlaybackParams& params) const {
    // 1. 获取需要绘制的操作的偏移量
    //    (使用 R-Tree 优化:只获取与画布相交的操作)
    std::vector<size_t> offsets = OffsetsOfOpsToRaster(canvas);
    
    // offsets = [0, 8, 24]  // SaveOp, DrawRectOp, RestoreOp 的偏移量
    
    // 2. 遍历 paint_op_buffer_ 中的操作,执行它们
    paint_op_buffer_.Playback(canvas, params, true, &offsets);
}

// PaintOpBuffer::Playback() 的实现
void PaintOpBuffer::Playback(SkCanvas* canvas,
                             const PlaybackParams& params,
                             bool local_ctm,
                             const std::vector<size_t>* offsets) const {
    // 遍历所有操作
    for (const PaintOp& op : PaintOpBuffer::OffsetIterator(*this, offsets)) {
        // 对于每一个操作,调用它的 Raster() 函数
        op.Raster(canvas, params);
        
        // 这就是魔法发生的地方!
        // Raster() 是虚函数,每个 PaintOp 子类有自己的实现
    }
}

现在让我们看看具体的操作执行:

// DisplayItemList 中有这些操作(序列化的形式):
// [SaveOp] [DrawRectOp(...)] [RestoreOp]

// ============================================
// 操作 1:SaveOp::Raster()
// ============================================
void SaveOp::Raster(const SaveOp* op,
                    SkCanvas* canvas,
                    const PlaybackParams& params) {
    canvas->save();
    // 这保存了当前的图形状态(颜色、变换、剪裁等)
}

// ============================================
// 操作 2:DrawRectOp::Raster()(最关键!)
// ============================================
void DrawRectOp::RasterWithFlags(const DrawRectOp* op,
                                 const PaintFlags* flags,
                                 SkCanvas* canvas,
                                 const PlaybackParams& params) {
    // op->rect = SkRect{x=100, y=100, w=200, h=200}
    // flags->color = SK_ColorBLUE = 0x0000FFFF (RGBA)
    
    // 这是最关键的调用!
    canvas->drawRect(op->rect, *flags);
    
    // Skia 会在这一刻做什么?
    // 它会遍历要填充的矩形中的每一个像素,设置它们的颜色为蓝色
}

// Skia 内部如何执行 drawRect?
// ====================================
// 伪代码,大致思路:
void SkCanvas::drawRect(const SkRect& rect, const SkPaint& paint) {
    // 1. 将 SkRect 转换为像素坐标
    int x1 = (int)rect.left();    // 100
    int y1 = (int)rect.top();     // 100
    int x2 = (int)rect.right();   // 300
    int y2 = (int)rect.bottom();  // 300
    
    SkColor color = paint.getColor();  // 0x0000FFFF (蓝色)
    
    // 2. 遍历矩形内的所有像素
    for (int y = y1; y < y2; y++) {
        for (int x = x1; x < x2; x++) {
            // 3. 对于每一个像素,写入蓝色颜色值
            uint32_t* pixel_ptr = bitmap.getAddr32(x, y);
            *pixel_ptr = color;  // 设置为 0x0000FFFF
        }
    }
    
    // 实际上 Skia 做的更复杂(处理反锯齿、混合等),
    // 但核心思想就是这样:写入像素值
}

// 栅格化后的内存状态:
// y=0    FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ... (全是白色)
// y=99   FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ...
// y=100  FFFFFF... FFFFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFF... (开始有蓝色!)
//        ^        ^       ^
//        0      100    100-300 是蓝色
// y=101  FFFFFF... FFFFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFF...
// ...
// y=299  FFFFFF... FFFFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFF...
// y=300  FFFFFF... FFFFFF 0000FFFF 0000FFFF ... 0000FFFF FFFFFF...
// y=301  FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF ... (又都是白色了)

// ============================================
// 操作 3:RestoreOp::Raster()
// ============================================
void RestoreOp::Raster(const RestoreOp* op,
                       SkCanvas* canvas,
                       const PlaybackParams& params) {
    canvas->restore();
    // 恢复之前保存的图形状态
}

经过这三个操作,我们已经有了一个完整的栅格化结果!


完整的数据变换示意

// 开始栅格化

// 输入 1:DisplayItemList(绘制指令)
DisplayItemList {
    paint_op_buffer: [
        SaveOp,
        DrawRectOp(
            rect={x:100, y:100, w:200, h:200},
            color=0x0000FFFF  // 蓝色
        ),
        RestoreOp
    ],
    visual_rects: [{100,100,200,200}, ...]
}

// 输入 2:空白画布(256×256 的位图)
SkBitmap bitmap {
    width: 256,
    height: 256,
    pixels: [
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 0 行(256 个白色像素)
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 1 行
        ...
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF   // 第 255 行
    ]
}

// ============================================================
// 执行回放
// ============================================================

canvas->save();
canvas->drawRect(SkRect{100,100,200,200}, blue_paint);
canvas->restore();

// 栅格化后的输出:位图内容被修改了
SkBitmap bitmap {
    width: 256,
    height: 256,
    pixels: [
        // y=0 到 y=99:全是白色
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 0 行
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 1 行
        ...
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 99 行
        
        // y=100 到 y=299:中间是蓝色,边缘是白色
        0xFFFFFFFF, ..., 0xFFFFFFFF, 0x0000FFFF, 0x0000FFFF, ..., 0x0000FFFF, 0xFFFFFFFF, ...,
        //         |
        //    x=0 到 99  100 到 299(蓝色区域)  300 到 255
        
        0xFFFFFFFF, ..., 0xFFFFFFFF, 0x0000FFFF, 0x0000FFFF, ..., 0x0000FFFF, 0xFFFFFFFF, ...,
        // ... 重复 200 次(y=100 到 y=299)
        
        // y=300 到 y=255:全是白色
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 300 行
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF,  // 第 301 行
        ...
        0xFFFFFFFF, 0xFFFFFFFF, ..., 0xFFFFFFFF   // 第 255 行
    ]
}

为什么能把指令转换成像素?

关键是 Skia 库的设计:

// Skia 的核心就是提供一个虚拟的 SkCanvas
// 无论你是在 CPU 上还是 GPU 上绘制,接口都是一样的

class SkCanvas {
public:
    // 这些方法有多种实现:
    // - CPU 实现:直接修改像素缓冲区
    // - GPU 实现:生成 GPU 指令
    
    virtual void drawRect(const SkRect& rect, const SkPaint& paint);
    virtual void drawPath(const SkPath& path, const SkPaint& paint);
    virtual void drawImage(const SkImage* image, ...);
    // ... 等等
};

// CPU 版本的 drawRect(简化)
class SkRasterCanvas : public SkCanvas {
    void drawRect(const SkRect& rect, const SkPaint& paint) override {
        // 直接操作 fBitmap 的像素
        uint32_t* pixels = fBitmap.getPixels();
        
        for (int y = rect.top(); y < rect.bottom(); y++) {
            for (int x = rect.left(); x < rect.right(); x++) {
                int index = y * width + x;
                pixels[index] = paint.getColor();  // 设置像素颜色
            }
        }
    }
};

// GPU 版本的 drawRect(简化)
class SkGPUCanvas : public SkCanvas {
    void drawRect(const SkRect& rect, const SkPaint& paint) override {
        // 生成 GPU 指令
        glBindFramebuffer(GL_FRAMEBUFFER, fFramebuffer);
        glClearColor(paint.getColor());
        glClear(GL_COLOR_BUFFER_BIT);
        // ... 等等
    }
};

总结:

  1. DrawRectOp 本质上就是 canvas->drawRect()
  2. drawRect() 遍历矩形内的所有像素
  3. 对于每个像素,设置它的颜色为指定的颜色
  4. 结果是位图中的像素被修改了

这就是为什么能把绘制指令转换成像素!


关键代码片段总结


void RasterSource::PlaybackDisplayListToCanvas(
    SkCanvas* raster_canvas,
    const PlaybackSettings& settings) const {
  CHECK(display_list_);
  
  // 创建回放参数
  PlaybackParams params(settings.image_provider, SkM44());
  params.raster_inducing_scroll_offsets =
      settings.raster_inducing_scroll_offsets;
  params.destination_hdr_headroom = settings.hdr_headroom;
  
  // 核心:回放 DisplayItemList
  // 这会遍历所有 PaintOps 并执行它们
  display_list_->Raster(raster_canvas, params);
}

// 这就是一切!
// display_list_->Raster() 会:
// 1. 遍历 DisplayItemList 中的每个 PaintOp
// 2. 对于每个 PaintOp,调用它的 Raster() 方法
// 3. 每个 Raster() 方法使用 Skia canvas 修改像素
// 4. 最后得到一个填满颜色的位图
❌
❌