Some History of On Screen Graphics

นั่งเขียน RuneKit บน OS ต่างๆ แล้วต้องไปจับ API low level ของ display server พอสมควร เลยนั่งอ่านเพิ่มแล้วรู้สึกว่าภาพบนจอคอมพิวเตอร์ที่เราใช้มันแทบจะคือต้องให้ดาวเรียงตัวกันถึงจะใช้งานได้เลยนะ แต่มันกลับ reliable มากๆ

Windows 98

สมัยก่อนการ capture หน้าจอใน Windows XP หรือเก่ากว่า ถ้ามีโปรแกรมซ้อนหน้าต่างอยู่จะสังเกตว่า capture แล้วจะได้ภาพหน้าต่างที่ทับกันอยู่ด้วย เหมือนกับที่เราเห็นบนหน้าจอ รวมถึงโปรแกรมอัดวิดีโอต่างๆ ก็จะเห็นว่าไม่สามารถทำ window capture ได้เหมือนระบบสมัยใหม่

ที่เป็นแบบนี้เพราะใน OS เก่าๆ นั้นโปรแกรมทุกตัวจะวาดภาพที่เราเห็นอยู่บนหน้าจอไปยังภาพเดียวกัน เมื่อเรา capture หน้าจอก็คือการเอาภาพนี้ออกมาเป็นไฟล์กราฟฟิคแล้ว crop ให้พอดีกับขนาดของหน้าต่างที่เราต้องการเท่านั้น ไม่มีภาพหน้าต่างส่วนอื่นๆ อีก

ปัญหาของการวาดเป็นภาพเดียวคือในกรณีที่มี 2 หน้าต่างวางซ้อนทับกันอยู่ เมื่อเราย้ายหน้าต่างด้านบนหลบไปแล้วเราจะไม่ทราบว่าภาพที่ถูกบังอยู่เป็นภาพอะไร ระบบก็ต้องส่งสัญญาณขอให้โปรแกรมที่อยู่ด้านล่างนั้นวาดภาพขึ้นมาใหม่ด้วย รวมถึงหน้าต่างด้านบนก็ต้องวาดที่ตำแหน่งใหม่เช่นกัน เราจึงจะเห็นว่าการย้ายหรือปรับขนาดหน้าต่างใน OS รุ่นเก่านั้นจะมีอยู่สองประเภทคือ

  • แบบเร็วจะเห็นแต่กรอบตอนขณะที่ลาก แล้วจึงค่อยปรากฏภาพหน้าต่างเมื่อปล่อยเมาส์
  • แบบช้าคือบอกให้ทั้ง 2 หน้าต่างวาดภาพใหม่ในขณะที่ลากด้วย ซึ่งอาจจะเห็นอาการกระตุก

ถ้าหากระบบส่งสัญญาณให้หน้าต่างด้านหลังวาดใหม่แล้วแต่โปรแกรมนั้นค้างไม่ยอมวาดใหม่ ระบบก็จะค้างภาพหน้าจอพิกเซลนั้นไว้ จึงเป็นสาเหตุที่ทำให้เราวาดภาพทับโปรแกรมที่ค้างได้

อีกปัญหาหนึ่งของระบบสมัยก่อนคือ video ต่างๆ ที่เราไม่สามารถใช้ print screen จับภาพได้ หรือ game ก็ตาม และถ้าซวยใหญ่ก็คือวิดีโอไม่เล่นกลายเป็นสีเขียวๆ แบบนี้

เหตุผลก็คือคอมพิวเตอร์มักจะไม่แรงพอที่จะถอดรหัส video บน CPU ได้ หรือถึงจะแรงพอแต่ CPU ก็จะ 100% (ลองเปิด YouTube 4K ดูตอนนี้ก็ได้) ซึ่งคอมก็จะช้าทำอย่างอื่นไม่ได้อีก ดังนั้นการจะถอดรหัสวิดีโอต้องไปทำในชิพเฉพาะเช่นการ์ดจอแทน หลังจากถอดแล้วภาพก็จะแสดงผลบนหน้าจอทันทีไม่ผ่านเข้าไปในแรมที่พักภาพอีก เราจึงไม่สามารถ capture ภาพออกมาได้ รวมถึงโปรแกรมเล่นวิดีโอเองก็จะเห็นว่าไม่มีตัวไหนในสมัยนั้นที่เอา video control ไปวางทับภาพ

