A UEFI DXE driver that makes pre-boot text readable on HiDPI displays, without leaving native resolution.
Firmware text consoles draw a hardcoded 8×19 pixel font. On a 4K panel that means unreadably tiny text in every pre-boot surface that uses the Simple Text Output protocol — most notably the systemd-boot menu, whose only alternative (a low-resolution text mode) forces a resolution switch and the accompanying flicker on the way into the OS. Big Console replaces the console's renderer instead: the framebuffer stays at native resolution and the glyphs get bigger.
The systemd-boot menu on a 3840×2160 framebuffer, captured from the project's VM check. Each image is the same 1280×720 window at 1:1, so the glyphs compare at their real size.
| Stock console (8×19 cells) | Big Console (16×32 cells) | Big Console 2× (32×64 cells) |
|---|---|---|
![]() |
![]() |
![]() |
The whole frame, reduced to a third, shows how much of the panel each one uses; the stock menu is the speck in the middle.
| Stock console | Big Console | Big Console 2× |
|---|---|---|
![]() |
![]() |
![]() |
These come from the systemd-boot-menu check, which boots systemd-boot
from an ESP with the driver in its drivers directory, the way it is
deployed, and dumps the framebuffer once the menu is up.
A fork of TianoCore edk2's GraphicsConsoleDxe
(MdeModulePkg/Universal/Console/GraphicsConsoleDxe) with:
- An embedded high-resolution font. Glyph bitmaps are generated at build time from a PSF2 console font (Terminus 16×32 by default) and drawn directly by the driver. Characters the font lacks fall back to the platform's HII 8×19 glyphs, stretched to the cell.
- Integer magnification on top (
GLYPH_SCALE), for panels or viewing distances where even 16×32 is still too small. A 2× build (32×64 cells) is provided. On panels too small for an 80×25 grid of big cells the driver degrades to the stock 8×19 rendering path. - Console takeover. The driver binding registers at version
0xFFFFFFF0(the UEFI spec's platform-reserved band, above any IHV-range version a firmware console uses — edk2 binds at 0xa, AMI Aptio V at 0x10). At image start the driver evicts the incumbent console from every GOP handle, re-runs the binding contest it now wins, and re-syncs the system console's current mode. Loading the driver is sufficient; no platform modification and no explicit reconnect step is required.
The text-mode table keeps the UEFI-mandated 80×25 (and 80×50 where it fits), the stock driver's historical preset grids, and a computed full-screen mode — e.g. 240×67 on 3840×2160 with 16×32 cells.
From the moment the driver loads until the operating system takes over
the display, every pre-boot program that writes through the Simple Text
Output protocol renders in the big cells, whatever loaded it and from
wherever. That includes systemd-boot's menu, the UEFI shell, GRUB when
its output terminal is console (the usual state of a GRUB fetched
over the network), rEFInd in text mode, iPXE, shim and MokManager, the
firmware's own boot menu and network boot messages, and the few lines
the kernel's EFI stub prints. It does not include programs that draw
their own pixels through the graphics output protocol: GRUB's gfxterm
(what distribution configs select by default), rEFInd's graphical mode,
Limine, and Windows Boot Manager. The Linux console after boot is a
different renderer with its own font setting.
How early the driver loads decides how much of that it reaches:
- Loaded by another UEFI application. systemd-boot loads drivers
from
\EFI\systemd\drivers\on the ESP (the file must end inx64.efi, oraa64.efion AArch64), rEFInd from itsdrivers_x64(ordrivers_aa64) directory, and the UEFI shell onload. The effect starts there and covers that program and everything it chainloads afterwards. - Loaded by the firmware itself. A
Driver####boot variable (e.g.Driver0000, created withefibootmgr --driver --create ...) or a firmware setup option makes the driver active before the firmware's own screens, so the boot menu and network boot messages render big too.
Under Secure Boot the driver must be signed with a db-trusted key in either case; an unsigned driver is skipped and boot proceeds with the stock console.
The framebuffer stays at native resolution throughout; what a text mode
chooses is the grid. By default the console keeps the firmware's current
mode, normally the spec-mandated 80×25, so the big text sits as a
centered block; selecting the full-screen mode (systemd-boot's
console-mode max in loader.conf, or mode 240 67 in the shell at
3840×2160) fills the panel instead. On a firmware whose console also
includes a serial terminal (servers, and edk2's virt firmware for
AArch64), the console splitter publishes only the modes every device
supports, so the largest grid is the terminal's 160×42, drawn in the
big cells as a centered block.
x86-64 and AArch64 UEFI. On x86-64 the driver is verified on every
change against TianoCore edk2 (OVMF in QEMU at 3840×2160, both variants,
loaded from the UEFI shell) and is in use on one AMI Aptio V firmware,
loaded by systemd-boot. On AArch64 it is verified the same way against
edk2's ArmVirtQemu on an emulated virt machine, and has not been run
on real hardware. Firmware early-loading (Driver####) has not received
much testing on either. Other vendors' firmware should work by the UEFI
driver-binding rules the console takeover relies on, but has not been
tried.
Prebuilt drivers for both scale variants and both architectures are
attached to each release, with checksums, if you would rather not build
anything. Each release artifact is the store path CI built for the
tagged commit, so it is byte-identical to what nix build produces at
that tag.
Build (Nix): nix build .#big-console-dxe (or .#big-console-dxe-2x);
the AArch64 builds are .#big-console-dxe-aarch64 and
.#big-console-dxe-2x-aarch64, cross-compiled from any host. The output
is a single BigGraphicsConsoleDxe.efi, loaded by any of the paths
above.
nix flake check boots OVMF in QEMU at 3840×2160, loads the driver from a
UEFI shell, and asserts — from the mode table and framebuffer screendumps —
that the console was taken over, the mode grid matches the cell geometry,
and rendered text actually fills the tall cells (with a no-thin-stems check
for magnified builds). Both the 1× and 2× builds are exercised.
In CI the same nix flake check runs with the Nix store cached between
runs, since a cold runner builds the edk2 toolchain and both variants
before it can boot OVMF. A pull request that leaves .github/ alone is
checked by main's copy of the workflow, in main's context once its
own check completes, and adds its build to the shared cache; one that
changes the pipeline is checked by its own copy, under a cache only it
can see. The comments at the top of the two workflow files say why that
split is what makes the cache safe to write from a pull request.
What main builds, both driver variants included, is pushed to the
clhodapp cachix cache, signed with its key, so nix build at the same
pins downloads the driver instead of building the edk2 toolchain. That
cache skips paths its upstreams already hold, so using it means using
them too:
| Substituter | Public key |
|---|---|
https://clhodapp.cachix.org |
clhodapp.cachix.org-1:EW/0conxH0OQyo0o4ub/grdkFspholmQMSnQyj0vrZI= |
https://nix-community.cachix.org |
nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs= |
https://numtide.cachix.org |
numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE= |
The flake's nixConfig declares all three, so a direct nix build or
nix flake check here uses them once accepted: answer Nix's prompt, or
pass --accept-flake-config. A flake that consumes this one
as an input must add them to its own extra-substituters and
extra-trusted-public-keys; Nix does not carry an input's settings
into the consumer.
Everything in this repository is BSD-2-Clause-Patent, matching upstream
edk2: the driver sources (pkgs/big-console/big-console-dxe/src, forked
from edk2, per the headers in each file) and the Nix build scaffolding
and tests alike. See LICENSE for the terms and NOTICE for what came
from where.
The one exception is the font: the glyph bitmaps embedded in the built
driver are generated from Terminus and carry the SIL Open Font License
1.1. See LICENSES/OFL-1.1.txt.





