feature: Add ability to draw to display directly from Python

This commit is contained in:
Tera << 8 2025-06-08 09:19:04 -04:00
parent 81b89a1a4f
commit d15a716476
Signed by: imterah
GPG key ID: 8FA7DD57BA6CEA37
8 changed files with 32 additions and 27 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "evdi"] [submodule "evdi"]
path = evdi path = evdi
url = https://github.com/DisplayLink/evdi url = https://github.com/DisplayLink/evdi
[submodule "raylib-python-cffi"]
path = raylib-python-cffi
url = https://git.terah.dev/imterah/raylib-python-cffi

View file

@ -8,5 +8,13 @@ UnrealXR is a display multiplexer for the original Nreal Air (other devices may
2. Initialize the development shell: `nix-shell` 2. Initialize the development shell: `nix-shell`
3. Create a virtual environment: `python3 -m venv .venv; source .venv/bin/activate` 3. Create a virtual environment: `python3 -m venv .venv; source .venv/bin/activate`
4. Install Python dependencies: `pip install -r requirements.txt` 4. Install Python dependencies: `pip install -r requirements.txt`
4. Build libevdi: `cd evdi/library; make -j$(nproc); cd ../..`
5. Build pyevdi: `cd evdi/pyevdi; make -j$(nproc); make install; cd ../..` ### Building `PyEvdi`
1. Build `libevdi`: `cd evdi/library; make -j$(nproc); cd ../..`
2. Build `PyEvdi`: `cd evdi/pyevdi; make -j$(nproc); make install; cd ../..`
### Building patched `PyRay`/`raylib`
1. Build the native version of raylib: `cd raylib-python-cffi/raylib-c; mkdir -p build/out; cd build; cmake -DCUSTOMIZE_BUILD=ON -DSUPPORT_FILEFORMAT_JPG=ON -DSUPPORT_FILEFORMAT_FLAC=ON -DWITH_PIC=ON -DCMAKE_BUILD_TYPE=Release -DPLATFORM=DRM -DENABLE_WAYLAND_DRM_LEASING=ON -DSUPPORT_CLIPBOARD_IMAGE=ON -DCMAKE_INSTALL_PREFIX:PATH=$PWD/out ..; make install -j$(nproc)`
2. Build the Python version of raylib: `cd ../..; PKG_CONFIG_PATH_FOR_TARGET="$PKG_CONFIG_PATH_FOR_TARGET:$PWD/raylib-c/build/out/lib64/pkgconfig/" PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$PWD/raylib-c/build/out/lib64/pkgconfig/" ENABLE_WAYLAND_DRM_LEASING=YES RAYLIB_PLATFORM=DRM python3 setup.py bdist_wheel; pip install dist/*.whl`

10
main.py
View file

@ -8,6 +8,7 @@ from platformdirs import user_data_dir, user_config_dir
from loguru import logger from loguru import logger
import PyEvdi import PyEvdi
import pyedid import pyedid
import pyray
import time import time
from render import render_loop from render import render_loop
@ -160,6 +161,11 @@ def main():
atexit.register(unload_custom_fw) atexit.register(unload_custom_fw)
input("Press the Enter key to continue loading after you unplug and plug in your XR device.") input("Press the Enter key to continue loading after you unplug and plug in your XR device.")
# Raylib gets confused if there's multiple dri devices so we initialize the window before anything
logger.info("Initializing XR headset")
pyray.set_target_fps(edid.max_refresh_rate)
pyray.init_window(edid.max_width, edid.max_height, "UnrealXR")
logger.info("Initializing virtual displays") logger.info("Initializing virtual displays")
cards = [] cards = []
@ -173,9 +179,7 @@ def main():
atexit.register(lambda: card.close()) atexit.register(lambda: card.close())
logger.info("Initialized displays") logger.info("Initialized displays. Entering rendering loop")
logger.info("Beginning rendering")
render_loop(edid, cards) render_loop(edid, cards)
if __name__ == "__main__": if __name__ == "__main__":
print("Welcome to UnrealXR!\n") print("Welcome to UnrealXR!\n")

1
raylib-python-cffi Submodule

@ -0,0 +1 @@
Subproject commit 1e195e4ac9b1dcd834fcf9c3d7bfc4e91bc10ecc

View file

@ -5,26 +5,10 @@ import pyray
from libunreal import EvdiDisplaySpec from libunreal import EvdiDisplaySpec
def render_loop(display_metadata: EvdiDisplaySpec, cards: list[PyEvdi.Card]): def render_loop(display_metadata: EvdiDisplaySpec, cards: list[PyEvdi.Card]):
pyray.init_window(display_metadata.max_width, display_metadata.max_height, "UnrealXR")
while not pyray.window_should_close(): while not pyray.window_should_close():
# Implement fullscreen toggle
if pyray.is_key_pressed(pyray.KeyboardKey.KEY_F11):
display = pyray.get_current_monitor()
if pyray.is_window_fullscreen():
pyray.set_window_size(display_metadata.max_width, display_metadata.max_height)
else:
pyray.set_window_size(pyray.get_monitor_width(display), pyray.get_monitor_height(display))
pyray.toggle_fullscreen()
# Ctrl-C to quit
elif pyray.is_key_down(pyray.KeyboardKey.KEY_LEFT_CONTROL) and pyray.is_key_down(pyray.KeyboardKey.KEY_C):
break
pyray.begin_drawing() pyray.begin_drawing()
pyray.clear_background(pyray.BLACK) pyray.clear_background(pyray.BLACK)
pyray.draw_text("Hello world", 190, 200, 20, pyray.VIOLET) pyray.draw_text("Hello world from Python!", 190, 200, 96, pyray.WHITE)
pyray.end_drawing() pyray.end_drawing()
logger.info("Goodbye!") logger.info("Goodbye!")

View file

@ -3,4 +3,5 @@ pybind11==2.13.6
pyedid==1.0.3 pyedid==1.0.3
loguru==0.7.3 loguru==0.7.3
platformdirs==4.3.8 platformdirs==4.3.8
raylib==5.5.0.2 cffi=1.17.1
wheel=0.45.1

View file

@ -11,18 +11,22 @@
linuxHeaders linuxHeaders
# raylib build dependencies # raylib build dependencies
cmake
clang-tools
pkg-config
wayland
libGL libGL
libgbm
libdrm
xorg.libXi xorg.libXi
xorg.libXcursor xorg.libXcursor
xorg.libXrandr xorg.libXrandr
xorg.libXinerama xorg.libXinerama
xorg.libX11 xorg.libX11
waylandpp
libxkbcommon
]; ];
shellHook = '' shellHook = ''
export LD_LIBRARY_PATH="$PWD/evdi/library:${pkgs.lib.makeLibraryPath [ pkgs.xorg.libX11 pkgs.libGL ]}:$LD_LIBRARY_PATH" export LD_LIBRARY_PATH="$PWD/evdi/library:${pkgs.lib.makeLibraryPath [ pkgs.libGL ]}:$LD_LIBRARY_PATH"
mkdir -p "$PWD/data/config" "$PWD/data/data" mkdir -p "$PWD/data/config" "$PWD/data/data"
export UNREALXR_CONFIG_PATH="$PWD/data/config" export UNREALXR_CONFIG_PATH="$PWD/data/config"
export UNREALXR_DATA_PATH="$PWD/data/data" export UNREALXR_DATA_PATH="$PWD/data/data"

View file

@ -1 +1 @@
sudo LD_LIBRARY_PATH="$LD_LIBRARY_PATH" UNREALXR_CONFIG_PATH="$UNREALXR_CONFIG_PATH" UNREALXR_DATA_PATH="$UNREALXR_DATA_PATH" python3 main.py sudo LD_LIBRARY_PATH="$LD_LIBRARY_PATH" UNREALXR_CONFIG_PATH="$UNREALXR_CONFIG_PATH" UNREALXR_DATA_PATH="$UNREALXR_DATA_PATH" WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR="/user/run/0" python3 main.py