แต่การ์ดจอรู้ได้ยังไงว่าต้องวาง video ไว้ตรงไหน? โปรแกรมที่จะเล่น video นอกจากส่งวิดีโอไปแล้วยังต้องบอกตำแหน่งที่ต้องการวางภาพด้วย ปัญหาก็คือถ้ามีหน้าต่างวางซ้อนวิดีโออยู่ก็จะถูกวิดีโอแสดงทับ

วิธีแก้ไขก็พื้นๆ คือใช้ green screen อย่างที่เห็น แล้วบอกว่าที่ตำแหน่งนี้ถ้ามีสีเขียวขอให้แสดงเป็นภาพวิดีโอ (หรืออาจจะเป็นสีอื่นๆ)

X11

ใน Linux หรือ Unix ใดๆ นั้น เนื่องจากระบบออกแบบมาให้ใช้งานหลาย user พร้อมๆ กันจึงไม่สามารถให้โปรแกรมวาดภาพหน้าจอเองได้เพื่อความปลอดภัย การทำงานจึงเป็นรูปแบบของ client-server โดยที่โปรแกรม X Server จะควบคุมหน้าจอ แล้วโปรแกรมอื่นๆ เชื่อมต่อเข้ามาใช้งาน

การวาดภาพต่างๆ บนหน้าจอบน X server นั้นโปรแกรมต่างๆ ไม่มีสิทธิ์เข้าถึงหน้าจอจึงจะต้องส่งคำสั่งวาดมาที่ X server แล้ว X server จึงจะวาดให้ โดยโปรโตคอลที่ใช้งานก็เป็นรูปแบบ binary request-response ก็ไม่แน่ว่าถ้ามันเพิ่งคิดค้นมาในยุคนี้มันอาจจะเป็น gRPC ไปแล้วก็ได้เหมือนที่ Docker เป็น HTTP over Unix socket

ข้อดีอย่างหนึ่งของการที่มันเป็น client server ก็คือทั้งสองฝั่งไม่จำเป็นต้องอยู่บนเครื่องเดียวกันก็ได้ เรียกว่า X forwarding คือเราสามารถ ssh ไปอีกเครื่องหนึ่งและ forward X socket บนเครื่องไปด้วย แล้วรันโปรแกรมบนเครื่องนั้นก็จะปรากฏภาพหน้าจอบนเครื่องของเรา อย่างตัวอย่างในภาพคือ X server ที่รันใน Windows 7 แต่ว่าแสดงหน้าจอของโปรแกรมบน Unix

ทั้งนี้เนื่องจาก X protocol นั้นไม่ได้ออกแบบมาให้ใช้งานข้ามเครื่องกันนอกเหนือจากภายใน LAN เดียวกันจึงยังจำเป็นต้องใช้โปรโตคอล remote desktop ต่างๆ ที่มีการบีบอัดสำหรับการใช้งานผ่านอินเทอร์เน็ต แต่จากที่เห็นในภาพด้านบนก็จะเห็นว่าบน X forwarding เราเห็นหน้าต่างเป็นหน้าต่างจริงๆ สามารถ minimize แยกกันได้อย่างอิสระต่างกับ remote desktop ที่เห็นเป็นภาพหน้าจอไม่ใช่แยกหน้าต่างกัน

Quartz

ตอนที่ Apple จะสร้าง OS X ในรุ่นแรกๆ ก็ใช้ X11 แต่ว่า Apple อยากได้ระบบกราฟฟิคแบบล้ำๆ ก็เลยเลือกที่จะทำระบบขึ้นมาใหม่เองเรียกว่า Quartz

