pygame: fbじゃなくてKMSDRM

 Wayland上で動かしているPygameのスクリプトが、Pi3ではメモリ不足でOOM Killerにやられてしまうので、WaylandのないLite版でフレームバッファ描画でメモリを節約して動作できないかということで試す。

 現在のPygame2では、バックエンドがSDL2になっていて、fbconを使うことはなくなって、KMSDRMを使うのだそうな。pygameからfb描画ができるってのは、相当アップデートしてない知識だわw

Can SDL2 still use the Linux Framebuffer? - SDL Development - Simple Directmedia Layer
https://discourse.libsdl.org/t/can-sdl2-still-use-the-linux-framebuffer/20083

 KMSDRM は、Kernel Mode Setting + Direct Rendering Managerを使う、SDL2向けの ディスプレイドライバ・バックエンド。

 KMS (Kernel Mode Setting)は、Linuxカーネルが直接ディスプレイの解像度や表示モードを管理する仕組み。

 DRM (Direct Rendering Manager)は、GPUやフレームバッファへのアクセスをカーネル経由で安全に行う仕組み。/dev/dri/card0 のようなデバイスファイルを介して描画する。

 KMSDRMドライバ (SDL2のvideo driver)は、SDL2 が KMS/DRM API を直接使ってフレームを描画するモード。fbcon のように /dev/fb0 へ書き込む旧式の方法より高速で、ハードウェアアクセラレーションも利用できる。

 では、試す。ベースは、bookwormで。ターゲットはPi3A。

 SDのオーバークロックはやっておく。

Pi3aのSDをオーバークロック|kinneko|pixivFANBOX
https://www.fanbox.cc/@kinneko/posts/8947033

fd@pi3b:~ $ sudo vi /boot/firmware/config.txt
dtoverlay=sdtweak,overclock_50=100
fd@pi3b:~ $ sudo reboot

 vc4-kms-v3dで動作しているので、GPUはKMS対応で動作している状態。

fd@pi3b:~ $ grep dtoverlay /boot/firmware/config.txt
dtoverlay=sdtweak,overclock_50=100
dtoverlay=vc4-kms-v3d
dtoverlay=dwc2,dr_mode=host

 pythonのバージョン。

fd@pi3b:~ $ python3 --version

 Python 3.11.2

 pygameはパッケージで入れる。

