1 คะแนน โดย GN⁺ 2024-04-07 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • UEFIRC คือกราฟิกไคลเอนต์ IRC ที่ทำงานใน สภาพแวดล้อมก่อนบูตของ UEFI ภายในเฟิร์มแวร์ของเมนบอร์ดก่อนที่ระบบปฏิบัติการจะเริ่มทำงาน แสดงให้เห็นว่าแม้ในสภาพแวดล้อมสำหรับบูตโหลดเดอร์ก็ยังสามารถสร้าง UI และความสามารถด้านเครือข่ายที่ใกล้เคียงแอปทั่วไปได้
  • การพัฒนานี้อาศัย ไดรเวอร์ NIC และสแตก TCP ที่ UEFI มีไว้สำหรับการบูตผ่านเครือข่าย และมี network backend vmnet สำหรับ QEMU ที่ทำให้การพัฒนาเป็นไปได้
  • จุดที่ยากที่สุดคือการจัดการโปรโตคอล TCP ของ UEFI ใน Rust โดยมีทั้งสถานะส่วนกลาง, callback แบบ reentrant, scatter-gather buffer และความสัมพันธ์ซับซ้อนระหว่าง event, token, handle และ protocol
  • GUI เป็นการนำ GUI toolkit ของ axle ที่เขียนด้วย Rust พร้อม TrueType renderer มาปรับให้ทำงานบน UEFI และยังมีการ ปรับปรุง libgui เพื่อรองรับอินพุตเมาส์, แถบเลื่อน และการเรนเดอร์ข้อความใน scroll view
  • ผลลัพธ์สุดท้ายใกล้เคียงกับโปรเจกต์มุกที่ทำอย่างประณีตมากกว่าจะเป็นไคลเอนต์ IRC สำหรับใช้งานจริง แต่ก็กลายเป็นเครื่องมือสำหรับบ่นเรื่อง UEFI TCP/IP stack ผ่าน IRC จากภายใน UEFI ได้

UEFIRC ทำอะไรได้บ้าง

  • UEFIRC คือ กราฟิกไคลเอนต์ IRC ที่รันบน UEFI
  • เขียนด้วย Rust และใช้ GUI toolkit กับ TrueType renderer ที่สร้างขึ้นสำหรับ axle user space
  • สามารถเชื่อมต่อกับ IRC server เพื่อแชตและอ่านข้อความได้
  • ในการพัฒนาใช้ vmnet network backend สำหรับ QEMU

UEFI ในฐานะเวทีการทำงาน

  • บูตโหลดเดอร์ของระบบปฏิบัติการถูกโหลดขึ้นมาด้วยความช่วยเหลือจากเฟิร์มแวร์ที่เก็บอยู่ใน ROM ของเมนบอร์ด
  • ในอดีต BIOS มีข้อจำกัดหลายอย่าง จึงมีการสร้างมาตรฐาน UEFI ขึ้นมาเพื่อทดแทน
    • BIOS กำหนดให้บูตโหลดเดอร์ต้องเริ่มในโหมด 16-bit
    • ยังมีข้อกำหนดให้โหลดเดอร์ขั้นแรกต้องมีขนาดไม่เกิน 512 ไบต์
  • UEFI ทำให้บูตโหลดเดอร์เริ่มต้นในสภาพแวดล้อม 64-bit ตั้งแต่แรก พร้อมมี API สำหรับสลับความละเอียดจอแบบ VESA, จัดสรรหน่วยความจำ และเข้าถึง EFI filesystem
  • แม้จะก้าวหน้ากว่า BIOS มาก แต่ก็มีเสียงวิจารณ์ว่าออกแบบมาซับซ้อนเกินไป

นำความสามารถการบูตผ่านเครือข่ายมาใช้กับ IRC

  • บูตโหลดเดอร์บางตัวสามารถโหลด ระบบปฏิบัติการผ่านเครือข่าย แทนการใช้ local block device ได้
  • การรองรับกรณีใช้งานนี้ทำให้เฟิร์มแวร์ UEFI ต้องมี network stack อยู่ภายใน
    • ไดรเวอร์ NIC
    • implementation ของ TCP
    • API ให้แอปพลิเคชันที่ทำงานในสภาพแวดล้อมก่อนบูตเข้าถึงสแตกนี้ได้
  • และเพราะบูตโหลดเดอร์ไม่ได้จำเป็นต้องใช้เพื่อโหลดระบบปฏิบัติการเท่านั้น จึงสามารถรันไคลเอนต์ IRC ในสภาพแวดล้อมเดียวกันได้ด้วย