ไอเดียของ Quartz อยู่ที่ว่าโปรแกรมไม่เข้ามาวาดบนหน้าจอเองแล้ว แต่ระบบจะมีพื้นที่วาดส่วนตัวให้แต่ละแอพวาดโดยใช้ Quartz 2D หรือ OpenGL แล้วโปรแกรม Quartz Compositor จะ copy ภาพของแต่ละแอพมารวมกันเป็นรูปสุดท้าย

ดังนั้นมันจึงแก้ปัญหาของระบบวาดลงหน้าจอแบบเก่าได้ทุกข้อ !!

  • เราสามารถแคปจอหน้าต่างเดียวได้แล้ว เพียงแค่ไปขโมยภาพของโปรแกรมนั้นๆ ออกมา
  • เวลาลากหน้าจอไม่มีการกระตุกใดๆ ทั้งสิ้นเพราะ Quartz Compositor แค่เปลี่ยนที่ paste รูปให้ถูกจุด

ข้อเสียคือมันต้องก๊อปรูปไปมาหลายรอบ เปลือง CPU แทน ก็กระตุกอยู่ดี

ใน OS X 10.2 Quartz Compositor ย้ายไปรันบน OpenGL เรียกว่า Quartz Extreme ทำให้กระบวนการ copy ต่างๆ ลื่นไหลไม่มีสะดุด ไม่กิน CPU เพราะการ์ดจอมันสร้างมาเพื่องานนี้โดยเฉพาะ

บน OS X 10.4 ระบบวาดหน้าจอ Quartz 2D ย้ายมารันบนการ์ดจอเช่นเดียวกัน ทำให้ระบบกราฟฟิคของแมคใช้การ์ดจอเร่งความเร็วได้ทุกส่วน

Windows Vista

หลังจาก Apple มี Quartz ในปี 2001 แล้ว ในปี 2007 Microsoft ก็หันมาใช้ composite บ้างใน Windows Vista ที่ผู้ใช้งานอาจจะรู้จักกันในชื่อ Aero โดยโปรแกรมที่ทำหน้าที่แทน Quartz Extreme บน Windows เรียกว่า Desktop Window Manager (DWM)

ลูกเล่นต่างๆ ใน Aero ที่เราเห็นนั้นก็ทำได้เพราะ Compositing ทั้งนั้น เช่น

  • กรอบหน้าต่างโปร่งใส (ใช้การ์ดจอเบลอภาพ)
  • Aero flip (เอาภาพหน้าจอมาหมุน)
  • Thumbnail บน Taskbar เวลาชี้หน้าต่าง
  • Alt-Tab แล้วมี Thumbnail ของโปรแกรม
  • Fade หน้าต่างออกเวลาทำ Aero peek

จุดที่น่าสนใจคือในยุคนี้เกมเริ่มมีโหมด Borderless Fullscreen ซึ่งหลายๆ คนชอบใช้งาน ในโหมดนี้ภาพของเกมจะวาดนอกหน้าจอเหมือนโปรแกรมอื่นๆ แล้ว DWM ค่อยคัดลอกมาแสดงผลบนหน้าจอ ทำให้สามารถสลับโปรแกรมได้อย่างรวดเร็ว

ข้อเสียคือเนื่องจาก DWM เป็นคนกำหนดการตั้งค่าของหน้าจอทำให้เกมไม่สามารถปรับค่าได้ เช่น Resolution, Refresh rate และแม้เกมจะรันเร็วแค่ไหนแต่จะถูกจำกัดโดยเฟรมเรตของ DWM แทน ซึ่งจะต่างกับ mode full screen จริงๆ ที่ตัวเกมสามารถควบคุมทั้งหน้าจอได้เลย สามารถกำหนดอะไรก็ได้ แต่พอเรา tab กลับมาที่ desktop ปกติแล้วระบบจะต้องเสียเวลาปรับ setting ต่างๆ กลับให้เหมือนเดิม