fd@pi3b:~ $ sudo apt install python3-pygame

 Reading package lists... Done

 Building dependency tree... Done

 Reading state information... Done

 The following additional packages will be installed:

 fontconfig fonts-freefont-ttf libasyncns0 libblas3 libcairo2 libdatrie1

 libdecor-0-0 libdecor-0-plugin-1-cairo libdrm-amdgpu1 libdrm-radeon1

 libfluidsynth3 libfribidi0 libgbm1 libgfortran5 libglapi-mesa libgraphite2-3

 libharfbuzz0b libinstpatch-1.0-2 libjack-jackd2-0 liblapack3 libllvm15

 libmodplug1 libmp3lame0 libmpg123-0 libopus0 libopusfile0 libpango-1.0-0

 libpangocairo-1.0-0 libpangoft2-1.0-0 libpixman-1-0 libportmidi0 libpulse0

 libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0 libsdl2-ttf-2.0-0

 libsensors-config libsensors5 libsndfile1 libthai-data libthai0

 libvorbisenc2 libvorbisfile3 libwayland-client0 libwayland-cursor0

 libwayland-egl1 libwayland-server0 libx11-xcb1 libxcb-dri2-0 libxcb-dri3-0

 libxcb-present0 libxcb-randr0 libxcb-render0 libxcb-shm0 libxcb-sync1

 libxcb-xfixes0 libxcursor1 libxfixes3 libxi6 libxkbcommon0 libxrandr2

 libxrender1 libxshmfence1 libxss1 libz3-4 mesa-libgallium python3-numpy

 timgm6mb-soundfont x11-common

 Suggested packages:

 jackd2 opus-tools pulseaudio xdg-utils lm-sensors gfortran python3-dev

 python3-pytest python-pygame-doc timidity fluid-soundfont-gm

 The following NEW packages will be installed:

 fontconfig fonts-freefont-ttf libasyncns0 libblas3 libcairo2 libdatrie1

 libdecor-0-0 libdecor-0-plugin-1-cairo libdrm-amdgpu1 libdrm-radeon1

 libfluidsynth3 libfribidi0 libgbm1 libgfortran5 libglapi-mesa libgraphite2-3

 libharfbuzz0b libinstpatch-1.0-2 libjack-jackd2-0 liblapack3 libllvm15

 libmodplug1 libmp3lame0 libmpg123-0 libopus0 libopusfile0 libpango-1.0-0

 libpangocairo-1.0-0 libpangoft2-1.0-0 libpixman-1-0 libportmidi0 libpulse0

 libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0 libsdl2-ttf-2.0-0

 libsensors-config libsensors5 libsndfile1 libthai-data libthai0

 libvorbisenc2 libvorbisfile3 libwayland-client0 libwayland-cursor0

 libwayland-egl1 libwayland-server0 libx11-xcb1 libxcb-dri2-0 libxcb-dri3-0

 libxcb-present0 libxcb-randr0 libxcb-render0 libxcb-shm0 libxcb-sync1

 libxcb-xfixes0 libxcursor1 libxfixes3 libxi6 libxkbcommon0 libxrandr2

 libxrender1 libxshmfence1 libxss1 libz3-4 mesa-libgallium python3-numpy

 python3-pygame timgm6mb-soundfont x11-common

 0 upgraded, 70 newly installed, 0 to remove and 0 not upgraded.

 Need to get 62.6 MB of archives.

 After this operation, 241 MB of additional disk space will be used.

Do you want to continue? [Y/n] y
(snip)

 driデバイスが動作しているか確認。card0とrenderD128があるので問題なさそう。

fd@pi3b:~ $ ls -l /dev/dri

 total 0

 drwxr-xr-x 2 root root 80 Oct 28 16:01 by-path

 crw-rw---- 1 root video 226, 0 Oct 28 16:01 card0

 crw-rw---- 1 root render 226, 128 Oct 28 16:01 renderD128

 動作確認。

fd@pi3b:~ $ SDL_VIDEODRIVER=kmsdrm python3
Python 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] on linux

 Type "help", "copyright", "credits" or "license" for more information.

>>> import pygame
pygame 2.1.2 (SDL 2.26.5, Python 3.11.2)
Hello from the pygame community. https://www.pygame.org/contribute.html
>>> pygame.display.get_driver()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>

 pygame.error: video system not initialized

 >>>

 あ、ディスプレイの初期化必要だったね。KMSDRMと確認できた。

>>> pygame.display.init()
>>> pygame.display.get_driver()

 'KMSDRM'

 簡単なテストコード。

fd@pi3b:~ $ vi test.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os, sys, pygame

KMSDRM を指定(TTY上で実行)

os.environ["SDL_VIDEODRIVER"] = "kmsdrm"
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.display.set_caption("KMSDRM Test")

描画

screen.fill((0, 0, 0))
font = pygame.font.Font(None, 100)
text = font.render("test", True, (255, 255, 255))
screen.blit(text, text.get_rect(center=screen.get_rect().center))
pygame.display.flip()

何かキー/クリック/タッチがあるまで待つ

 pygame.event.set_blocked(None) # いったん全ブロック

pygame.event.set_allowed([

 pygame.KEYDOWN, pygame.QUIT,

 pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN

])
running = True
while running:
for e in pygame.event.get():
if e.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN, pygame.QUIT):
running = False

 break

pygame.time.wait(10)
pygame.quit()
sys.exit(0)

 実行してみる。

fd@pi3b:~ $ python3 test.py
pygame 2.1.2 (SDL 2.26.5, Python 3.11.2)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
File "/home/fd/test.py", line 11, in <module>
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)

 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

 pygame.error: EGL not initialized

 EGL関係を入れてなかったか... 入れる。ついでに権限も与えておく。