ความยากของการจัดการ UEFI TCP ใน Rust

  • ส่วนที่ยากที่สุดของโปรเจกต์คือการทำ UEFI TCP protocol client ใน Rust
  • โปรโตคอล TCP ของ UEFI ต้องการอายุข้อมูลและรูปแบบปฏิสัมพันธ์ที่อธิบายกับ Rust ได้ยาก
    • สถานะส่วนกลาง
    • callback แบบ reentrant
    • scatter-gather buffer
    • event, token, handle, protocol
  • มีการทดสอบโค้ด Rust อยู่หลายวันเพื่อกำจัดทั้ง memory leak และปัญหา use-after-free ใน TCP receive buffer

ความสับสนของ NOTIFY_SIGNAL และ NOTIFY_WAIT

  • API event ของ UEFI เป็นสิ่งที่ดูจากชื่อแล้วคาดเดาพฤติกรรมได้ยาก
  • หากกำหนด NOTIFY_SIGNAL callback จะถูกเรียกเมื่อเกิด event และการใช้ wait() จะกลายเป็นข้อผิดพลาด
  • หากกำหนด NOTIFY_WAIT แล้วเรียก wait() UEFI อาจเรียก callback หลายครั้งก่อน event จะเกิดขึ้น และเมื่อ event เกิด wait() จึงจะถูกปลดล็อก
  • ทั้งสองโหมดทำให้ callback ตัวเดียวกันมี ความหมายที่ต่างกันโดยสิ้นเชิง
    • NOTIFY_SIGNAL: event เกิดขึ้นแล้ว ถึงเวลาทำงานถัดไป
    • NOTIFY_WAIT: event ยังไม่เกิด ถึงเวลาช่วยผลักดันให้กระบวนการเดินหน้าต่อ
  • สุดท้ายมีการใช้ลูป NOTIFY_WAIT ร่วมกับ timer timeout สั้น ๆ เพื่อทำ asynchronous buffering ของข้อมูลแพ็กเก็ตรับเข้า

การรองรับเมาส์และเคอร์เซอร์

  • เมาส์ไม่ใช่สิ่งจำเป็นสำหรับไคลเอนต์ IRC แต่ช่วยให้แอปรู้สึกโต้ตอบได้มากขึ้น
  • ใช้ Simple Pointer Protocol ของ UEFI เพื่ออ่านการเคลื่อนที่และการกดปุ่มเมาส์ แล้วส่งข้อมูลตำแหน่งเคอร์เซอร์กลับไปยัง GUI
  • Simple Pointer Protocol ไม่รองรับ scroll wheel
    • ใน UEFIRC จึงต้องใช้ปุ่มลูกศรหรือใช้เคอร์เซอร์ลากแถบเลื่อนแทน
  • เนื่องจากเฟิร์มแวร์ UEFI มาตรฐานของ OVMF ไม่สามารถรับ event จากเมาส์ได้ จึงต้อง build เฟิร์มแวร์ UEFI แบบคัสตอมที่รวมไดรเวอร์และโปรโตคอลที่จำเป็น เช่น UsbMouseDxe
  • เพื่อให้ทดลอง UEFIRC บน QEMU ได้ จึงมีการ อัปโหลดเฟิร์มแวร์ UEFI ดังกล่าวไว้ในหน้า release ด้วย

การสเกลการเคลื่อนที่ของเมาส์

  • ไดรเวอร์เมาส์รายงาน การเปลี่ยนแปลงตำแหน่ง ไม่ใช่ตำแหน่งแบบสัมบูรณ์
  • ถ้าเพียงนำ delta_x, delta_y มาบวกตรง ๆ ด้วยการสเกลแบบเชิงเส้น จะให้ความรู้สึกหน่วง
  • ระบบปฏิบัติการมักใช้การสเกลที่ช่วยให้ทั้งการเคลื่อนที่เร็วและการปรับละเอียดทำได้พร้อมกัน
  • ในตัวอย่างการใช้งาน มีการนำผลรวมค่าสัมบูรณ์ของระยะเคลื่อนที่ไปคูณกับค่าที่ได้จาก log2() เพื่อเพิ่มระยะการเลื่อนของเคอร์เซอร์
  • เคอร์เซอร์ที่เคลื่อนที่แบบเชิงเส้นล้วน ๆ มักทำให้ทั้งสภาพแวดล้อมดูช้าและตอบสนองไม่ดี