Compiz

แค่หน้าต่างโปร่งใสยังถือว่าเด็กน้อย เมื่อ Composite มาถึง X11 ในปีเดียวกัน

Compiz น่าจะเรียกได้ว่าสุดยอดแห่งอาหารตาชนิดที่ว่าคนใช้ OS อื่นๆ ยังต้องยอมย้ายมา Linux เพื่อจะลองเล่น ไม่ว่าจะเป็นหน้าต่างพับเป็นจรวดตอน minimize, โย้เย้เป็นเจลลี่ตอนย้าย ไปจนถึง Desktop หมุนเป็นลูกบาศก์

Welcome to the future with GPU

นี่คือเหตุผลที่ชอบศึกษา OS มากๆ เพราะมันอธิบายปรากฏการณ์ในชีวิตประจำวันได้ดีมากๆ

สมัย Windows Vista ออกมาแล้วบอกว่าพิมพ์งาน Word ต้องซื้อการ์ดจอ ตอนนั้นก็ว่าบ้า กินสเปคเครื่องโคตรๆ แต่มันไม่มีใครบอกเลยนะว่ามันคือจุดจบของ Fraps แล้วที่เราจะสามารถอัดหน้าจอด้วยโปรแกรมอะไรก็ได้

ก็ไม่แน่ว่าในอนาคตพิมพ์สูตรใน Excel อาจจะต้องอัพการ์ดจออีกแล้วนะ

Trying out Qt

ปกติเวลาลองของใหม่ๆ จะลงในเฟสอยู่เรื่อยๆ แต่จะตั้ง friend only ไว้เพราะว่าเดี๋ยวนี้วงการนี้ดราม่าเยอะ ถ้าจะกด public หรือลง blog ต้องมานั่ง fact check พอสมควรก่อนถึงจะออกได้

ปีนี้เนื่องจาก Facebook ปรับ UI ใหม่ให้น่ารำคาญขึ้น ก็คิดว่าน่าจะถึงเวลาเลิกแล้วกลับมาเขียนในบล็อค ก็เลยคิดว่าจะลองดูใหม่คือเอา content แนวเดิม formatting แย่ๆ แบบเดิมแต่เขียนลงบล็อคดูบ้าง

สำหรับคนที่อาจจะไม่เคยได้อ่าน content พวกนั้น, ปกติจะ:

  • เป็น first impression มากๆ คือไม่ได้ลึกและบางทีที่เชียร์ผ่านไปอีกสองวันก็ไม่เชียร์แล้ว แต่ไม่ได้กลับมาอัพเดตนะ
  • rapid fire จัดๆ เน้น bullet point คิดอะไรออก brain dump ลงไป
  • เขียนแบบคิดว่าคนอ่านเข้าใจใน topic นั้นแล้ว
  • ไม่ได้ proofread

ช่วงนี้โคลนแอพ Alt1 มารันใน Linux อยู่ (Alt1 เป็นโปรแกรมช่วยเล่นเกม RuneScape โดยมันจะ stream หน้าจอให้ web page แล้ว web page ทำ OCR) ก็พบว่าถึงโค้ดมันจะค่อนข้างแย่ แต่ไอเดียรอบๆ มันฉลาดดีแล้วทำให้ port ง่ายมาก เพราะ OCR ทั้งหมดไปรันใน JavaScript หมด

Goal คิดว่าอยากได้ Linux + Mac ก็เลยเลือก Qt ก่อนหน้านี้เคยใช้ Gtk กับ wxWidget มาแล้ว คิดว่า Gtk ไม่น่าจะรันในแมคได้ดี ส่วน wxWidget คิดว่า API จะไม่พอใช้