fd@pi3b:~ $ sudo usermod -aG video,render $USER
fd@pi3b:~ $ newgrp video
fd@pi3b:~ $ newgrp video
fd@pi3b:~ $ sudo apt install libegl1-mesa libgles2-mesa libgbm1 mesa-utils kmscube

 Reading package lists... Done

 Building dependency tree... Done

 Reading state information... Done

libgbm1 is already the newest version (24.2.8-1~bpo12+rpt4).

 libgbm1 set to manually installed.

 The following additional packages will be installed:

 gstreamer1.0-plugins-base libcdparanoia0 libegl-mesa0 libegl1 libgl1

 libgl1-mesa-dri libgles2 libglvnd0 libglx-mesa0 libglx0

 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 liborc-0.4-0 libtheora0

 libunwind8 libvisual-0.4-0 libvulkan1 libxcb-glx0 libxxf86vm1 mesa-utils-bin

 mesa-vulkan-drivers

 Suggested packages:

 gvfs libvisual-0.4-plugins gstreamer1.0-tools

 The following NEW packages will be installed:

 gstreamer1.0-plugins-base kmscube libcdparanoia0 libegl-mesa0 libegl1

 libegl1-mesa libgl1 libgl1-mesa-dri libgles2 libgles2-mesa libglvnd0

 libglx-mesa0 libglx0 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0

 liborc-0.4-0 libtheora0 libunwind8 libvisual-0.4-0 libvulkan1 libxcb-glx0

 libxxf86vm1 mesa-utils mesa-utils-bin mesa-vulkan-drivers

 0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.

 Need to get 15.0 MB of archives.

 After this operation, 63.1 MB of additional disk space will be used.

Do you want to continue? [Y/n] y
(snip)

 再実行する。画面中央にtestと白文字で出た。

fd@pi3b:~ $ python3 test.py
pygame 2.1.2 (SDL 2.26.5, Python 3.11.2)
Hello from the pygame community. https://www.pygame.org/contribute.html

 ここに貼るのに、画面をキャプチャして変換しようと思ったら、ツールがなかった。

 ffmpegを入れる。

fd@pi3b:~ $ sudo cat /dev/fb0 > screen.raw
fd@pi3b:~ $ ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s 1280x720 -i screen.raw screen.png

 bash: ffmpeg: command not found

