一:概述
? ? ? ? 前面在介紹了顯示模式設置(分辨率,刷新率)之后,本文繼續分析下,顯示區域的繪制,詳細看看虛擬機的畫面是如何由QXL顯卡繪制出來的。?
二:相關數據結構介紹?
struct qxl_monitors_config {uint16_t count; // 當前啟用的顯示器數量(head 數量)uint16_t max_allowed; // 驅動允許的最大顯示器數量,如果為 0,表示沒有固定限制,由驅動動態決定struct qxl_head heads[]; // 每個顯示器的配置數組(實際大小為 count)
};
struct qxl_head {uint32_t id; // 顯示頭(顯示器)IDuint32_t surface_id; // 綁定的 QXLSurface IDuint32_t width; // 顯示區域的寬度(像素)uint32_t height; // 顯示區域的高度(像素)uint32_t x; // 在綁定 surface 上的 X 偏移(起始坐標)uint32_t y; // 在綁定 surface 上的 Y 偏移uint32_t flags; // 標志位,用于額外配置(比如是否啟用、旋轉等)
};
struct qxl_surface_id {uint32_t surface_id; // 表示 QXL 表面(Surface)的唯一標識符。每個表面都會有一個唯一的 ID,用于區分和識別不同的表面。
};
struct qxl_image {struct qxl_image_descriptor descriptor; // 圖像的描述符,包含圖像的基本信息(例如圖像類型、大小等)union { /* 可變長度的數據,根據圖像類型選擇使用 */struct qxl_bitmap bitmap; // 圖像數據,作為位圖(bitmap)表示struct qxl_encoder_data quic; // 圖像數據,作為 QUIC 編碼數據表示(用于高效傳輸)struct qxl_surface_id surface_image; // 圖像數據,作為表面圖像的 ID 表示(指向一個渲染表面)} u;
};
struct qxl_image_descriptor {uint64_t id; // 圖像的唯一標識符,用于標識該圖像在系統中的位置uint8_t type; // 圖像類型,用于指示該圖像的具體格式或用途(例如位圖、編碼圖像等)uint8_t flags; // 圖像的標志位,通常用于表示圖像的特定屬性(例如是否是透明圖像等)uint32_t width; // 圖像的寬度,以像素為單位uint32_t height; // 圖像的高度,以像素為單位
};
struct qxl_encoder_data {uint32_t data_size; // 編碼數據的大小,單位是字節。uint8_t data[]; // 可變長度的編碼數據,存儲編碼后的圖像或圖形數據。
};
struct qxl_palette {uint64_t unique; // 唯一標識符,用于區分不同的調色板。每個調色板都會有一個唯一的 ID。uint16_t num_ents; // 調色板條目的數量,即調色板中包含的顏色數。uint32_t ents[]; // 可變長度數組,存儲調色板中的顏色值。每個條目代表一個顏色,通常是32位顏色值(例如 RGBA)。
};
struct qxl_bitmap {uint8_t format; // 位圖的格式,表示圖像數據的顏色深度或編碼方式。uint8_t flags; // 位圖的標志字段,指示位圖的附加屬性或狀態。uint32_t x; // 位圖的 X 坐標,表示位圖左上角的位置。uint32_t y; // 位圖的 Y 坐標,表示位圖左上角的位置。uint32_t stride; // 位圖每行的字節數,即圖像數據的跨度。用于訪問像素數據時的步長。QXLPHYSICAL palette; // 調色板的物理地址,用于與調色板相關的數據。通常這是指向 `qxl_palette` 結構體的指針。QXLPHYSICAL data; // 圖像數據的物理地址。指向存儲位圖像素數據的內存區域。
};
struct qxl_surface {uint32_t format; // 圖形表面的格式(例如 RGBA,BGRA 等)uint32_t width; // 表面的寬度uint32_t height; // 表面的高度int32_t stride; // 行跨度,表示表面每行數據占用的字節數QXLPHYSICAL data; // 指向表面數據的物理地址
};
struct qxl_surface_cmd {union qxl_release_info release_info; // 用于釋放的額外信息uint32_t surface_id; // 表面ID,標識一個具體的圖形表面uint8_t type; // 命令類型,指示是創建表面還是銷毀表面uint32_t flags; // 命令標志,可能用于控制命令的行為union {struct qxl_surface surface_create; // 創建表面時的相關信息} u;
};
struct qxl_clip_rects {uint32_t num_rects; // 包含的矩形數量struct qxl_data_chunk chunk; // 包含矩形數據的內容
};
struct qxl_drawable {// 釋放信息,用于釋放渲染資源時的管理union qxl_release_info release_info;// 該可繪制對象使用的表面IDuint32_t surface_id;// 繪制效果類型,用于標識渲染操作的效果類型(例如:透明、混合等)uint8_t effect;// 繪制對象類型,定義該對象的類型(例如:填充、文本、復制等)uint8_t type;// 標記是否是自帶位圖數據,如果是,則會直接使用該位圖uint8_t self_bitmap;// 如果是自帶位圖,定義位圖的有效區域struct qxl_rect self_bitmap_area;// 可繪制對象的邊界框,定義了渲染的邊界范圍struct qxl_rect bbox;// 裁剪區域,定義了在渲染時應考慮的區域,超出該區域的部分將被裁剪掉struct qxl_clip clip;// 內存管理時間戳,用于標識該對象的渲染時間uint32_t mm_time;// 目標表面數組,最多支持三個目標表面int32_t surfaces_dest[3];// 每個目標表面的渲染區域矩形struct qxl_rect surfaces_rects[3];// 根據繪制類型選擇不同的繪制操作,這里使用聯合體來支持多種操作union {// 填充操作,例如使用顏色填充區域struct qxl_fill fill;// 不透明操作,可能表示不透明的填充區域struct qxl_opaque opaque;// 復制操作,將源區域復制到目標區域struct qxl_copy copy;// 透明操作,可能表示透明背景或區域struct qxl_transparent transparent;// alpha混合操作,支持透明度的混合效果struct qxl_alpha_blend alpha_blend;// 復制位圖操作,支持特定的位圖復制struct qxl_copy_bits copy_bits;// 復制操作的另一種形式struct qxl_copy blend;// 3元邏輯操作,用于圖像處理中基于位的像素操作struct qxl_rop_3 rop3;// 描邊操作,用于繪制路徑的邊框struct qxl_stroke stroke;// 文本渲染操作,用于繪制文本struct qxl_text text;// 黑色掩碼操作,支持黑色掩碼效果struct qxl_mask blackness;// 反轉掩碼操作,用于像素的反色效果struct qxl_mask invers;// 白色掩碼操作,支持白色掩碼效果struct qxl_mask whiteness;// 合成操作,用于合成多個圖像層struct qxl_composite composite;} u;
};
struct qxl_cursor_header {uint64_t unique; // 唯一標識符,標識一個光標uint16_t type; // 光標類型(例如:標準光標、自定義光標)uint16_t width; // 光標的寬度(以像素為單位)uint16_t height; // 光標的高度(以像素為單位)uint16_t hot_spot_x; // 熱點位置的 X 坐標(相對于光標左上角)uint16_t hot_spot_y; // 熱點位置的 Y 坐標(相對于光標左上角)
};
struct qxl_cursor {struct qxl_cursor_header header; // 光標的基本信息uint32_t data_size; // 光標數據的大小struct qxl_data_chunk chunk; // 包含光標數據的內容(例如:像素數據)
};
struct qxl_cursor_cmd {union qxl_release_info release_info; // 釋放信息,用于同步或更新uint8_t type; // 光標操作類型(例如:設置、移動、隱藏)union {struct { struct qxl_point_1_6 position; // 光標的新位置uint8_t visible; // 光標是否可見QXLPHYSICAL shape; // 光標形狀數據} set; // 設置光標操作struct { uint16_t length; // 光標軌跡的長度uint16_t frequency; // 光標軌跡的頻率} trail; // 設置光標軌跡操作struct qxl_point_1_6 position; // 光標的新位置(在移動或其他命令中使用)} u;uint8_t device_data[QXL_CURSOR_DEVICE_DATA_SIZE]; // 設備特定數據,大小為 128 字節
};
struct qxl_ram_header {uint32_t magic; // 魔術數字,用于驗證數據結構是否正確uint32_t int_pending; // 當前掛起的中斷uint32_t int_mask; // 中斷屏蔽uint8_t log_buf[QXL_LOG_BUF_SIZE]; // 日志緩沖區,用于記錄日志struct qxl_ring_header cmd_ring_hdr; // 命令環頭,用于管理命令隊列struct qxl_command cmd_ring[QXL_COMMAND_RING_SIZE]; // 命令環,用于存儲具體的命令struct qxl_ring_header cursor_ring_hdr; // 光標環頭,用于管理光標隊列struct qxl_command cursor_ring[QXL_CURSOR_RING_SIZE]; // 光標環,用于存儲光標命令struct qxl_ring_header release_ring_hdr; // 釋放環頭,用于管理釋放命令uint64_t release_ring[QXL_RELEASE_RING_SIZE]; // 釋放環,用于存儲釋放命令struct qxl_rect update_area; // 更新區域,表示圖形更新的區域/* appended for qxl-2 */uint32_t update_surface; // 更新的表面 ID,用于指定目標表面struct qxl_mem_slot mem_slot; // 內存槽,用于分配圖形內存struct qxl_surface_create create_surface; // 表面創建結構,表示新創建的圖形表面uint64_t flags; // 標志位,用于存儲相關的配置信息/* appended for qxl-4 *//* used by QXL_IO_MONITORS_CONFIG_ASYNC */QXLPHYSICAL monitors_config; // 顯示器配置,用于存儲顯示器相關的配置信息uint8_t guest_capabilities[64]; // 客戶端能力,存儲與虛擬機環境相關的信息
};
/* qxl-1 compat: append only */
struct qxl_rom {uint32_t magic; // 魔術數,用于驗證這是一個有效的 QXL ROMuint32_t id; // ROM 的唯一標識符uint32_t update_id; // 更新標識符,用于標記 ROM 版本或更新次數uint32_t compression_level; // 壓縮級別,可能指示 ROM 數據的壓縮程度uint32_t log_level; // 日志級別,控制 QXL 驅動的日志輸出詳細程度uint32_t mode; // 驅動的工作模式(qxl-1 特定)uint32_t modes_offset; // 顯示模式的偏移量,指向模式數據的位置uint32_t num_io_pages; // I/O 頁的數量,表示 ROM 中的 I/O 頁數量uint32_t pages_offset; // 頁面的偏移量,指向頁面數據的位置(qxl-1 特定)uint32_t draw_area_offset; // 繪制區域的偏移量,表示繪制區域在 ROM 中的位置(qxl-1 特定)uint32_t surface0_area_size; // 表面0區域的大小,用于描述第一個圖形表面的內存大小(qxl-1 特定,名為 draw_area_size)uint32_t ram_header_offset; // RAM 頭部的偏移量,指向 RAM 頭部數據的位置uint32_t mm_clock; // 內存時鐘頻率,控制 RAM 訪問的時序,影響性能/* qxl-2 特有字段 */uint32_t n_surfaces; // 表面數量,表示支持的圖形表面數量uint64_t flags; // 標志位,存儲驅動的配置特性uint8_t slots_start; // 起始槽位,內存槽位的起始位置uint8_t slots_end; // 結束槽位,內存槽位的結束位置uint8_t slot_gen_bits; // 槽生成位數,用于標識槽位的生成位uint8_t slot_id_bits; // 槽 ID 位數,表示槽位的唯一標識符的位數uint8_t slot_generation; // 槽代號,用于表示槽位的代次(內存管理、版本控制)/* qxl-4 特有字段 */uint8_t client_present; // 客戶端存在標志,指示客戶端是否存在uint8_t client_capabilities[58]; // 客戶端的能力位圖,描述客戶端支持的功能或特性uint32_t client_monitors_config_crc; // 客戶端顯示配置的 CRC 校驗和,用于驗證配置的有效性struct {uint16_t count; // 顯示器配置的數量uint16_t padding; // 填充,保持對齊struct qxl_urect heads[64]; // 顯示器配置,最多支持 64 個顯示器} client_monitors_config; // 客戶端監視器配置,描述顯示器的位置和尺寸
};
三:顯示區域更新介紹
? ? ? ? 下面這段代碼是用戶空間通過 ioctl 發來的更新顯示區域命令,這個代碼的主要邏輯如下:
? ? ? ? 1. 首先解析用戶空間輸入的更新區域,并檢查區域是否有效。
? ? ? ? 2. 然后根據用戶空間傳遞的GEM對象句柄,查找到顯存中的緩沖區對象。
? ? ? ? 3. 將緩沖區對象固定在顯存中,并映射到內核虛擬空間,加鎖。
? ? ? ? 4. 調用 qxl_io_update_area 更新指定的區域,這個函數的意思是給QXL 顯卡發送命令,并等待命令完成。
? ? ? ? 5. 更新完成后,解鎖,釋放相關資源。
int qxl_io_update_area(struct qxl_device *qdev, struct qxl_bo *surf,const struct qxl_rect *area)
{int surface_id;uint32_t surface_width, surface_height;int ret;if (!surf->hw_surf_alloc)DRM_ERROR("got io update area with no hw surface\n");if (surf->is_primary)surface_id = 0;elsesurface_id = surf->surface_id;surface_width = surf->surf.width;surface_height = surf->surf.height;if (area->left < 0 || area->top < 0 ||area->right > surface_width || area->bottom > surface_height)return -EINVAL;mutex_lock(&qdev->update_area_mutex);qdev->ram_header->update_area = *area;qdev->ram_header->update_surface = surface_id;ret = wait_for_io_cmd_user(qdev, 0, QXL_IO_UPDATE_AREA_ASYNC, true);mutex_unlock(&qdev->update_area_mutex);return ret;
}
四:dma_fence 介紹
? ? ? ? dma_fence 是 Linux 內核中用于同步GPU 或其他設備訪問共享內存(如 buffer object)的機制。 比如 CPU 將渲染任務交給GPU(命令提交),GPU可能幾毫秒后完成,所以此時就會建一個fence,意思是說別動這塊內存,GPU正在使用,等GPU完成后,這個“牌子”就會被撤掉,CPU就可以安全訪問了。
? ? ? ? 在qxl驅動中,qxl_release 表示一次命令提交,當用戶空間在命令提交時,qxl的?qxl_release_fence_buffer_objects 就會將一個buffer object 關聯上一個dma_fence, 這樣命令執行完成時,可以設置dma_fence 狀態,用于通知用戶空間了;下面是dma_fence 函數介紹和一個流程圖:
????????