ปัญหาแรกที่เจอคือคนเขียน cefpython ไม่ได้อัพเดตต่อ คือไม่มีตังให้ก็ไม่ทำ ซึ่งก็ถูกของเค้าล่ะ ก็เลยต้องเปลี่ยน engine ไปใช้ QtWebEngine ซึ่งมันไม่ใช่ CEF แต่มันเป็น Embedded Chromium เหมือนกัน

พอเอา QtWebEngine มารันได้ ถัดมาที่ต้องทำคือจำลอง Alt1 API ซึ่งบน Alt1 มันจะมี window.alt1 ที่มีเมธอดต่างๆ เช่น getRegion เพื่อที่จะ transfer capture มายัง JavaScript ปัญหาก็คือ CEF3 ไม่มีท่าที่สามารถ provide native object ให้ web ได้แล้ว รวมถึง QtWebEngine ด้วย เหตุผลเพราะว่า Blink มันรันเป็น multiprocess แล้วทำให้ไม่สามารถแชร์ของข้าม process ไปได้ ก็จะมีแต่ Async API ให้เท่านั้น

ยังสงสัยอยู่ว่า Alt1 จะทำยังไง หรือจะไม่อัพเกรด CEF?

ก็นึกอยู่คืนหนึ่งว่าทำยังไงดี QtWebEngine ก็ไม่มีแนะนำ จนกระทั่งไปอ่าน docs ของ CEF แล้วมันเขียนว่าให้ทำ synchronous XHR ซึ่งทีแรกก็ลังเลเพราะมันจะ deprecate แล้ว แต่ก็ไม่มีทางเลือกเลยต้องทำ

พอ Get รูปได้รอบแรกดีใจมาก แต่ปัญหาคือพอไปลองกับ app จริงๆ แล้วมัน poll เอารูปค่อนข้างถี่มากๆ แล้ว encoder algorithm มันเขียนด้วย Python ซึ่งช้ามาก (ฝั่ง JS expect รูปเป็นแบบ color channel BGRA ซึ่งต้องสลับเอง) เลยลองไปเขียน Cython ดู พบว่าเขียนง่ายมากๆ และออกเป็น C จริงๆ มีให้ดูด้วย แถมเร็วมากๆ

(ตอนหลังมาพบว่ามัน split() แล้ว merge() เพื่อเรียง channel ใหม่ได้ เร็วกว่า cython ที่เขียนอีก…)

พอรันได้แล้วก็เลยลองหาวิธีต่อกับเกมจริงๆ บ้าง ตอน capture เกมพบว่า Qt สามารถเอา WId ให้มันแล้วมันจะ capture หน้าต่างได้ ซึ่งบน Linux ต้องใช้ Xlib หา X Window ID ออกมาแล้วส่งให้ ที่ Qt ทำคือมันจะแคปจอทั้งหมด แล้ว crop ให้เหลือแต่ด้านในของ Window นั้น ซึ่งก็ง่ายดีแต่แปลว่าห้ามมีอะไรบังหน้าต่าง

ไปต่อ Xlib ตรงๆ นี่ปวดหัวมาก ทำ segfault ไปหลายรอบ ยังขี้เกียจบ่นอยู่เพราะเพิ่งมารู้ว่าควรจะต่อกับ XCB แทน เดี๋ยวจะไปเขียนใหม่


ทีนี้ที่รู้สึกเจ๋งมากคือลอง port ไปรันใน Mac ดูบ้าง ส่วนที่ต้องทำก็คือ

  • หาวิธี list game window บน Mac
  • บน Mac WId คือ Window pointer ของ Quartz ซึ่งไม่มี representation ใน Python แปลว่าใช้ท่า capture เดิมไม่ได้เลย

ก็ลองเขียนต่อกับ Quartz ดูแล้วพบว่า API ของ Mac ใช้งานง่ายกว่าเยอะ docs คุณภาพดีกว่ามาก (ของ Python นี่ไม่มี API docs ต้องนั่งแกะซอร์สเทียบกับ protocol definition) งมอยู่ประมาณ 4 ชั่วโมงก็ใช้ได้ทั้ง 2 feature แถม capture window ที่โดนบังได้ด้วยเพราะ Mac มี API ที่ระบุหน้าต่างแล้วมันเอามาให้ได้เลย

