🌐 English · Русский
Proof of Concept: rendering a DRM/KMS dma-buf framebuffer through EGL.
This is not a finished product. It is a PoC that demonstrates one specific thing end-to-end: taking a dma-buf framebuffer, importing it into a headless EGL/GBM context, letting the GPU detile/convert it, reading the pixels back and writing them to PNG. The KMS screen-capture path is built on top of that core, but the point of the project is the dma-buf → EGL → pixels interaction itself.
Originally explored as part of a diploma project focused on DRM/KMS internals; the capture idea is modeled on GPU Screen Recorder (flathub).
dma-buf fd ──▶ EGLImage (EGL_LINUX_DMA_BUF_EXT, with modifiers)
──▶ external OES texture
──▶ render into an RGBA8 FBO (GPU detile + format convert)
──▶ glReadPixels
──▶ PNG (libpng)
Runs headless on a render node, so it needs no DRM master and no window system. The same code path serves two entry points:
ds-selftest— feeds a synthetic dma-buf (a GBM buffer painted with a known pattern) through the pipeline. Proves the whole flow works without root and without a display.ds-capture— feeds real KMS framebuffers (the compositor's scanned-out planes) through the same pipeline to produce a screenshot.
Extra deps for the PoC: libepoxy, mesa/libgbm, libpng, libjpeg
(Arch: sudo pacman -S libepoxy libpng libjpeg-turbo mesa).
Both tools take -f=png (default, lossless) or -f=jpg (lossy, alpha dropped)
to pick the output format.
Round-trips a known pattern (top red / bottom blue) through the exact dma-buf
import path and checks the read-back pixels. Writes selftest.png to the
current directory (or to the path given as an argument).
make selftest
./ds-selftest # -> selftest.png
./ds-selftest out.png # custom path
./ds-selftest -f=jpg # -> selftest.jpg
make capture
sudo ./ds-capture screenshot # -> screenshot-<N>[-cursor].png per plane
sudo ./ds-capture -f=jpg screenshot # -> screenshot-<N>[-cursor].jpg
Why root: drmModeGetFB2 zeroes the GEM handles of other clients'
framebuffers unless the caller is the DRM master or has CAP_SYS_ADMIN. A
Wayland compositor holds the master and won't give it up, so capturing another
client's framebuffer requires privilege. (In a bare TTY with no compositor you
become master on open(), which is why capture "worked" only there before.)
make # builds drm_test from src/main.c + src/mydrm.c + src/kms.c
Technical notes live in docs/ — the end-to-end pipeline,
the DRM/KMS and EGL primers, design choices (what's swappable at each step), the
original bugs, and the correct architecture. Start at
docs/README.md.
| File | Role |
|---|---|
src/egl_capture.c / include/egl_capture.h |
the PoC core — dma-buf → EGL → RGBA → PNG |
src/selftest.c |
synthetic dma-buf driver (ds-selftest) |
src/capture_main.c |
real KMS-capture driver (ds-capture) |
src/kms.c / include/kms.h |
KMS plane enumeration, framebuffer → dma-buf export |
src/mydrm.c, src/drmcheck.c |
early DRM/KMS experiments (not on the PoC path) |
Debian-like:
sudo apt install libdrm-dev gcc make build-essential
# for the PoC pipeline: libepoxy-dev libgbm-dev libpng-dev
Note on the video group: on a live session logind grants device access via
ACLs, but ds-capture needs root anyway (see above), so root already covers it.
groups # check
sudo usermod -aG video "username" # add
GNU General Public License v3.0 or later — see LICENSE. Copyright © 2026 Shibakov Nikita <tind.nik.28@gmail.com>.
GPL is a deliberate choice: any derivative work must also stay free and open-source under the GPL, so this code cannot be absorbed into a closed-source product (e.g. a proprietary DLP tool). Note that copyleft only guarantees the source stays open — by design it does not restrict the field of use.