fd@pi3b:~ $ sudo apt install ffmpeg

 Reading package lists... Done

 Building dependency tree... Done

 Reading state information... Done

 The following additional packages will be installed:

 libaacs0 libass9 libavc1394-0 libavcodec59 libavdevice59 libavfilter8

 libavformat59 libavutil57 libbdplus0 libbluray2 libbs2b0 libcaca0

 libcairo-gobject2 libcdio-cdda2 libcdio-paranoia2 libcdio19 libchromaprint1

 libcjson1 libcodec2-1.0 libdc1394-25 libepoxy0 libflite1 libgdk-pixbuf-2.0-0

 libgdk-pixbuf2.0-bin libgdk-pixbuf2.0-common libgme0 libgsm1 libhwy1

 libiec61883-0 libjxl0.7 liblcms2-2 liblilv-0-0 libmbedcrypto7 libmysofa1

 libnorm1 libopenal-data libopenal1 libopenjp2-7 libopenmpt0 libpgm-5.3-0

 libplacebo208 libpocketsphinx3 libpostproc56 librabbitmq4 libraw1394-11

 librist4 librsvg2-2 librsvg2-common librubberband2 libserd-0-0 libshine3

 libsnappy1v5 libsndio7.0 libsodium23 libsord-0-0 libsoxr0 libspeex1

 libsphinxbase3 libsratom-0-0 libsrt1.5-gnutls libssh-gcrypt-4 libswresample4

 libswscale6 libtwolame0 libudfread0 libva-drm2 libva-x11-2 libva2

 libvdpau-va-gl1 libvdpau1 libvidstab1.1 libvpx7 libwebpmux3 libx264-164

 libxcb-shape0 libxv1 libxvidcore4 libzimg2 libzmq5 libzvbi-common libzvbi0

 mesa-va-drivers mesa-vdpau-drivers ocl-icd-libopencl1 pocketsphinx-en-us

 va-driver-all vdpau-driver-all

 Suggested packages:

 ffmpeg-doc libcuda1 libnvcuvid1 libnvidia-encode1 libbluray-bdj

 liblcms2-utils libportaudio2 libraw1394-doc librsvg2-bin serdi sndiod sordi

 speex opencl-icd nvidia-vdpau-driver

 The following NEW packages will be installed:

 ffmpeg libaacs0 libass9 libavc1394-0 libavcodec59 libavdevice59 libavfilter8

 libavformat59 libavutil57 libbdplus0 libbluray2 libbs2b0 libcaca0

 libcairo-gobject2 libcdio-cdda2 libcdio-paranoia2 libcdio19 libchromaprint1

 libcjson1 libcodec2-1.0 libdc1394-25 libepoxy0 libflite1 libgdk-pixbuf-2.0-0

 libgdk-pixbuf2.0-bin libgdk-pixbuf2.0-common libgme0 libgsm1 libhwy1

 libiec61883-0 libjxl0.7 liblcms2-2 liblilv-0-0 libmbedcrypto7 libmysofa1

 libnorm1 libopenal-data libopenal1 libopenjp2-7 libopenmpt0 libpgm-5.3-0

 libplacebo208 libpocketsphinx3 libpostproc56 librabbitmq4 libraw1394-11

 librist4 librsvg2-2 librsvg2-common librubberband2 libserd-0-0 libshine3

 libsnappy1v5 libsndio7.0 libsodium23 libsord-0-0 libsoxr0 libspeex1

 libsphinxbase3 libsratom-0-0 libsrt1.5-gnutls libssh-gcrypt-4 libswresample4

 libswscale6 libtwolame0 libudfread0 libva-drm2 libva-x11-2 libva2

 libvdpau-va-gl1 libvdpau1 libvidstab1.1 libvpx7 libwebpmux3 libx264-164

 libxcb-shape0 libxv1 libxvidcore4 libzimg2 libzmq5 libzvbi-common libzvbi0

 mesa-va-drivers mesa-vdpau-drivers ocl-icd-libopencl1 pocketsphinx-en-us

 va-driver-all vdpau-driver-all

 0 upgraded, 88 newly installed, 0 to remove and 0 not upgraded.

 Need to get 75.6 MB of archives.

 After this operation, 165 MB of additional disk space will be used.

Do you want to continue? [Y/n] y
(snip)

 変換する。

fd@pi3b:~ $ ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb32 -s 1280x720 -i screen.raw screen.png
ffmpeg version 5.1.7-0+deb12u1+rpt1 Copyright (c) 2000-2025 the FFmpeg developers
built with gcc 12 (Debian 12.2.0-14+deb12u1)
configuration: --prefix=/usr --extra-version=0+deb12u1+rpt1 --toolchain=hardened --incdir=/usr/include/aarch64-linux-gnu --enable-gpl --disable-stripping --disable-mmal --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libglslang --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librist --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sand --enable-sdl2 --disable-sndio --enable-libjxl --enable-neon --enable-v4l2-request --enable-libudev --enable-epoxy --libdir=/usr/lib/aarch64-linux-gnu --arch=arm64 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-vout-drm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-libplacebo --enable-librav1e --enable-shared
libavutil      57. 28.100 / 57. 28.100
libavcodec     59. 37.100 / 59. 37.100
libavformat    59. 27.100 / 59. 27.100
libavdevice    59.  7.100 / 59.  7.100
libavfilter     8. 44.100 /  8. 44.100
libswscale      6.  7.100 /  6.  7.100
libswresample   4.  7.100 /  4.  7.100
libpostproc    56.  6.100 / 56.  6.100
[rawvideo @ 0x559b6d54d0] Estimating duration from bitrate, this may be inaccurate

 Input #0, rawvideo, from 'screen.raw':

