מסלול תצוגה וחיי חלון¶
ציור והצגה הם פעולות שונות¶
נניח שהתוכנית כתבה את כל הפיקסלים של תמונה לבאפר. עדיין אין בכך הבטחה שהתמונה מוצגת. ייתכן שהמעבד הגרפי עוד עובד, שהבאפר ממתין להרכבה עם חלונות אחרים או שמנוע התצוגה עדיין סורק תמונה קודמת. כדי להבין זמן תגובה וקרעים בתמונה נפריד בין יצירת תוכן, מסירתו, הרכבתו והצגתו.
בתחתית המסלול מנהל התצוגה של הקרנל מספק ממשקים לזיכרון גרפי, הגשת עבודה ובקרת תצוגה. ממשקי DRM ו־KMS כוללים בין השאר עצמים שמייצגים מחברים, בקרי תצוגה ומישורי תמונה. תוכנה שמחזיקה את הסמכות המתאימה יכולה לקבוע אילו באפרים ייסרקו למסכים ואיזו תצורה פעילה.
בדרך כלל יישום שולחן עבודה אינו שולט ישירות במסך. הוא מוסר תוכן למרכיב התצוגה, שמחליט כיצד לשלב חלונות, לטפל בקלט ולתזמן הצגה. אם חלון מתאים להצגה ישירה, המרכיב עשוי להימנע מהרכבתו לתמונה נוספת. זו החלטת מימוש ותנאים, ולא הבטחה שכל חלון עובר תמיד אותו מספר העתקות.
פיקסל אינו מספיק כדי לתאר באפר¶
באפר מתואר באמצעות מידות, פורמט, מרחק בין שורות ולעיתים כמה מישורים ושינויי פריסה. כפל רוחב בגובה ובמספר בתים לפיקסל אינו מספיק לכל פורמט. בגרפיקה מודרנית קיימים גם דחיסה ופריסות אריחים שמצריכות תיאור נוסף.
נשתף באפר בין רכיבים רק כאשר הם מסכימים על פורמט ויכולת גישה. שימוש במתאר שיתוף אינו אומר שהבאפר מוכן לקריאה מיד. מנגנוני סנכרון, כמו גדרות השלמה, מתארים מתי עבודה קודמת הסתיימה. ניהול החיים של המתאר וניהול השלמת הציור הם שני חוזים שונים.
חיבור לשרת והכרה ביכולות¶
נכתוב לקוח קטן שמתחבר למושב תצוגה קיים ומדפיס את הממשקים שהשרת מפרסם. הקוד משתמש בספריית הלקוח של ויילנד בשפת סי כדי להראות את ממשק הפרוטוקול עצמו. הוא אינו יוצר חלון ואינו דורש סמכות על התקן תצוגה.
#include <wayland-client.h>
#include <stdint.h>
#include <stdio.h>
static void global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
printf("add name=%u interface=%s version=%u\n", name, interface, version);
}
static void removed(void *data, struct wl_registry *registry, uint32_t name)
{
printf("remove name=%u\n", name);
}
static const struct wl_registry_listener listener = {
.global = global,
.global_remove = removed,
};
int main(void)
{
struct wl_display *display = wl_display_connect(NULL);
struct wl_registry *registry;
if (!display) {
perror("connect");
return 1;
}
registry = wl_display_get_registry(display);
if (!registry || wl_registry_add_listener(registry, &listener, NULL) < 0) {
wl_display_disconnect(display);
return 1;
}
if (wl_display_roundtrip(display) < 0) {
wl_registry_destroy(registry);
wl_display_disconnect(display);
return 1;
}
wl_registry_destroy(registry);
wl_display_disconnect(display);
return 0;
}
נבנה בעזרת חבילת הפיתוח של הספרייה ונריץ מתוך המושב הגרפי. כשל חיבור במכונה ללא שרת ויילנד אינו תקלה במהדר.
cc registry.c -o registry $(pkg-config --cflags --libs wayland-client)
WAYLAND_DEBUG=client ./registry
נבחין בין גרסת ממשק שפורסמה לגרסה שהלקוח יודע להשתמש בה. לקוח קושר את הגרסה שהוא והשרת תומכים בה, ואינו שולח בקשה חדשה רק מפני שהשם מוכר לו. שמות העצמים בפרוטוקול הם מקומיים לחיבור, והפסקת החיבור מסיימת את המושב הזה.
חיי משטח וחיי הבאפר¶
ליצירת חלון נדרשים משטח ותפקיד מתאים, בדרך כלל דרך פרוטוקול המעטפת של שולחן העבודה. הלקוח מקבל אירועי תצורה ומאשר את התצורה המתאימה לפני מסירת תוכן לפי חוזה הפרוטוקול. שינוי גודל אינו רק הקצאת מערך חדש; הוא תיאום בין רצון הלקוח, החלטת מנהל החלונות והבאפר שנמסר.
לאחר כתיבת הפיקסלים הלקוח מצרף באפר, מסמן אזור שנפגע ומבצע התחייבות של מצב המשטח. עד אז חלק מהשינויים הם מצב ממתין. השרת רשאי להשתמש בתוכן עד שהוא מודיע שהבאפר שוחרר. שינוי מוקדם של התוכן עלול לגרום לתמונה פגומה גם אם אין שום מירוץ בין תהליכונים בתוך הלקוח.
אירוע מסגרת משמש לתזמון יצירת תוכן נוסף. הוא אינו זהה לאירוע שחרור באפר ואינו הוכחה שהתמונה הוצגה פיזית ברגע מסוים. למדידת הצגה נדרש פרוטוקול משוב מתאים ותמיכה בו. ערבוב האירועים יוצר באגים של שימוש חוזר מוקדם או מדידות זמן תגובה שגויות.
מודל של מאגר באפרים¶
נריץ מודל קטן שמייצג שני באפרים. ההודעה על מסגרת נוספת אינה משחררת אותם. רק אירוע שחרור מפורש משנה את הבעלות. הדוגמה היא מודל מצב, ולא מימוש פרוטוקול תצוגה.
#[derive(Clone, Copy, Debug, PartialEq)]
enum Owner { Client, Compositor }
struct Pool([Owner; 2]);
impl Pool {
fn submit(&mut self) -> Option<usize> {
let index = self.0.iter().position(|owner| *owner == Owner::Client)?;
self.0[index] = Owner::Compositor;
Some(index)
}
fn release(&mut self, index: usize) -> Result<(), &'static str> {
let owner = self.0.get_mut(index).ok_or("invalid buffer")?;
if *owner != Owner::Compositor { return Err("duplicate release"); }
*owner = Owner::Client;
Ok(())
}
}
fn main() {
let mut pool = Pool([Owner::Client; 2]);
let first = pool.submit().unwrap();
let second = pool.submit().unwrap();
assert!(pool.submit().is_none());
println!("frame callback alone does not change ownership");
assert!(pool.submit().is_none());
pool.release(first).unwrap();
assert_eq!(pool.submit(), Some(first));
pool.release(second).unwrap();
}
לניסוי אמיתי נריץ את לקוח הדוגמה weston-simple-shm, אם חבילת ההדגמות של ההפצה מספקת אותו, עם משתנה המעקב של הפרוטוקול. נזהה את הצירוף, ההתחייבות, בקשת המסגרת והשחרור. נעקוב אחר מזהה באפר אחד ונבדוק אם התזמון שנצפה תואם להנחות המודל. התנהגות של הרצה אחת אינה מבטיחה סדר שאין בפרוטוקול.
הקשר למערכת חלונות ותיקה¶
במערכת איקס־אחת־עשרה יש שרת, משאבים ופרוטוקול שונים, וקיימות הרחבות להרכבה ולהעברת באפרים. יישומים כאלה יכולים לפעול גם דרך שכבת תאימות על שולחן עבודה מבוסס ויילנד. לכן שם סביבת שולחן העבודה אינו מוכיח איזה פרוטוקול משתמש כל חלון.
בחקירת תקלה נזהה תחילה את המסלול האמיתי: באיזה שרת משתמש הלקוח, מי מצייר, מי מחזיק בבאפר ומי מציג אותו. רק אחר כך נבחר כלי מדידה. שימוש בזיכרון משותף, עיבוד גרפי והצגה ישירה הם חלופות במסלול, ולא מילים נרדפות.
להרחבה ניעזר במפרט פרוטוקול התצוגה ובממשקי הגרפיקה למרחב משתמש.