Skip to main content

max / shop

Keep a machine without Vulkan in a working terminal Two ways the surface setup could refuse to come up on a host it had never run on. The backend list was Vulkan alone, so a fresh install or a VM with no driver got an adapter error and no window at all. And the present mode was Mailbox unconditionally, which is a validation failure rather than a downgrade on a driver that never advertised it. Add GL behind Vulkan and pick Fifo when Mailbox is absent. wgpu ranks Vulkan first on its own, so nothing changes where it works. The gles feature comes with it: Backends::GL finds no adapter without it, so the flag and the feature move together. Metal goes, having never been selectable in a Wayland-only terminal. The chosen mode and format are logged, since they are the first thing worth knowing about a machine that renders wrong.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-12 15:36 UTC
Signed with PGP, not checked
Commit: 183ce3d0d6698ef34be98b7dd9d474eb60fbfb15
Parent: f3fe6d5
3 files changed, +260 insertions, -57 deletions
M Cargo.lock +235 -54
@@ -137,13 +137,10 @@
137 137 checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
138 138
139 139 [[package]]
140 - name = "block2"
141 - version = "0.6.2"
140 + name = "bumpalo"
141 + version = "3.20.3"
142 142 source = "registry+https://github.com/rust-lang/crates.io-index"
143 - checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
144 - dependencies = [
145 - "objc2",
146 - ]
143 + checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
147 144
148 145 [[package]]
149 146 name = "bytemuck"
@@ -423,6 +420,30 @@
423 420 "yeslogic-fontconfig-sys",
424 421 ]
425 422
423 + [[package]]
424 + name = "futures-core"
425 + version = "0.3.34"
426 + source = "registry+https://github.com/rust-lang/crates.io-index"
427 + checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
428 +
429 + [[package]]
430 + name = "futures-task"
431 + version = "0.3.34"
432 + source = "registry+https://github.com/rust-lang/crates.io-index"
433 + checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
434 +
435 + [[package]]
436 + name = "futures-util"
437 + version = "0.3.34"
438 + source = "registry+https://github.com/rust-lang/crates.io-index"
439 + checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
440 + dependencies = [
441 + "futures-core",
442 + "futures-task",
443 + "pin-project-lite",
444 + "slab",
445 + ]
446 +
426 447 [[package]]
427 448 name = "getrandom"
428 449 version = "0.4.3"
@@ -434,6 +455,38 @@
434 455 "r-efi",
435 456 ]
436 457
458 + [[package]]
459 + name = "gl_generator"
460 + version = "0.14.0"
461 + source = "registry+https://github.com/rust-lang/crates.io-index"
462 + checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
463 + dependencies = [
464 + "khronos_api",
465 + "log",
466 + "xml-rs",
467 + ]
468 +
469 + [[package]]
470 + name = "glow"
471 + version = "0.17.0"
472 + source = "registry+https://github.com/rust-lang/crates.io-index"
473 + checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5"
474 + dependencies = [
475 + "js-sys",
476 + "slotmap",
477 + "wasm-bindgen",
478 + "web-sys",
479 + ]
480 +
481 + [[package]]
482 + name = "glutin_wgl_sys"
483 + version = "0.6.1"
484 + source = "registry+https://github.com/rust-lang/crates.io-index"
485 + checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e"
486 + dependencies = [
487 + "gl_generator",
488 + ]
489 +
437 490 [[package]]
438 491 name = "gpu-allocator"
439 492 version = "0.28.0"
@@ -554,6 +607,62 @@
554 607 source = "registry+https://github.com/rust-lang/crates.io-index"
555 608 checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
556 609
610 + [[package]]
611 + name = "jni-sys"
612 + version = "0.3.1"
613 + source = "registry+https://github.com/rust-lang/crates.io-index"
614 + checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
615 + dependencies = [
616 + "jni-sys 0.4.1",
617 + ]
618 +
619 + [[package]]
620 + name = "jni-sys"
621 + version = "0.4.1"
622 + source = "registry+https://github.com/rust-lang/crates.io-index"
623 + checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
624 + dependencies = [
625 + "jni-sys-macros",
626 + ]
627 +
628 + [[package]]
629 + name = "jni-sys-macros"
630 + version = "0.4.1"
631 + source = "registry+https://github.com/rust-lang/crates.io-index"
632 + checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
633 + dependencies = [
634 + "quote",
635 + "syn 2.0.119",
636 + ]
637 +
638 + [[package]]
639 + name = "js-sys"
640 + version = "0.3.104"
641 + source = "registry+https://github.com/rust-lang/crates.io-index"
642 + checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
643 + dependencies = [
644 + "cfg-if",
645 + "futures-util",
646 + "wasm-bindgen",
647 + ]
648 +
649 + [[package]]
650 + name = "khronos-egl"
651 + version = "6.0.0"
652 + source = "registry+https://github.com/rust-lang/crates.io-index"
653 + checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
654 + dependencies = [
655 + "libc",
656 + "libloading",
657 + "pkg-config",
658 + ]
659 +
660 + [[package]]
661 + name = "khronos_api"
662 + version = "3.1.0"
663 + source = "registry+https://github.com/rust-lang/crates.io-index"
664 + checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
665 +
557 666 [[package]]
558 667 name = "kittygfx"
559 668 version = "0.0.0"
@@ -710,6 +819,15 @@
710 819 "thiserror",
711 820 ]
712 821
822 + [[package]]
823 + name = "ndk-sys"
824 + version = "0.6.0+11769913"
825 + source = "registry+https://github.com/rust-lang/crates.io-index"
826 + checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
827 + dependencies = [
828 + "jni-sys 0.3.1",
829 + ]
830 +
713 831 [[package]]
714 832 name = "nix"
715 833 version = "0.31.3"
@@ -761,18 +879,6 @@
761 879 "objc2",
762 880 ]
763 881
764 - [[package]]
765 - name = "objc2-core-graphics"
766 - version = "0.3.2"
767 - source = "registry+https://github.com/rust-lang/crates.io-index"
768 - checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
769 - dependencies = [
770 - "dispatch2",
771 - "objc2",
772 - "objc2-core-foundation",
773 - "objc2-io-surface",
774 - ]
775 -
776 882 [[package]]
777 883 name = "objc2-encode"
778 884 version = "4.1.0"
@@ -790,17 +896,6 @@
790 896 "objc2-core-foundation",
791 897 ]
792 898
793 - [[package]]
794 - name = "objc2-io-surface"
795 - version = "0.3.2"
796 - source = "registry+https://github.com/rust-lang/crates.io-index"
797 - checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
798 - dependencies = [
799 - "bitflags",
800 - "objc2",
801 - "objc2-core-foundation",
802 - ]
803 -
804 899 [[package]]
805 900 name = "objc2-metal"
806 901 version = "0.3.2"
@@ -808,7 +903,6 @@
808 903 checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
809 904 dependencies = [
810 905 "bitflags",
811 - "block2",
812 906 "objc2",
813 907 "objc2-foundation",
814 908 ]
@@ -822,7 +916,6 @@
822 916 "bitflags",
823 917 "objc2",
824 918 "objc2-core-foundation",
825 - "objc2-core-graphics",
826 919 "objc2-foundation",
827 920 "objc2-metal",
828 921 ]
@@ -1062,6 +1155,12 @@
1062 1155 "windows-sys",
1063 1156 ]
1064 1157
1158 + [[package]]
1159 + name = "rustversion"
1160 + version = "1.0.23"
1161 + source = "registry+https://github.com/rust-lang/crates.io-index"
1162 + checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
1163 +
1065 1164 [[package]]
1066 1165 name = "scoped-tls"
1067 1166 version = "1.0.1"
@@ -1249,6 +1348,15 @@
1249 1348 source = "registry+https://github.com/rust-lang/crates.io-index"
1250 1349 checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1251 1350
1351 + [[package]]
1352 + name = "slotmap"
1353 + version = "1.1.1"
1354 + source = "registry+https://github.com/rust-lang/crates.io-index"
1355 + checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
1356 + dependencies = [
1357 + "version_check",
1358 + ]
1359 +
1252 1360 [[package]]
1253 1361 name = "smallvec"
1254 1362 version = "1.15.2"
@@ -1504,6 +1612,57 @@
1504 1612 source = "registry+https://github.com/rust-lang/crates.io-index"
1505 1613 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
1506 1614
1615 + [[package]]
1616 + name = "version_check"
1617 + version = "0.9.5"
1618 + source = "registry+https://github.com/rust-lang/crates.io-index"
1619 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1620 +
1621 + [[package]]
1622 + name = "wasm-bindgen"
1623 + version = "0.2.127"
1624 + source = "registry+https://github.com/rust-lang/crates.io-index"
1625 + checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
1626 + dependencies = [
1627 + "cfg-if",
1628 + "once_cell",
1629 + "rustversion",
1630 + "wasm-bindgen-macro",
1631 + "wasm-bindgen-shared",
1632 + ]
1633 +
1634 + [[package]]
1635 + name = "wasm-bindgen-macro"
1636 + version = "0.2.127"
1637 + source = "registry+https://github.com/rust-lang/crates.io-index"
1638 + checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
1639 + dependencies = [
1640 + "quote",
1641 + "wasm-bindgen-macro-support",
1642 + ]
1643 +
1644 + [[package]]
1645 + name = "wasm-bindgen-macro-support"
1646 + version = "0.2.127"
1647 + source = "registry+https://github.com/rust-lang/crates.io-index"
1648 + checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
1649 + dependencies = [
1650 + "bumpalo",
1651 + "proc-macro2",
1652 + "quote",
1653 + "syn 2.0.119",
1654 + "wasm-bindgen-shared",
1655 + ]
1656 +
1657 + [[package]]
1658 + name = "wasm-bindgen-shared"
1659 + version = "0.2.127"
1660 + source = "registry+https://github.com/rust-lang/crates.io-index"
1661 + checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
1662 + dependencies = [
1663 + "unicode-ident",
1664 + ]
1665 +
1507 1666 [[package]]
1508 1667 name = "wayland-backend"
1509 1668 version = "0.3.16"
@@ -1622,9 +1781,20 @@
1622 1781 dependencies = [
1623 1782 "dlib",
1624 1783 "log",
1784 + "once_cell",
1625 1785 "pkg-config",
1626 1786 ]
1627 1787
1788 + [[package]]
1789 + name = "web-sys"
1790 + version = "0.3.104"
1791 + source = "registry+https://github.com/rust-lang/crates.io-index"
1792 + checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
1793 + dependencies = [
1794 + "js-sys",
1795 + "wasm-bindgen",
1796 + ]
1797 +
1628 1798 [[package]]
1629 1799 name = "wgpu"
1630 1800 version = "30.0.0"
@@ -1675,7 +1845,7 @@
1675 1845 "rustc-hash",
1676 1846 "smallvec",
1677 1847 "thiserror",
1678 - "wgpu-core-deps-apple",
1848 + "wgpu-core-deps-emscripten",
1679 1849 "wgpu-core-deps-windows-linux-android",
1680 1850 "wgpu-hal",
1681 1851 "wgpu-naga-bridge",
@@ -1683,10 +1853,10 @@
1683 1853 ]
1684 1854
1685 1855 [[package]]
1686 - name = "wgpu-core-deps-apple"
1856 + name = "wgpu-core-deps-emscripten"
1687 1857 version = "30.0.0"
1688 1858 source = "registry+https://github.com/rust-lang/crates.io-index"
1689 - checksum = "3283b6da70d7fc892de95840215aa36381b5d0cbc479e88ee2b173c7b992b225"
1859 + checksum = "85dcf2b2e5c2afb9eda64c570a87a4e570304bf39e52eddbaaf222d655b656ad"
1690 1860 dependencies = [
1691 1861 "wgpu-hal",
1692 1862 ]
@@ -1710,23 +1880,22 @@
1710 1880 "arrayvec",
1711 1881 "ash",
1712 1882 "bitflags",
1713 - "block2",
1714 1883 "bytemuck",
1715 1884 "cfg-if",
1716 1885 "cfg_aliases",
1886 + "glow",
1887 + "glutin_wgl_sys",
1717 1888 "gpu-allocator",
1718 1889 "hashbrown 0.17.1",
1890 + "js-sys",
1891 + "khronos-egl",
1719 1892 "libc",
1720 1893 "libloading",
1721 1894 "log",
1722 1895 "naga",
1723 1896 "naga-types",
1897 + "ndk-sys",
1724 1898 "objc2",
1725 - "objc2-core-foundation",
1726 - "objc2-core-graphics",
1727 - "objc2-foundation",
1728 - "objc2-metal",
1729 - "objc2-quartz-core",
1730 1899 "ordered-float",
1731 1900 "parking_lot",
1732 1901 "portable-atomic",
@@ -1738,9 +1907,13 @@
1738 1907 "smallvec",
1739 1908 "static_assertions",
1740 1909 "thiserror",
1910 + "wasm-bindgen",
1911 + "wayland-sys",
1912 + "web-sys",
1741 1913 "wgpu-naga-bridge",
1742 1914 "wgpu-types",
1743 1915 "windows",
1916 + "windows-result",
1744 1917 ]
1745 1918
1746 1919 [[package]]
@@ -1761,10 +1934,12 @@
1761 1934 dependencies = [
1762 1935 "bitflags",
1763 1936 "bytemuck",
1937 + "js-sys",
1764 1938 "log",
1765 1939 "naga-types",
1766 1940 "raw-window-handle",
1767 1941 "static_assertions",
1942 + "web-sys",
1768 1943 ]
1769 1944
1770 1945 [[package]]
@@ -1918,6 +2093,12 @@
1918 2093 "bytemuck",
1919 2094 ]
1920 2095
2096 + [[package]]
2097 + name = "xml-rs"
2098 + version = "0.8.29"
2099 + source = "registry+https://github.com/rust-lang/crates.io-index"
2100 + checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
2101 +
1921 2102 [[package]]
1922 2103 name = "yazi"
1923 2104 version = "0.2.1"
@@ -1967,6 +2148,10 @@
1967 2148 source = "registry+https://github.com/rust-lang/crates.io-index"
1968 2149 checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
1969 2150
2151 + [[patch.unused]]
2152 + name = "docengine"
2153 + version = "0.5.0"
2154 +
1970 2155 [[patch.unused]]
1971 2156 name = "synckit-client"
1972 2157 version = "0.8.0"
@@ -1975,18 +2160,6 @@
1975 2160 name = "synckit-config"
1976 2161 version = "0.2.0"
1977 2162
1978 - [[patch.unused]]
1979 - name = "kberg"
1980 - version = "0.1.0"
1981 -
1982 - [[patch.unused]]
1983 - name = "painhours"
1984 - version = "0.1.0"
1985 -
1986 - [[patch.unused]]
1987 - name = "tagtree"
1988 - version = "0.4.0"
1989 -
1990 2163 [[patch.unused]]
1991 2164 name = "quasi-axum"
1992 2165 version = "0.1.0"
@@ -2012,5 +2185,13 @@
2012 2185 version = "0.1.0"
2013 2186
2014 2187 [[patch.unused]]
2015 - name = "docengine"
2016 - version = "0.5.0"
2188 + name = "kberg"
2189 + version = "0.1.0"
2190 +
2191 + [[patch.unused]]
2192 + name = "painhours"
2193 + version = "0.1.0"
2194 +
2195 + [[patch.unused]]
2196 + name = "tagtree"
2197 + version = "0.4.0"
M Cargo.toml +5 -1
@@ -22,7 +22,11 @@
22 22 xkeysym = "0.2"
23 23 calloop = "0.14"
24 24 calloop-wayland-source = "0.4"
25 - wgpu = { version = "30", default-features = false, features = ["wgsl", "vulkan", "metal"] }
25 + # Two backends, both reachable: vulkan is the one shop is built for, gles is the
26 + # fallback that keeps a machine with no Vulkan driver in a working terminal.
27 + # `Backends::GL` finds nothing without this feature, so the pair moves together.
28 + # No metal — shop is Wayland-only, so that backend can never be selected.
29 + wgpu = { version = "30", default-features = false, features = ["wgsl", "vulkan", "gles"] }
26 30 raw-window-handle = "0.6"
27 31 pollster = "1"
28 32 anyhow = "1"
@@ -220,7 +220,13 @@
220 220
221 221 let (raw_display, raw_window) = shop_wayland::raw_handles(&conn, &chrome);
222 222 let mut instance_desc = wgpu::InstanceDescriptor::new_without_display_handle();
223 - instance_desc.backends = wgpu::Backends::VULKAN;
223 + // Vulkan is what shop is built for and what it gets on every machine with a
224 + // driver worth having. GL is here so the machine without one still opens a
225 + // terminal: a text grid is not demanding, and a slow shop beats no shop on
226 + // a fresh install, in a VM, or behind a driver that has not landed yet.
227 + // wgpu ranks Vulkan above GL on its own, so listing both costs nothing
228 + // where Vulkan works.
229 + instance_desc.backends = wgpu::Backends::VULKAN | wgpu::Backends::GL;
224 230 let instance = wgpu::Instance::new(instance_desc);
225 231
226 232 // SAFETY: raw_display / raw_window reference wayland pointers owned by
@@ -245,6 +251,18 @@
245 251
246 252 let caps = surface.get_capabilities(&adapter);
247 253 let format = caps.formats[0];
254 + // Mailbox lets a burst of output land on the next compositor tick instead
255 + // of waiting a whole frame for it, which is what keeps fast scrollback
256 + // feeling immediate. Not every driver offers it — the GL backend above
257 + // often does not — and configuring a surface with a mode it did not
258 + // advertise is a validation failure, not a downgrade. Fifo is the one mode
259 + // always present, so it is what we fall back to.
260 + let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) {
261 + wgpu::PresentMode::Mailbox
262 + } else {
263 + wgpu::PresentMode::Fifo
264 + };
265 + info!(?present_mode, ?format, "surface configured");
248 266 let surface_config = wgpu::SurfaceConfiguration {
249 267 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
250 268 format,
@@ -254,7 +272,7 @@
254 272 width: INITIAL.0,
255 273 height: INITIAL.1,
256 274 desired_maximum_frame_latency: 2,
257 - present_mode: wgpu::PresentMode::Mailbox,
275 + present_mode,
258 276 };
259 277 let text = TextRenderer::new(&device, &queue, format, font_data, FONT_PX)?;
260 278 let images = ImageRenderer::new(&device, format);