Duration: 00:00:00.04, start: 0.000000, bitrate: 829440 kb/s
Stream #0:0: Video: rawvideo (BGRA / 0x41524742), bgra, 1280x720, 737280 kb/s, 25 tbr, 25 tbn

 Stream mapping:

Stream #0:0 -> #0:0 (rawvideo (native) -> png (native))

 Press [q] to stop, [?] for help

 Output #0, image2, to 'screen.png':

 Metadata:

 encoder : Lavf59.27.100

Stream #0:0: Video: png, rgba(pc, gbr/unknown/unknown, progressive), 1280x720, q=2-31, 200 kb/s, 25 fps, 25 tbn

 Metadata:

 encoder : Lavc59.37.100 png

[rawvideo @ 0x559b6d54d0] Packet corrupt (stream = 0, dts = 1). speed=   0x

 screen.raw: corrupt input packet in stream 0

[rawvideo @ 0x559b6deab0] Invalid buffer size, packet size 460800 < expected frame_size 3686400

 Error while decoding stream #0:0: Invalid argument

[image2 @ 0x559b6df230] The specified filename 'screen.png' does not contain an image sequence pattern or a pattern is invalid.
[image2 @ 0x559b6df230] Use a pattern such as %03d for an image sequence or use the -update option (with -frames:v 1 if needed) to write a single image.
frame=    1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=0.227x

 video:16kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

fd@pi3b:~ $ file screen.png
screen.png: PNG image data, 1280 x 720, 8-bit/color RGBA, non-interlaced
fd@pi3b:~ $ ls -al screen.png
-rw-r--r-- 1 fd video 16459 Oct 28 16:25 screen.png

 うーん、fb0からは取れないみたい。

 ffmpegで直接キャプチャーデバイスを指定して取得してみようとしたのだけど、ことごとく失敗。

 しょうがないので、pygameの中で画像を保存することにした。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

 """

 KMSDRM用 最小テスト + 画面キャプチャ

  • 物理TTYで実行

  • "test" を表示

  • 何か押されたら "shot.png" を保存して自動終了

 """

import os
import sys
import pygame

KMSDRM を指定(必要なら外部で SDL_VIDEODRIVER=kmsdrm でも可)

os.environ.setdefault("SDL_VIDEODRIVER", "kmsdrm")

 os.environ.setdefault("SDL_RENDER_DRIVER", "opengles2") # 失敗時は下でsoftwareに切替

pygame.init()
pygame.font.init()

set_mode 試行(EGL未初期化などの環境で software にフォールバック)

 try:

 screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) # ネイティブ解像度

 except pygame.error:

os.environ["SDL_RENDER_DRIVER"] = "software"
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
pygame.display.set_caption("KMSDRM Test & Capture")

描画

screen.fill((0, 0, 0))

 font = pygame.font.Font(None, 100) # デフォルトフォント

text = font.render("test", True, (255, 255, 255))
screen.blit(text, text.get_rect(center=screen.get_rect().center))
pygame.display.flip()

入力待ち(キー/マウス/タッチ)。入力が来たらキャプチャして終了

pygame.event.set_blocked(None)
pygame.event.set_allowed([pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN, pygame.QUIT])
running = True
while running:
e = pygame.event.wait()
if e.type in (pygame.KEYDOWN, pygame.MOUSEBUTTONDOWN, pygame.FINGERDOWN):

 try:

pygame.image.save(screen, "shot.png")

 finally:

running = False
elif e.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit(0)

 画面では、左上にカーソルが出ているのだけど、キャプチャには映っていないのな。

 とりあえず、動くことは確認できた。

 100円投げ銭  https://kinneko.booth.pm/items/1963720


オリジナル投稿:
pygame: fbじゃなくてKMSDRM|kinneko|pixivFANBOX
https://kinneko.fanbox.cc/posts/10806321