การโมเดลข้อความ IRC

  • การโมเดลข้อความ IRC ค่อนข้างง่ายและทำได้อย่างสบาย
  • IRC ใช้ ฟอร์แมตบรรทัดแบบข้อความล้วน จึงแยกวิเคราะห์ได้ง่าย
  • แต่การขยายต่อเนื่องตลอดหลายสิบปีก็ทำให้ยังมีภาระจากสิ่งที่ถูกทำให้เป็นมาตรฐานเพียงบางส่วน

การใช้ libgui บน UEFI

  • GUI toolkit ของ axle ที่เขียนด้วย Rust ถูกเตรียมไว้พอสมควรแล้วสำหรับการใช้งานนอกบริบทของ axle จึงไม่ยากมากที่จะนำมารันบน UEFI
  • งานหลักคือการสร้าง implementation ของ AwmWindow ที่ใช้ได้ภายใน UEFI
  • จากนั้นก็สามารถใช้ความสามารถหลายอย่างของ libgui ได้ตามเดิม
    • การจัดการ event
    • การเรนเดอร์ฟอนต์
    • การ compositing เลเยอร์
    • การตกแต่ง view
    • คอมโพเนนต์ที่ซับซ้อนอย่าง scroll view

แถบเลื่อนและการเรนเดอร์ข้อความใน scroll view

  • libgui แบบ C ของ axle มีความสามารถเรื่อง scrollbar อยู่แล้ว แต่เวอร์ชัน Rust ยังขาดบางส่วน
  • เนื่องจากปฏิสัมพันธ์หลักของ UEFIRC เกิดขึ้นใน scroll view ที่เต็มไปด้วยข้อความ จึงมีการนำ ความสามารถของ scrollbar กลับมา implement ใน Rust libgui
  • scroll view มีต้นทุนการเรนเดอร์พิกเซลสูงกว่า view ขนาดคงที่
    • view ขนาดคงที่คิดง่าย ๆ เป็น RGB buffer ขนาด width * height
    • ส่วน scroll view ต้องจัดการกับแคนวาสที่ขยายได้ไม่สิ้นสุด
  • GUI toolkit Rust ของ axle จัดการ scroll view แบบ tile-based
    • แต่ละ tile คือบัฟเฟอร์พิกเซลสี่เหลี่ยมจัตุรัสกว้างหลายร้อยพิกเซล
    • จะจัดสรรเฉพาะ tile ที่จำเป็นสำหรับบริเวณที่มีการเรนเดอร์เนื้อหาจริง
    • จากนั้นคำนวณ tile ที่มองเห็นและนำมาต่อกันเป็นภาพสุดท้าย
  • หาก TrueType renderer เรียก putpixel() สำหรับทุกพิกเซลของ glyph, scroll view จะไม่รู้ล่วงหน้าถึงพื้นที่เรนเดอร์ทั้งหมด ทำให้ไม่มีประสิทธิภาพ
  • เพื่อแก้ปัญหานี้ จึงเพิ่ม polygon stack เข้าไปในหน่วยการวาดพื้นฐานอย่างเส้น, วงกลม และสี่เหลี่ยม
    • ทำให้ scroll view รู้ล่วงหน้าได้ว่ากำลังวาด polygon ขนาดใหญ่ และสามารถจัดสรร tile ที่จำเป็นไว้ก่อน
    • แม้จะไม่ชอบนักที่ต้องใช้การเติม arbitrary polygon เป็น primitive พื้นฐาน แต่ในทางปฏิบัติก็ทำงานได้ดี

libgui ที่ถูกปรับปรุงระหว่างสร้าง UEFIRC

ผลลัพธ์ที่ไม่จำเป็นอย่างสิ้นเชิง

  • ตัวไคลเอนต์ IRC เองเป็นโปรเจกต์มุกที่ทำอย่างประณีต จึงไม่ได้มีประโยชน์เชิงใช้งานจริงมากนัก
  • แต่เมื่อรู้สึกหงุดหงิดกับ UEFI TCP/IP stack ก็สามารถใช้มันเป็นเครื่องมือระบายความไม่พอใจได้
  • และท้ายที่สุดก็สามารถเข้าไปทักทายในช่อง IRC #edk2 สำหรับนักพัฒนา UEFI ภายใน UEFI ได้เลย

