;;; xcb-slides.el --- X11-only slideshow -*- lexical-binding: t; -*- ;; Copyright (C) 2023 Vasilij Schneidermann ;; SPDX-License-Identifier: CC-BY-4.0 ;; Author: Vasilij Schneidermann ;; URL: https://depp.brause.cc/talks/chicken-village ;; Version: 0.0.1 ;; Package-Requires: ((emacs "28.1")) ;; Keywords: multimedia ;; This file is NOT part of GNU Emacs. ;; This file is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; This file is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; Slides system for a talk about X11 programming in elisp ;;; Code: (slides-define '(title-slide :title "X11 programming in Emacs Lisp" :subtitle "- A descent into madness -")) ;; HACK: this outline must be manually synchronized (slides-define '(text-slide :title "Outline" :text "1. Intro 2. X11 explained 3. The Xlib way 4. X11/XCB woes 5. The Render extension 6. Outro")) (slides-define '(title-slide :title "Section 1" :subtitle "Intro")) (slides-define '(text-slide :title "About" :text "- Vasilij Schneidermann, 30 - Offensive Security Specialist at CODE WHITE GmbH - mail@vasilij.de - https://depp.brause.cc - https://emacsninja.com")) (slides-define '(text-slide :title "Motivation" :text "- Highly interactive Emacs games - Reached limits of purely textual display - Reached limits of XBM/SVG images - FFI in Emacs is a major pain - Pushing the boundary with X11 programming - Understanding how X11 works")) (slides-define '(text-slide :title "Motivation" :text " \"Low-level programming is good for the programmer's soul\" - John Carmack")) (slides-define '(text-slide :title "Dogfooding" :text "- This presentation uses X11! - Text is drawn with GNU Unifont - Slides are defined in Emacs Lisp - Title/text slides draw the specified text - Key events trigger next/prev slide - Transformation matrix for readability")) (slides-define '(text-slide :title "Dogfooding (source code)" :text "(slides-define '(text-slide :title \"Dogfooding\" :text \"- This presentation uses X11! - Text is drawn with GNU Unifont - Slides are defined in Emacs Lisp - Title/text slides draw the specified text - Key events trigger next/prev slide - Transformation matrix for readability\"))")) (slides-define '(text-slide :title "Upcoming slide (source code)" :text "(slides-define '(title-slide :title \"Section 2\" :subtitle \"X11 explained\"))")) (slides-define '(title-slide :title "Section 2" :subtitle "X11 explained")) (slides-define '(text-slide :title "X11 design" :text "- Old windowing system server (80ies) - Handles everything you need for GUIs (and a bit more) - Binary protocol, but not tied to C libraries - Spoken via TCP/UNIX socket - Network-transparency - Many (obsolete) extensions")) (slides-define '(text-slide :title "X11 design choices" :text "- Supports monochrome, palette and 24bit color - Flexibility over fast/good rendering - Basic server-side font handling - Client-side GUI decorations - Window managers are clients, too - Security is not much of a concern")) (slides-define '(text-slide :title "X11 pain points" :text "- User input is a huge mess - Font handling is better done client-side - Render extension mandatory for good rendering - HW acceleration is a pain to accomplish - Documentation could be better - Linux graphics stack is highly confusing")) (slides-define '(text-slide :title "X11 protocol libraries" :text "- Xlib: Helper library smoothing out rough edges - Decent documentation - Internal batching/synchronous - No anti-aliasing support - Color drawing uses palettes - Use of FFI required, one library per extension - XCB: - Small core API - XML protocol description -> auto-generated bindings - Every extension has a corresponding XML file - No batching/asynchronous - Bad/missing documentation - Some missing functionality")) (slides-define '(text-slide :title "My project strategy" :text "- Learn how to use XCB Emacs Lisp Bindings (XELB) - Port small demos from C to Emacs Lisp - Learn how to do old-school X11 rendering - Learn how to handle input, colors, images, fonts, ... - Build my own interactive demos - Learn how to use Render - Build more complex demos - Goal: Well-performing demos/games - Bonus: Learning material for others")) (slides-define '(title-slide :title "Section 3" :subtitle "The Xlib way")) (slides-define '(text-slide :title "X life cycle" :text "- Connect to a DISPLAY - Obtain setup/screen/root objects from the connection - Allocate a window ID - Create and map (show) the window - Do something actually interesting - Destroy the window by its ID - Disconnect")) (slides-define '(text-slide :title "X geometry drawing" :text "- Big selection of drawing primitives - Points, lines, rectangles, circles, arcs - Drawing parameters passed via graphics context - Drawing performed against a drawable (window, pixmap) - No anti-aliasing or true transparency available" :picture (lambda () slides-picture-primitives))) (slides-define '(text-slide :title "X pixmaps/images" :text "- Pixmaps are drawables backed by another drawable - Useful as off-screen drawing targets - Image data can be uploaded/downloaded to/from them - Pixmaps can be copied to other drawables" :picture (lambda () slides-picture-emacsicon))) (slides-define '(text-slide :title "Aside: Image loaders & farbfeld" :text "- Xlib does have a XPM image loader - XCB does not have any image loaders - Loading GIF/JPG/PNG/... in Emacs Lisp is complicated - farbfeld is brutally simple in comparison - 8 bytes magic, 4 bytes width, 4 bytes height, 4x16bit RGBA data (not pre-multiplied)" :picture (lambda () slides-picture-farbfeldhex))) (slides-define '(text-slide :title "Aside: Image loaders & farbfeld" :text "- PNG compression uses RLE + zlib (gzip) - File sizes with bzip2 are competitive - More image loader research needed (what do games do?)" :picture (lambda () slides-picture-farbfeldsize))) (slides-define '(text-slide :title "X server-side fonts" :text "- Surprisingly rich API - Fonts are looked up, opened, inspected, ... - To draw a font, a special graphics context is used - Multi-line text is fancy stuff - Anything serious relies on external libraries" :picture (lambda () slides-picture-fontfixed))) (slides-define '(text-slide :title "X events" :text "- Events can be registered per window - The basic stuff works as expected (mouse/keyboard) - Making sense of keyboard events is complicated - Some events depend on others - For example, DestroyNotify <- StructureNotify - To redraw a window correctly, redraw on exposure - More obscure events are reported for other changes: - Focus/visibility/window property/selection/...")) (slides-define '(text-slide :title "X properties" :text "- Metadata can be associated with windows - Window role, title, class, ... - Window size/state hints relevant for window manager - EWMH/ICCCM extension surprisingly complex - xprop demo")) (slides-define '(text-slide :title "X11 extensions of interest" :text "- XKB (keyboard layouts, keysyms) - BIG-REQUESTS (raise request size limit) - RENDER (modern drawing) - MIT-SHM / DAMAGE / Composite (faster drawing) - Screensaver / DPMS (display power management settings) - EWMH/ICCCM (WM hints)")) (slides-define '(title-slide :title "Section 4" :subtitle "X11/XCB woes")) (slides-define '(text-slide :title "XCB vs Xlib usage" :text "int XPutImage(Display *display, Drawable d, GC gc, XImage *image, int src_x, int src_y, int dest_x, int dest_y, unsigned int width, unsigned int height); xcb_void_cookie_t xcb_put_image(xcb_connection_t *conn, uint8_t format, xcb_drawable_t drawable, xcb_gcontext_t gc, uint16_t width, uint16_t height, int16_t dst_x, int16_t dst_y, uint8_t left_pad, uint8_t depth, uint32_t data_len, const uint8_t *data);")) (slides-define '(text-slide :title "XCB vs Xlib usage" :text "- XCB requires a connection argument - Explicit arguments (format, depth, left_pad) - Missing arguments (src_x, src_y) - Error handling is more painful in XCB - In Xlib, return code hints at error - In XCB, the return code allows fetching the error" - On the upside, XCB makes bindings easy)) (slides-define '(text-slide :title "XCB documentation issues" :text "- XCB docs tell you to learn Xlib first - Xlib is more high-level - Not all Xlib API has XCB equivalent - Some helper libraries do paper over differences - XCB docs are sparse (\"TODO: NOT YET DOCUMENTED\") - You are expected to know the X protocol")) (slides-define '(text-slide :title "X protocol vs XCB mismatch (Render spec)" :text "CompositeGlyphs8 [...] src-x, src-y: INT16 dst-x, dst-y: INT16 glyphcmds: LISTofGLYPHITEM8")) (slides-define '(text-slide :title "X protocol vs XCB mismatch (XCB spec)" :text " ")) (slides-define '(text-slide :title "X protocol vs XCB mismatch (XCB renderutil)" :text "void xcb_render_util_glyphs_8 ( xcb_render_util_composite_text_stream_t *stream, int16_t dx, int16_t dy, uint32_t count, const uint8_t *glyphs ) { _glyph_header_t header = { count, {0,0,0}, dx, dy }; if (count > 252) return; /* FIXME */ /* ... */ _grow_stream(stream, sizeof(header) + count+3); memcpy(stream->current, &header, sizeof(header)); /* ... */")) (slides-define '(text-slide :title "Debugging issues" :text "- Mistakes are very easy to make - Typical issue: Something isn't drawn - Fun issue: Graphical glitches - Debugging is detective work - Errors can be raised by requesting/checking them - Best reference so far: Cairo/pixman sources - X.org sources surprisingly readable")) (slides-define '(text-slide :title "Glitch: Image pixel endianness" :text "Mistake: RGBA vs BGRA" :picture (lambda () slides-picture-emacsicon-glitch))) (slides-define '(text-slide :title "Glitch: Incorrect font dimensions" :text "Mistake: Mixed up width and height" :picture (lambda () slides-picture-unifontdim-glitch))) (slides-define '(text-slide :title "Glitch: Incorrect LSB of font" :text "Mistake: 0x01 vs 0xFF typo in font generation code" :picture (lambda () slides-picture-unifontlsb-glitch))) (slides-define '(text-slide :title "Glitch: Drawing with uninitialized memory" :text "Mistake: Copying memory yet to be written to" :picture (lambda () slides-picture-uninitialized-glitch))) (slides-define '(text-slide :title "Glitch: Drawing with uninitialized memory (again)" :text "This time it copied text from another slide..." :picture (lambda () slides-picture-uninitialized2-glitch))) (slides-define '(text-slide :title "Debugging story (showing the uninitialized picture)" :text "- The image is 256x256 = 65536 pixels = 262144 bytes - Uploading the image resulted in a hang - Maximum X request size: 65536*4 (2 bytes length field) - PutImage data limit is lower (due to other metadata) - According to Cairo, other metadata is 18 bytes - According to X protocol, it's 24 bytes - Bigreq extension exists to work around this (but cannot be used in XELB without modifying xcb.el) - Other workaround: Draw the maximum amount of lines at different y offsets...")) (slides-define '(text-slide :title "Drawing pictures in chunks" :text "(let ((stride (* width 4)) (y-offset 0) (chunk-height (/ max-pixels-size width))) (while (< y-offset height) (let* ((chunk-beg (* y-offset stride)) (chunk-end (+ chunk-beg (* chunk-height stride))) (chunk-end (min chunk-end (length data))) (chunk (substring data chunk-beg chunk-end))) (xcb:+request slides-x-conn (xcb:PutImage ; ... :width width :height chunk-height :dst-x 0 :dst-y y-offset :data chunk))) (setq y-offset (+ y-offset chunk-height))))")) (slides-define '(text-slide :title "XELB issues" :text "- Creates tons of objects - Serialization is slow (2.4s connection setup) - It was an accidentally quadratic subvector... - Pending PR: ch11ng/xelb#30 (improves it to 0.31s) - Easy to misuse (keyword args, xcb:+request) - Generated sources are outdated (2019) - Weird code (async, but reference-counted locking) \"[...]Concurrency is disabled as it breaks the orders of errors, replies and events.\"")) (slides-define '(text-slide :title "XELB issues" :text "(xcb:+request slides-x-conn (xcb:PutImage :format xcb:ImageFormat:ZPixmap :drawable pm :gc gc :width width :height height :dst-x 0 :dst-y 0 :left-pad 0 :depth 32 :data data))")) (slides-define '(text-slide :title "Tearing" :text "- Partial renders are very common - DBE extension exists, but not available via XCB - Workaround: Draw to surface, copy it to window - Compositor programs exist to smooth things out - They lower performance though - Smooth video recording impossible with compositor")) (slides-define '(text-slide :title "OpenGL" :text "- Windowing interface only defined for Xlib (GLX) - Pure XCB use of OpenGL therefore not possible - Sample code of mixing XCB with Xlib exists - GLX would need to be rewritten in terms of XCB")) (slides-define '(title-slide :title "Section 5" :subtitle "The Render extension")) (slides-define '(text-slide :title "Render basics" :text "- Defines picture type (backed by pixmap) - Colors are defined directly - Triangles are the fundamental shape, along with square/trapezoid helpers - Shapes like circles need to be built from triangles - Antialiasing enabled per picture - Composite operator allows combining pictures")) (slides-define '(text-slide :title "Render transparency" :text "- Transparency via alpha color component - Pre-multiplied alpha required (except gradients) - Instead of specifying 0xFFFF as color and 0x9999 as alpha, (0xFFFF*0x9999)>>16 is used a color - Combining transparent colors then just needs addition - Feels like a terrible hack though")) (slides-define '(text-slide :title "Porter/Duff operators & blending" :text "- Many ways to combine source/dest pixel - A resulting pixel may use the source, dest or nothing - Case of source-only, dest-only or both possible - This results in an algebra of 3^3 combinations - Over operator is a sensible default to stack pictures - Other operators can be used to create complex shapes - Clear operator can be used to delete parts - Blending modes can be used to change colors - For example, only the hue can be affected")) (slides-define '(text-slide :title "Transparency/blending demo" :picture (lambda () slides-picture-transparency))) (slides-define '(text-slide :title "Render client-side fonts" :text "- The font API is a lot simpler compared to Xlib - Create glyphset, upload glyphs, draw line of glyphs - Client needs to obtain font data/metrics - Typically done with FreeType2 - Good text rendering requires more libraries - Fontconfig, Pango, Harfbuzz, ...")) (slides-define '(text-slide :title "Aside: Font loader hacks" :text "- Game programmer hack: Font atlas - Texture with pre-rendered glyphs in a grid - Each grid cell is then uploaded to glyphset - Even hackier idea: GNU Unifont .hex file - Plaintext format: \"
: \" - Generation script parsing glyphs of interest - Each glyph is expanded into desired format - Emit a lisp file defining a char table - Easily embeddable")) (slides-define '(title-slide :title "Section 6" :subtitle "Outro")) (slides-define '(text-slide :title "Results" :text "- Performance is a bit better than vanilla Emacs - Pretty inconvenient to use though - One XELB performance improvement (ch11ng/xelb#30) - Follow-up topic: XCB in Scheme or better XELB (no GC) - For next game jam, I'll try to explore SVG more - X11 is kinda elegant, but lacks cohesion - Better understanding why X11 rendering sucks - Better understanding why Wayland is needed - More appreciation for Cairo")) (slides-define '(text-slide :title "Questions?")) ;; Local Variables: ;; read-symbol-shorthands: (("slides-" . "xcb-slides-") ;; ("slides-data" . "xcb-slides-data") ;; ("slides-data-" . "xcb-slides-data-")) ;; End: (provide 'slides-data) ;;; xcb-slides-data.el ends here