2.1 绘图模型概述

2 概念
2.1 绘图模型概述
本章详细介绍 GTK 的绘图模型。如果你想了解 GTK 绘制其控件和窗口所遵循的流程,那么应该阅读本章;如果你决定实现自己的控件,了解这些内容会很有帮助。本章还将阐明 GTK 中某些操作方式背后的原因。
2.1.1 窗口(Window)与事件(event)
使用窗口系统的应用程序通常会在屏幕上创建称为“绘面(surface:在 Wayland 协议中指屏幕上的可绘制区域)”的矩形区域(GTK 遵循 Wayland 的术语,其他窗口系统如 X11 可能称之为“窗口(window)”)。传统的窗口系统不会自动保存绘面的图形内容,而是在需要时要求应用程序提供新内容。例如,如果一个堆叠在其他窗口下方的窗口被移到顶部,应用程序就必须重新绘制它,以便显示之前被遮挡的区域。当窗口系统要求应用程序重绘窗口时,它会向该窗口发送一个帧事件(在 X11 术语中称为曝光事件)。
每个 GTK 顶级窗口或对话框都与一个窗口系统绘面相关联。像按钮或输入框这样的子控件没有自己的表面,它们使用其顶级窗口的表面。
通常,当 GTK 从底层窗口系统接收到帧事件时,绘图周期开始:如果用户将一个窗口拖到另一个窗口上方,窗口系统会通知底层绘面需要重新绘制自身。当控件自身决定需要更新其显示时,也可以启动绘图周期。例如,当用户在输入框控件中输入一个字符时,输入框会要求 GTK 为其排队一个重绘操作。
窗口系统为绘面生成帧事件。GDK 与窗口系统的接口会将此类事件转换为触发受影响绘面的“::render”信号。GTK 顶级窗口会连接到该信号,并做出适当反应。
以下各节将描述 GTK 如何确定哪些控件需要响应此类事件进行重绘,以及控件在使用窗口系统资源方面的内部工作方式。
2.1.2 帧时钟
所有 GTK 应用程序都是由主循环驱动的,这意味着应用程序在大部分时间里都处于空闲状态,运行在一个循环中,这个循环只会等待事件发生,然后在事件发生时调用相应的处理。在此基础上,GTK 有一个帧时钟,它为应用程序提供“脉冲信号”。这个时钟以稳定的频率运行,其频率与输出设备的帧率相关(通过窗口管理器/合成器与显示器同步)。典型的刷新率是每秒 60 帧,因此大约每 16 毫秒就会产生一个新的“脉冲”。
该时钟包含多个阶段:
• 事件阶段(Events)
• 更新阶段(Update)
• 布局阶段(Layout)
• 绘制阶段(Paint)
这些阶段按上述顺序进行,并且我们会在回到起始阶段之前完整运行每个阶段。
事件阶段是每次重绘之间的一段时间,在此期间 GTK 会处理来自用户的输入事件以及其他事件(例如网络 I/O 事件)。有些事件(如鼠标移动事件)会被压缩,这样每个时钟周期只需处理一个鼠标移动事件。
事件阶段结束后,外部事件会暂停,重绘循环开始运行。首先是更新阶段,此时所有动画都会运行,根据下一帧预计可见的时间(可通过帧时钟获取)来计算新状态。这通常会涉及几何变化,而这些变化会驱动下一个阶段——布局阶段。如果控件的尺寸要求有任何变化,就会为控件层次结构计算新的布局(即确定所有控件的大小和位置)。然后是绘制阶段,在此阶段我们会重绘窗口中需要重绘的区域。
如果没有任何操作需要执行更新/布局/绘制阶段,我们会一直停留在事件阶段,因为在没有任何变化的情况下,我们不希望进行重绘。每个阶段都可以请求在后续阶段中进行进一步处理(例如,更新阶段会产生布局工作,而布局变化会导致重绘)。
驱动时钟的方式有多种,在最低层级,你可以使用 gdk_frame_clock_request_phase() 函数请求特定阶段,该函数会根据需要安排一次时钟跳动,以确保最终能到达所请求的阶段。不过,在实际应用中,大多数操作都在更高层级进行:
• 如果你要制作动画,可以使用 gtk_widget_add_tick_callback() 函数,这会使时钟定期跳动,并在更新阶段执行回调,直到你停止该跳动。
• 如果某些状态变化导致控件大小改变,你可以调用 gtk_widget_queue_resize() 函数,该函数会请求布局阶段,并将你的控件标记为需要重新布局。
• 如果某些状态变化导致需要重绘控件的某些区域,你可以使用常规的 gtk_widget_queue_draw() 系列函数。这些函数会请求绘制阶段,并将该区域标记为需要重绘。
CSS 层也会隐式触发许多此类操作(它会根据需要执行动画、调整大小和重绘)。
2.1.3 场景图
“绘制”窗口的第一步是 GTK 为窗口中的所有控件创建渲染节点(render nodes)。这些渲染节点组合成一个树状结构,你可以将其视为描述窗口内容的场景图。
渲染节点属于 GSK 层,其种类丰富多样,对应着在转换控件内容和应用 CSS 样式时可能需要用到的各类绘图基元。典型的例子包括文本节点、渐变节点、纹理节点或剪切节点。
过去,GTK 中的所有绘制操作都是通过 cairo 完成的。现在,通过使用 cairo 渲染节点,仍然可以用 cairo 来绘制自定义控件的内容。
GSK 渲染器(renderer)会接收这些渲染节点,将其转换为目标绘图 API 的渲染命令,并安排将生成的绘图结果与正确的绘面相关联。GSK 拥有针对 OpenGL、Vulkan 和 cairo 的渲染器。
2.1.4 层级绘制
在绘制阶段,GTK 会在顶级绘面上收到一个“render”信号。信号处理器会创建一个快照对象(用于辅助创建场景图),并调用 GtkWidget::snapshot() 虚函数,该函数会沿着控件层级结构向下传播。这使得每个控件都能在合适的位置和时机对自身内容进行快照,从而正确处理部分透明和控件重叠等情况。
在每个控件的快照过程中,GTK 会根据 CSS 盒模型自动处理 CSS 渲染。其绘制顺序为:先快照背景,再快照边框,接着是控件内容本身,最后是轮廓。
为避免生成场景图时的过度运算,GTK 会对渲染节点进行缓存。每个控件都会保留对自身渲染节点的引用(该节点又会引用子控件、孙控件等的渲染节点),并在绘制阶段复用该节点。使控件失效(通过调用 gtk_widget_queue_draw() )会丢弃缓存的渲染节点,迫使控件在下次需要生成快照时重新创建该节点。

通过网盘分享的知识:GTK 4 Reference Manual
链接: https://pan.baidu.com/s/57mKoejMZPrxs_wh3a7Kj9g
--来自百度网盘超级会员v7的分享

posted @ 2025-08-05 08:23  Hoijuon  阅读(16)  评论(0)    收藏  举报