หลังจากใช้งานพื้นฐานได้ ก็ hook กับ Accessibility API อีกหลายชั่วโมงก็สามารถจับ game resize, game activity (ถ้าไม่กดเมาส์นานๆ เกมจะ auto logout) และ hotkey ได้ เป็นอันสมบูรณ์ก่อน Linux

แต่ว่า port Mac จริงๆ ก็ยังมีปัญหาอยู่บ้าง นั่นคือ

  1. Capture API ใน Mac คืนกรอบหน้าต่างมาด้วย ถึงจะบอกว่าไม่เอามันจะไม่ให้เงามาแต่ยังให้กรอบอยู่ดี ส่งผลให้ API ที่ใช้ตำแหน่งต่างๆ เลื่อนหมด
  2. Mac ใช้ HiDPI (Retina display) เป็นหลักและมันทำให้พิกัดต่างๆ ปวดหัวมากว่าอันนี้เป็น screen size หรือ actual size และตัวเกมเองไม่ได้ support Retina ด้วย

ทีรู้สึกว่า Qt มันเจ๋งมากๆ คือด้านบนที่เล่ามามีแค่ port ส่วนที่ต่อกับเกมซึ่ง Qt ไม่มี API ที่ไปเชื่อมต่อกับหน้าต่างแอพอื่นๆ สักเท่าไร แต่ในส่วน browser แล้วใช้โค้ดเดียวกันรันได้ทุก platform เหมือนกันเป๊ะ คือ Qt abstract platform ให้ได้ดีมากๆ บวกกับ Python ด้วย

เดี๋ยวถึงตอนจะทำ embed window, custom decoration ค่อยมาดูว่าจะรอดมั้ย

Go look at my code >> https://github.com/whs/runekit


สุดท้ายซ่าอยากลอง เลยลอง Port ไปรันใน Windows ดูบ้าง

  • WId บน Windows ใช้ HWND ของ Windows ได้เลย
  • ฟังค์ชั่น capture ของ Qt รันบน HWND แล้วได้ window capture !! สุดยอดมาก
  • API Windows เท่าที่ดูยังงงๆ ว่าอะไรเรียกว่าอะไร แต่ถ้าเจอแล้วก็เรียกใช้ได้ง่ายกว่า Xlib บน Linux อยู่ดี…
Real Alt1 vs clone

เขียน project นี้แล้วก็เริ่มรู้สึกเหมือนตอนเด็กกว่านี้ว่าต่อไปนี้เราทำ Qt UI ดีกว่า เขียนทีเดียวรันได้ทุกที่ แถม native ไม่ต้องใช้แล้ว React Native หรือ Flutter

แต่ปัญหาใหญ่ของ Qt คือมันเป็นบริษัทเขียนมันถึงจะออกมาได้ดีขนาดนี้ และบริษัทเองก็ให้ใช้งานภายใต้ LGPL ฟรี ปัญหาคือพอเป็น mobile app แล้ว โดยเฉพาะ iOS จะค่อนข้างยากที่จะทำให้ user สามารถ replace LGPL library เองได้ ถ้าจะซื้อรุ่นเสียเงินก็ราคาไม่ได้ถูกเลย โดยเฉพาะเมื่อเทียบกับ React Native หรือ Flutter ที่มันใช้ฟรี

แต่ทั้งสอง project เอง ก็ยังรู้สึกว่าเป็น corporate back อยู่ประมาณหนึ่งไม่ได้มี community มาทำแทนกันได้หมด ถึงจะมีคนใช้มากแต่ถ้า sponsor หลักไม่ทำก็อาจจะ innovate ไม่ทันเทคโนโลยีอื่นได้ ก็เป็นปัญหา classic ของ open source ที่ corporate ใช้ว่า take อย่างเดียวแต่ไม่ give