From 8955c39a9bab11c7e181cd53272cc5957c8f63ba Mon Sep 17 00:00:00 2001 From: Andros Fenollosa Date: Wed, 17 Jun 2026 15:41:10 +0200 Subject: [PATCH] Add visual QA checklist for the GPU rendering backend --- VISUAL-QA.org | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100644 VISUAL-QA.org diff --git a/VISUAL-QA.org b/VISUAL-QA.org new file mode 100644 index 00000000000..ebec4f14693 --- /dev/null +++ b/VISUAL-QA.org @@ -0,0 +1,517 @@ +#+TITLE: Visual QA Checklist — GPU Backend +#+DESCRIPTION: Visual behaviors to verify manually after any change to gfxterm.c, mtlterm.m, glterm.c or gfxdrv.h +#+STARTUP: overview +#+TODO: TODO | PASS FAIL SKIP + +* Instructions + Mark each item as PASS, FAIL or SKIP before pushing or releasing. + SKIP = does not apply to the current platform or the specific change being made. + + Useful commands to reproduce test cases: + - macOS (Metal): =/tmp/emacs-mtl-buildfull/src/emacs -Q= + - macOS (NS baseline): =EMACS_GPU_DISABLE=1 /tmp/emacs-mtl-buildfull/src/emacs -Q= + - Linux (GPU): =/usr/local/bin/emacs -Q= on the test laptop (ssh debian) + - Linux (cairo baseline): =EMACS_GPU_DISABLE=1 /usr/local/bin/emacs -Q= + + General principle: if anything looks different from the NS/cairo baseline + in equivalent cases, it is a failure unless explicitly documented as an + accepted difference. + +* 1. Render cycle and present +** TODO 1.1 No black flash on startup + Open without -Q (real config). The window must not show a black or blank + frame before the content is painted. +** TODO 1.2 No flash when switching buffers + =C-x b= between buffers. No previous buffer content or blank frame should + appear for even one vblank. + (Known regression: was the XDBE back buffer on Linux, fixed with + inhibit-double-buffering; on Metal it was swapchain pressure.) +** TODO 1.3 No flash when resizing the window (WM resize) + Drag the window border with the mouse. No black band or displaced text + should appear. + (Regression: v0.4.1 — partial present was caching a stale surface size.) +** TODO 1.4 No flash when opening/closing the minibuffer + =M-x= then =C-g=. The minibuffer area must appear and disappear cleanly. +** TODO 1.5 Clear-only cycle does not present (avoids white flash) + =C-x t t= (tab-bar). The first time it is activated, the tab-bar must + appear without a fully white frame flashing first. +** TODO 1.6 Deferred flush: mouse-face does not flicker + Move the mouse over a link or a mode-line button. The highlight must + appear and disappear without flickering between frames. +** TODO 1.7 Pending scroll-bar clears are applied correctly + Open a long buffer, scroll, then resize. The scroll-bar gutters must not + appear dirty. + +* 2. Text and glyphs +** TODO 2.1 Basic ASCII + Open a plain-text ASCII buffer. Characters must be sharp and aligned with + the NS/cairo baseline. +** TODO 2.2 Bold and italic fonts + =(propertize "hello" 'face '(:weight bold))= and =:slant italic=. + Both must be visually distinct from each other and from regular text. +** TODO 2.3 Extended Latin (ñ, ü, é, ç) + Type accented characters. They must not appear vertically shifted + relative to the rest of the line. +** TODO 2.4 CJK (kanji, hangul, Chinese characters) + Open a buffer with CJK text. Characters must occupy twice the width of + ASCII and be centered in their cell. +** TODO 2.5 Symbols and box-drawing (─ │ ┌ ┐ └ ┘) + Open neotree or a buffer with Unicode borders. Lines must connect without + gaps or overlaps. +** TODO 2.6 Font-lock (multiple face colors on the same line) + Open a .el or .py file. Syntax-highlight colors must match the baseline + at the same positions. +** TODO 2.7 Face inheritance (:inherit) + Verify that a face inheriting from another applies its attributes + correctly (color, weight, height). +** TODO 2.8 Relative :height (e.g. :height 1.5) + =(propertize "big" 'face '(:height 1.5))= in a buffer. The text must be + 1.5x taller and adjacent lines must not overlap. +** TODO 2.9 Variable-pitch and fixed-pitch on the same line + Enable variable-pitch-mode in a mixed buffer (org-mode with code blocks). + Both fonts must align to the same baseline. +** TODO 2.10 text-scale-increase / text-scale-decrease + =C-x C-= and =C-x C--=. Text must scale cleanly without glyphs from + the previous size remaining visible in the atlas. +** TODO 2.11 Advance precision (no drift on long lines) + Open a line of ~200 characters with wrapping disabled. The cursor at the + end must be exactly at the expected column. +** TODO 2.12 Stroke weight (gamma ^0.82 on Metal) + Compare text in Metal vs NS. Accepted difference: Metal may appear + marginally thinner. Unacceptable: noticeably thicker or thinner than + previous releases. + +* 3. Compositions and special scripts +** TODO 3.1 Combining characters (e + ́ = composed é) + Text with combining characters (U+0301 etc.). They must not appear + shifted relative to their base character. +** TODO 3.2 Arabic and Hebrew (shaped, RTL) + Open an Arabic or Hebrew buffer. Text must appear right-to-left with + correctly joined glyphs (proper shaping). +** TODO 3.3 Ligatures (if the font supports them) + With Fira Code or another font with ligatures, verify that -> renders + as a ligated arrow rather than two separate characters. +** TODO 3.4 Automatic composition (auto-composition-mode) + =(setq auto-composition-mode t)= and type text that triggers composition. + Composed glyphs must appear at the correct position. +** TODO 3.5 Mixed BiDi (Arabic + ASCII on the same line) + A buffer with interleaved L2R and R2L text. Verify that the visual + order is correct and cursors move as expected. + +* 4. Emoji and color glyphs +** TODO 4.1 Basic emoji (U+1F600, U+1F4BB) + Insert emoji in a buffer. They must appear in color, not grayscale, + and at the correct size (one or two cells). +** TODO 4.2 U+2764 RED HEART (may hang vanilla under Xvfb) + Verify on real hardware only, not in headless Xvfb. + The heart must appear in red. +** TODO 4.3 Emoji in the mode-line + =(setq mode-line-format '("" (:eval "📁 %b")))= + The emoji must render in the mode-line without overlapping adjacent text. +** TODO 4.4 ZWJ sequences (family emoji, flag emoji) + Insert a ZWJ sequence. It must render as a single composed glyph, not + as a sequence of separate characters. + +* 5. Face decorations +** TODO 5.1 Single underline + =(propertize "test" 'face '(:underline t))= + One line below the text, aligned to the font's baseline metrics. +** TODO 5.2 Double underline + =(propertize "test" 'face '(:underline (:style double)))= + Two parallel lines below the text. +** TODO 5.3 Wave underline + =(propertize "test" 'face '(:underline (:style wave :color "red")))= + A 3px-tall zigzag line. Verify that it is continuous across adjacent + strings (the phase must not reset per fragment). +** TODO 5.4 Dot underline + =(propertize "test" 'face '(:underline (:style dots)))= + Dotted segments. The phase must be continuous across fragments. +** TODO 5.5 Dash underline + =(propertize "test" 'face '(:underline (:style dashes)))= + Dash segments of 3x the line thickness. Phase must be continuous. +** TODO 5.6 Underline with specific color + =(propertize "test" 'face '(:underline (:color "blue")))= + The underline must be blue; the text must remain in its default color. +** TODO 5.7 Overline + =(propertize "test" 'face '(:overline "green"))= + A green line at the top of the text. +** TODO 5.8 Strike-through + =(propertize "test" 'face '(:strike-through "red"))= + A red line at mid-height of the text. +** TODO 5.9 Underline clamped to descent + Text with a font that has a small descent. The underline must not + escape the line cell. If pos+thickness > descent, it must collapse + to 1px at descent-1. + +* 6. Boxes and reliefs +** TODO 6.1 Simple box outline + =(propertize "test" 'face '(:box 1))= + A 1px border around the text. +** TODO 6.2 Box with larger line width + =(propertize "test" 'face '(:box (:line-width 3 :color "blue")))= + A blue 3px border. Horizontal and vertical edges must have the correct + thickness. +** TODO 6.3 Raised relief (mode-line, buttons) + Activate the mode-line and verify that tab-bar buttons appear raised + (light border top/left, dark border bottom/right). +** TODO 6.4 Sunken relief + =(propertize "test" 'face '(:box (:line-width 2 :style sunken-box)))= + The border must appear sunken (dark top/left, light bottom/right). +** TODO 6.5 Relief corners must not appear light + On a raised relief of >= 2px, corners where the light and shadow edges + overlap must appear dark (corner darkening). +** TODO 6.6 Relief colors in dark mode (Metal/macOS) + Switch to a dark theme. The mode-line relief colors must remain + coherent (not inverted or broken). +** TODO 6.7 Relief on images (:relief in tool-bar) + Verify that tool-bar buttons have correct relief (raised at rest, + sunken when pressed if applicable). + +* 7. Cursor +** TODO 7.1 FILLED_BOX (default cursor) + The filled box cursor must have inverted colors: cursor background + color, text in the cursor background color. +** TODO 7.2 HOLLOW_BOX + =(setq cursor-type 'hollow)= + Four 1px edges around the cell, transparent interior. +** TODO 7.3 BAR (vertical bar cursor) + =(setq cursor-type 'bar)= + A 1-2px vertical bar on the left edge of the cell. + For R2L text: on the right edge. +** TODO 7.4 HBAR (horizontal bar cursor) + =(setq cursor-type 'hbar)= + A horizontal bar at the bottom of the cell. +** TODO 7.5 Cursor on fringe when no glyph is present + Move the cursor to an empty line at the end of the buffer. + The cursor must appear in the fringe, not disappear. +** TODO 7.6 Cursor adapts to text-scale + Change text-scale and verify that the cursor adapts to the new cell + size. +** TODO 7.7 Invisible cursor (blink off state) + With blink-cursor-mode active, verify that during the "off" phase + the cursor disappears completely. +** TODO 7.8 [Metal] BLOCK cursor — no animation effect + =(setq gpu-cursor-animation 'block)= + The cursor must be static with no trail or animation when moving. +** TODO 7.9 [Metal] SPRING cursor + =(setq gpu-cursor-animation 'spring)= + The cursor must slide to the new position with critical damping + (~150ms settling time). +** TODO 7.10 [Metal] TORPEDO cursor (trail) + =(setq gpu-cursor-animation 'torpedo)= + Moving the cursor quickly must leave a fading trail behind it. +** TODO 7.11 [Metal] SONICBOOM, RIPPLE, PIXIEDUST effects on large jumps + Jump with =M-g g= to a distant line. Verify the burst/ripple/dust + effect appears around the destination. +** TODO 7.12 [Metal] Effect suppression during typing + =(setq gpu-cursor-effects-while-typing nil)= + Typing text rapidly must not trigger cursor animation effects on + every character insertion. + +* 8. Fringe +** TODO 8.1 Line continuation arrows + Enable truncate-lines and create a very long line. Continuation + arrows must appear in the right fringe. +** TODO 8.2 Empty-line indicator + Move past the last line of a buffer with trailing empty lines. + The tilde indicator must appear in the left fringe. +** TODO 8.3 Truncation arrows (left/right) + In a buffer with hscroll, truncation arrows must appear in the + appropriate fringe on each side. +** TODO 8.4 Custom fringe bitmap + =(define-fringe-bitmap 'my-bm [8 4 2 1 2 4 8] nil nil 'center)= + The bitmap must render at the correct position and color. +** TODO 8.5 Fringe background color + =(set-face-background 'fringe "yellow")= + The fringe area must have a yellow background. +** TODO 8.6 Fringe bitmap does not bleed onto the mode-line + With a long buffer, scroll to the end. The 72px empty-line indicator + must not overlap the mode-line. +** TODO 8.7 Overlay bitmap does not erase underlying content + Bitmaps with the overlay flag must be drawn on top of existing content + without clearing the fringe background first. + +* 9. Images +** TODO 9.1 Opaque PNG + =(insert-image (create-image "/path/to/opaque.png"))= + The image must appear with correct colors. +** TODO 9.2 PNG with alpha channel + A PNG image with transparency. The Emacs background must show through + the transparent areas. +** TODO 9.3 JPEG + A JPEG image must appear without compression artifacts added by the + backend. +** TODO 9.4 SVG + =(insert-image (create-image "/path/to/image.svg" 'svg))= + The vector image must render sharply. +** TODO 9.5 Static GIF (first frame) + A static GIF must display correctly (the backend always shows the first + frame in image-mode). +** TODO 9.6 Image with :scale + =(create-image ... :scale 2.0)= + The image must appear twice as large as the original. +** TODO 9.7 Image with :rotation + =(create-image ... :rotation 90)= + The image must appear rotated 90 degrees. +** TODO 9.8 Image slice (UV subrect) + =(insert-image img nil nil '(0 0 50 50))= + Only the specified crop of the image must be visible. +** TODO 9.9 Image with :relief + =(create-image ... :relief 2)= + The image must have a raised-relief border around it. +** TODO 9.10 Image with :margin + The image must have correct spacing relative to adjacent text. +** TODO 9.11 Image cache (same image inserted twice) + Insert the same image in two different places in the buffer. Both + occurrences must be identical. +** TODO 9.12 Inline images (iimage-mode) + =(iimage-mode)= in a directory with referenced images. + Inline images must render correctly. + +* 10. Scrolling and copy_region +** TODO 10.1 Smooth scroll down (C-n, C-v) + Existing text must slide upward without artifacts. New content must + appear cleanly at the bottom. +** TODO 10.2 Smooth scroll up (C-p, M-v) + Same as above but in the opposite direction. +** TODO 10.3 scroll_run: pixel-perfect parity with NS/cairo + The result of scrolling N lines must be identical to a fresh redraw + of the same lines. (See the run-scroll.sh harness.) +** TODO 10.4 Overlapping copy_region (scroll with overlap) + A 1-line scroll where src and dst overlap. Content must move correctly + (bounced through the scratch texture). +** TODO 10.5 [Metal] Scroll with EASE_OUT_QUAD easing + =(setq gpu-scroll-easing 'ease-out-quad)= + The scroll must show a smooth deceleration at the end. +** TODO 10.6 [Metal] Scroll with SPRING easing + =(setq gpu-scroll-easing 'spring)= + The scroll must slightly overshoot and bounce back. +** TODO 10.7 [Metal] Instant scroll (no easing) + =(setq gpu-scroll-easing 'none)= + The scroll must be instant, matching the baseline. +** TODO 10.8 Horizontal scroll (hscroll) + A long line with truncate-lines. =C-e= to jump to the end. + Content must scroll horizontally without artifacts. +** TODO 10.9 Glyph shift on insert (gfx_shift_glyphs_for_insert) + With overwrite-mode disabled, insert a character in the middle of a + line. Text to the right must shift cleanly. + +* 11. Emacs bars +** TODO 11.1 Basic mode-line + The mode-line must show the buffer name, major mode and line/column + info with correct foreground and background colors. +** TODO 11.2 Mode-line with custom face + =(set-face-background 'mode-line "navy")= + The mode-line must have a navy-blue background. +** TODO 11.3 Inactive mode-line (another window) + The mode-line of inactive windows must use mode-line-inactive face. +** TODO 11.4 Header-line + =(setq header-line-format "Header")= + A bar must appear above the text area. +** TODO 11.5 Tab-line (per-window) + =(global-tab-line-mode)= + Per-window tabs must render with relief and hover highlighting. +** TODO 11.6 Tab-bar (per-frame) + =(tab-bar-mode)= + Per-frame tabs must appear at the top without flickering when + switching tabs. +** TODO 11.7 Tool-bar + =(tool-bar-mode)= + Tool-bar icons must have raised relief and respond to hover without + flickering. +** TODO 11.8 Scroll-bar + =(scroll-bar-mode)= + The scroll-bar must appear on the right and update its position when + scrolling. + +* 12. Windows and splits +** TODO 12.1 Vertical split (C-x 2) + Two stacked windows. The dividing line must be visible and the + content of each window must not overlap. +** TODO 12.2 Horizontal split (C-x 3) + Two side-by-side windows. The vertical divider must be visible. +** TODO 12.3 Window divider >= 3px (first/last pixel colors) + =(setq window-divider-default-bottom-width 5)= + A 5px divider must have distinct colors on the first and last pixel. +** TODO 12.4 Resize windows with C-x ^, C-x {, C-x } + Windows must resize cleanly without rendering artifacts. +** TODO 12.5 Internal frame border (internal-border-width) + =(set-frame-parameter nil 'internal-border-width 10)= + A 10px border must appear with the correct color. +** TODO 12.6 Multiple frames (make-frame) + Create a second frame. Both frames must render independently without + interfering with each other. +** TODO 12.7 Child frame (corfu, company popup) + Open a completion popup. The child frame must appear above the text + without artifacts on its edges. + +* 13. Display properties and overlays +** TODO 13.1 Invisible text + =(put-text-property 1 5 'invisible t)= + Invisible text must not appear on screen but the cursor must skip + over it correctly. +** TODO 13.2 Display string (replacing text) + =(put-text-property 1 5 'display "REPLACED")= + The original text must be replaced by "REPLACED". +** TODO 13.3 Inline image via display property + =(put-text-property 1 2 'display (create-image ...))= + The image must appear in place of the character. +** TODO 13.4 Display space (:width, :align-to) + =(put-text-property 1 2 'display '(space :width 10))= + A space of 10 columns must appear. +** TODO 13.5 Overlay with face + =(overlay-put (make-overlay 1 10) 'face '(:background "yellow"))= + The overlay text must have a yellow background. +** TODO 13.6 Overlay with after-string / before-string + The extra string must appear at the correct position without + unexpectedly displacing surrounding text. +** TODO 13.7 Line numbers (display-line-numbers-mode) + =(display-line-numbers-mode)= + Line numbers must appear right-aligned in the left margin. +** TODO 13.8 hl-line-mode + =(hl-line-mode)= + The cursor line must have a distinct highlight background. +** TODO 13.9 Margins (left-margin-width, right-margin-width) + =(setq left-margin-width 4)= + A 4-column blank left margin must appear. +** TODO 13.10 Line wrapping (word-wrap, visual-line-mode) + Long lines that wrap. Wrapping must occur at word boundaries and + continuation lines must be correctly indented. +** TODO 13.11 Truncate lines + =(setq truncate-lines t)= + Long lines must be cut with a truncation arrow in the fringe. + +* 14. Common major modes (real-world integration) +** TODO 14.1 org-mode with headings, code blocks and tables + Open a real .org file. Headings with colors, code blocks with + background, tables with lines. No artifacts. +** TODO 14.2 dired + =(dired "~")= List a directory. File names, permissions and dates + must align correctly. +** TODO 14.3 magit (git interface) + Open magit-status. Diff hunks with add/remove colors, foldable + sections. No flickering when expanding/collapsing. +** TODO 14.4 corfu / company (completion popup) + In a code buffer, trigger completion. The popup (child frame or + overlay) must appear without flickering and with correct highlight + on the selected candidate. +** TODO 14.5 vertico / ivy / helm (minibuffer with candidates) + Open =M-x= with vertico. The candidate list must render cleanly + with the current item highlighted. +** TODO 14.6 elfeed (RSS reader with links and mixed fonts) + Open an article in elfeed. Links must have underlines and paragraph + font must be correct. +** TODO 14.7 treemacs / neotree (file tree with icons) + Open the file tree. Unicode/emoji icons must render at the correct + column. + +* 15. HiDPI / Retina +** TODO 15.1 [macOS] Retina @2x: atlas rasterized at physical resolution + On a Retina display, open the app and capture with gpu-capture-frame. + The capture size must be exactly double the logical size. +** TODO 15.2 [macOS] Text sharpness @2x + Zoom in 6x on the capture. Glyphs must show per-physical-pixel + antialiasing, not blurry upscaling from @1x. +** TODO 15.3 [macOS] NS vs Metal parity @2x + Compare Metal vs NS capture (EMACS_GPU_DISABLE=1) with the harness. + fuzz5%=~0.5%, fuzz20%=~0.05% are acceptable (AA on glyph edges). +** TODO 15.4 [Linux] GL_SCALE > 1 (supersampling) + =GL_SCALE=2 emacs -Q= + Text must appear sharper than at scale 1x. + +* 16. Buffer-switch transition (crossfade) +** TODO 16.1 Crossfade when switching buffers + =(setq gpu-buffer-transitions t)= + On =C-x b=, the previous buffer must fade out smoothly as the + new content appears. +** TODO 16.2 Configurable duration + =(setq gpu-buffer-transition-duration 0.5)= + The crossfade must last approximately 0.5 seconds. +** TODO 16.3 No artifacts after the fade + When the fade ends, the new buffer must appear perfectly clean, + with no residue from the previous buffer. +** TODO 16.4 Pump cadence during fade (60Hz) + The fade must be smooth (~60fps). It must not look like 30fps or + show frame jumps. + +* 17. Inline video (macOS Metal only) +** TODO 17.1 Insert video with gpu-video-insert + =(gpu-video-insert "/path/to/video.mp4")= + A placeholder must appear in the buffer. +** TODO 17.2 Playback: video appears at the placeholder position + When playback starts, the video must appear over the placeholder, + not at another position in the frame. +** TODO 17.3 Video follows scrolling + Scroll the buffer while the video is playing. The video must move + with the text, not remain fixed on screen. +** TODO 17.4 Video clipped to window interior + The video must not bleed over the mode-line or other windows. +** TODO 17.5 Stop with gpu-video-stop + The placeholder must remain and the video must stop playing. +** TODO 17.6 Stable 30fps cadence (no jumps, no timer starvation) + The median interval between frames must be ~33ms with p90 < 40ms. + +* 18. GL backend / Linux (platform-specific) +** TODO 18.1 Partial present (EGL_EXT_buffer_age) + Scroll in a large buffer. Only the changed region must be submitted + (verify with GL_LOG_PRESENT=1 that damage is partial). +** TODO 18.2 No flash from stale XDBE buffers + =inhibit-double-buffering= must be set for GL frames. No startup + content should appear during buffer transitions. +** TODO 18.3 Scroll parity: cairo vs GPU + Run run-bench.sh and compare page-scroll and line-scroll FPS against + the cairo baseline. page-scroll must be at parity (1.0x), + full-redraw > 1.0x. +** TODO 18.4 Resize under Xvfb without black bands + Resize the window under Xvfb + openbox. The resized frame must not + show black bands or text from the previous size. +** TODO 18.5 buffer-age == 0: re-query surface size + After a resize, the first frame with age==0 must use the new size, + not the cached stale size. +** TODO 18.6 EGL surfaceless headless (GL_FORCE_SURFACELESS=1) + The comparison harness under Xvfb must still produce the correct + FBO output and acceptable diffs. + +* 19. Edge cases and performance +** TODO 19.1 Buffer with 100k lines + Open a large file (logs). Scrolling must be fluid with no perceptible + lag. +** TODO 19.2 Many simultaneous overlays (hl-line + multiple packages) + With hl-line, doom-modeline, corfu and company all active, verify + there is no flickering in the mode-line or windows. +** TODO 19.3 Theme change (M-x load-theme) + Switch from a light to a dark theme. All colors must update without + artifacts or remnants of the previous theme. +** TODO 19.4 Atlas overflow (glyph atlas reset) + Use many different fonts and sizes until the atlas fills and resets. + Text must continue to display correctly after the reset. +** TODO 19.5 Garbaged frame (iconify and restore) + Iconify and then restore the window. Content must be fully and + correctly redrawn. +** TODO 19.6 MTL_LOG_SEQ=1 — no critical errors + =MTL_LOG_SEQ=1 emacs -Q= and perform normal operations. Logs must + not show unexpected "outside cycle" entries or presents without a + corresponding draw. + +* 20. Configuration and knobs +** TODO 20.1 gpu-status reports the active backend + =(gpu-status)= must report "Metal" or "OpenGL" according to the + platform. +** TODO 20.2 EMACS_GPU_DISABLE=1 disables the backend + Start with that env var. Rendering must be identical to vanilla Emacs + (NS on macOS, cairo on Linux). +** TODO 20.3 gpu-enable-for-frame / gpu-disable-for-frame + Disable GPU rendering on a specific frame. That frame must use the + native renderer while others remain on GPU. +** TODO 20.4 gpu-capture-frame (macOS) + =(gpu-capture-frame "/tmp/capture.png")= + The capture must match what is visible on screen. +** TODO 20.5 Warm glyph cache on startup (no blink on first draw) + Open Emacs and switch tabs immediately. The first redraw must not + show a blank frame while ASCII is being rasterized into the atlas. + +* Platform notes + - Items marked [Metal] apply only on macOS with the Metal driver. + - Items marked [Linux] or [GL] apply only with the OpenGL/EGL driver. + - Unmarked items must be verified on both platforms. + - The comparison baseline is always: same binary with EMACS_GPU_DISABLE=1.