1 ความคิดเห็น

 
GN⁺ 2024-04-07
ความคิดเห็นจาก Hacker News
  • ผมทำไคลเอนต์ IRC แบบกราฟิกที่รันได้เฉพาะใน สภาพแวดล้อมก่อนบูตของ UEFI เล่น ๆ และยังใส่ฟีเจอร์เกินจำเป็นอย่างฟอนต์ TrueType, เคอร์เซอร์ และองค์ประกอบตกแต่ง GUI เข้าไปด้วย
    เดิมทีเป็นโปรเจกต์ที่อยากทำอะไรเร็วและเบา ๆ หลังจากเหนื่อยกับการทำตัวรับสัญญาณ GPS ตั้งแต่ต้น แต่ก็เหมือนทุกครั้ง มันใช้เวลานานกว่าที่คิดไว้มาก
    ผมใช้เวลากับภาพประกอบในบทความที่แสดงวิธีโมเดล scroll view แล้วเรนเดอร์ลงใน viewport แบบคงที่อยู่ไม่น้อยเหมือนกัน หวังว่าจะดูแล้วสนุก
    ตอนแรกคิดว่า “ลองยัดสิ่งที่ไม่ควรอยู่ใน UEFI เข้าไปกันเถอะ” แล้วนึกถึงไคลเอนต์ Twitter แต่มีคนทำไว้ดีแล้วด้วยโปรโตคอล HTTP ของ UEFI เลยตัดสินใจเลี่ยง HTTP
    ดังนั้นจึงเลือก IRC ซึ่งทำงานบน TCP และยังให้ความรู้สึกแบบโซเชียลมีเดียที่ไม่เข้ากับสภาพแวดล้อมก่อนบูตเลย

    • แม้จะบอกว่า “ให้ความรู้สึกว่าไม่ควรอยู่ใกล้สภาพแวดล้อมก่อนบูตด้วยซ้ำ” แต่ถ้าจะขอให้ช่วยแก้ปัญหาการบูต มันกลับดูเหมือนเป็นที่ที่เหมาะพอดี
    • อยากทิ้งระบบปฏิบัติการที่ใหญ่เทอะทะและฟีเจอร์จิปาถะ แล้วไปใช้ UEFI ที่เล็กและเรียบง่ายกว่า เพราะมันน่าจะเริ่มได้เร็วขึ้นและทำให้การพัฒนาแบบ “embedded” ง่ายขึ้นด้วย
      แน่นอนว่าพูดเล่นนะ ก็ในระดับหนึ่ง
      ผมเป็นสายมินิมัลลิสต์จนไม่ต้องการ GUI หรือเมาส์ด้วยซ้ำ และ UEFI เองก็ดูเหมือนจะมีมากกว่าที่ผมจำเป็นต้องใช้แล้ว
      ไคลเอนต์ Twitter ที่พูดถึงอยู่ที่นี่: https://github.com/arata-nvm/mitnal
    • ถ้าซอฟต์แวร์ไหนใหญ่เกินกว่าจะยัดเข้า UEFI ได้ ก็ต้องถือว่ามันเป็น ซอฟต์แวร์บวมเทอะทะ ที่ไม่จำเป็นทั้งหมดตั้งแต่แรก สมัยก่อนฟลอปปีดิสก์ 360KB สองแผ่นก็พอแล้ว
    • เจ๋งจริง ๆ ผมสงสัยมานานแล้วว่าจะเก็บ ข้อมูลรับรอง VPN ไว้ใน UEFI แล้วให้ระบบเชื่อมต่อกับเซิร์ฟเวอร์เพื่อบูตผ่านเครือข่ายแบบ PXE ได้หรือไม่
      ดูเหมือนอาจเป็นวิธีที่ค่อนข้างดี และอาจอนุญาตได้อย่างปลอดภัย เพื่อกู้คืนระบบระยะไกลโดยอัตโนมัติเมื่อการติดตั้งพังจนบูตตามปกติไม่ได้
    • อยากฟังเรื่อง ตัวรับสัญญาณ GPS ที่ทำตั้งแต่ต้นมากกว่า
  • ดีมาก ๆ และยังแสดงให้เห็นชัดว่าใต้ระบบที่คนส่วนใหญ่คิดถึงนั้น มีซอฟต์แวร์ที่ซับซ้อนและทรงพลังกว่าที่คิดซ่อนอยู่
    คนมักเข้าใจผิดว่าระบบปฏิบัติการคือ “ชั้นล่างสุด” ของ software stack แต่จริง ๆ แล้วยังมี โค้ดลักษณะเฟิร์มแวร์ ที่เป็นเจ้าของระบบอย่างแท้จริงอยู่
    บางครั้งมันทำงานเสร็จแล้วก็หายไป และบางครั้งก็อยู่ตลอดเวลาที่ระบบเปิดอยู่ในสภาพที่แม้แต่ระบบปฏิบัติการยังรู้สึกเหมือนมองไม่เห็นมัน
    มีทัศนคติว่า “ก็แค่โค้ดระดับต่ำสำหรับขับอุปกรณ์เท่านั้น ไม่มีอะไรเลวร้ายเกิดขึ้นจากตรงนั้นหรอก” แต่ถ้าถึงขั้นใส่ไคลเอนต์ IRC ลงไปใต้ชั้นนั้นได้ เรื่องประสงค์ร้ายอื่น ๆ ก็จินตนาการได้ไม่ยาก

  • ถามว่า “ทำไม?” นี่ “ทำไม” เป็นคำถามอะไรกันแน่? ผมมา HN ก็เพื่อดูจิตวิญญาณแบบนี้แหละ
    “ความตระหนักที่น่ากลัวที่สุดมาเยือนผม สิ่งที่ผมทำไปไม่มีเหตุผลใด ๆ เลย ผมรู้ว่าทำไมผมถึงทำ ก็แค่เพราะคิดว่าน่าจะสนุก แต่พวกเขาจะถามว่า ‘ทำบ้าอะไรแบบนี้ไปทำไม’ และถ้าไม่มีเหตุผลที่ฟังขึ้นพอ ก็ดูเหมือนพวกเขาจะจับผมยัดเข้าโรงพยาบาลจิตเวช” — Boyd Rice

  • ไม่ต้องดูถูกตัวเองก็ได้ ที่นี่มีโปรเจกต์ ไคลเอนต์สั่งการและควบคุมบอตเน็ต อยู่
    UI นี่ตลกอยู่หน่อย ๆ

  • เจ๋งมาก ไม่รู้มาก่อนว่า UEFI API เข้าถึงได้ง่ายและมีเอกสารดีขนาดนี้
    อยากรู้ว่าวงจรการพัฒนาเป็นยังไง น่าจะรันใน VM แต่สงสัยว่าทุกครั้งที่รันไคลเอนต์ต้อง “บูต” ใหม่ทุกครั้งหรือเปล่า

    • ปกติ workflow คือบูตอินสแตนซ์ QEMU ที่โหลดแอป UEFI เข้าไป
      สคริปต์หลักสำหรับรันจะสร้าง EFI filesystem ใหม่ที่มี build ใหม่ของ UEFIRC แล้วส่งต่อให้ QEMU
      แต่ตอนทำ GUI overhead นี้ค่อนข้างน่ารำคาญ เลยตั้งค่าให้แอป build ได้ทั้งเป้าหมายเป็น UEFI แท้ ๆ และ host environment ที่รันบน Mac
      พอเปลี่ยน build flag แล้ว GUI toolkit จะวาดลง framebuffer ที่ UEFI ให้มาโดยตรง หรือเชื่อมกับ window system ของ Mac เพื่อรับส่ง event
      overhead ของแนวทางแบบ สองเป้าหมาย นี้เห็นได้จาก entry point ด้วย: https://github.com/codyd51/uefirc/blob/main/src/main.rs
      ส่วนการ parse ข้อความ IRC ไม่ต้องมีของตกแต่งพิเศษ จึงพัฒนาเป็นชุด unit test ที่รันบน Mac ได้โดยตรง และบางส่วนอยู่ที่นี่: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
    • QEMU รันแอป UEFI ได้
  • สักวันหนึ่งอยากทำ ระบบปฏิบัติการ สำหรับบอต IRC ของผมที่ยังรันอยู่ให้เสร็จ
    อาจเป็นสิ่งที่ไร้ประโยชน์ที่สุดที่จะพูดก็ได้ แต่การเคลื่อนเมาส์แบบไม่เชิงเส้น หรือก็คือ acceleration เป็นการตั้งค่าแรกที่ผมปิดเมื่อบูตระบบปฏิบัติการใหม่ขึ้นมา แปลกดีที่มือผมเจ็บจริง ๆ
    เช่น บน Mac มี linearmouse ฟรี ส่วนบน Windows ก็แค่ปิด acceleration ได้เลย บน Linux ก็แน่นอนว่าง่าย
    ถ้าใช้ mouse acceleration จะฝึกความรู้สึกในการ mapping ระหว่างระยะที่เมาส์เคลื่อนกับระยะที่เคลื่อนบนหน้าจอได้ยาก และในระยะยาวผมมองว่าใช้แบบไม่มี acceleration มีประสิทธิภาพกว่า
    เป็นวิธีที่เรียนรู้มาจากเกมเมอร์ และคิดว่าเกมเมอร์ยังทำแบบนั้นด้วยเหตุผลที่สมควรอยู่

    • ผมไม่ได้เปลี่ยนการตั้งค่าเมาส์เลยไม่รู้ว่าค่าเริ่มต้นคืออะไร แต่ยังคลิกบริเวณหน้าจอที่ถูกบังอยู่ได้อย่างแม่นยำ
      ไม่ว่าจะเป็นแบบไหน ความรู้สึกก็น่าจะคุ้นชินได้ เหมือนคันเร่งรถยนต์ที่ปกติก็ไม่ได้ map ตรงกับความเร็วโดยตรง
  • ถ้าถามว่า “ทำไม?” ก็เพราะตอน UEFI ถูกแนะนำครั้งแรก มันสัญญาไว้ถึงแอประดับต่ำแบบนี้
    ฝั่งที่สร้าง UEFI ยังเคยฝันว่าจะใช้มันแทนระบบปฏิบัติการมินิสำหรับอินเทอร์เน็ตโดยเฉพาะที่อิง Linux ซึ่งบาง vendor ให้กดปุ่มเฉพาะระหว่างบูตเพื่อเข้าใช้งานได้ด้วย จำชื่อไม่ได้แล้ว

    • นั่นคือฟีเจอร์ Quick View / Quick Boot ที่เคยมีในเครื่องอย่าง Dell รุ่นเก่า ๆ โดยมากจะบูตตรงเข้าแอป productivity ไม่กี่ตัว
      ผมเคยดูวิดีโอ YouTube ที่เจาะลึกเรื่องนี้ จำได้ว่าตอนแรกเป็น Linux แบบย่อส่วนหรือระบบปฏิบัติการปรับแต่งอื่น ๆ ต่อมาย้ายไปเป็นแอป UEFI แล้วสุดท้ายกระแสก็จางไป
  • บทความดี ทำให้นึกถึงมุก April Fools ของ bootloader barebox เมื่อ 2 ปีก่อน เป็นฟีเจอร์ที่ถ้าเป้าหมายบูตอื่น ๆ ล้มเหลวหมด ก็จะเชื่อมต่อไปที่ #barebox[1]
    ฝั่งนั้นโฟกัสที่การเพิ่มการรองรับ TCP ให้ barebox และไม่มีองค์ประกอบ GUI เท่ ๆ แบบที่นี่
    อินเทอร์เฟซมีแค่ command line และถ้า build barebox เป็น EFI payload ก็สามารถวาดบน EFI GOP ได้
    [1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...

  • นึกถึงวิดีโอล่าสุดของ Cathode Ray Dude ทันที ที่พูดถึง QuickLook ของ HP ซึ่งเป็น “ไคลเอนต์อีเมล” หรือจริง ๆ แล้วคือปลั๊กอิน Outlook และเป็นผลิตภัณฑ์ที่ implement แล้ววางขายด้วยวิธีประมาณนี้: https://www.youtube.com/watch?v=ssob-7sGVWs
    ในวิดีโอยังมีเรื่องแปลก ๆ กว่านี้ที่ HP เคยทำด้วย แต่โปรเจกต์นี้ทำได้ถึงส่วนยากที่ QuickLook เลี่ยงไป นั่นคือ networking

  • ภาพประกอบ ในบทความยอดเยี่ยมและน่าประทับใจอย่างน่าทึ่ง