From d15a7164764b917059ae1c3215fde2fa049c303a Mon Sep 17 00:00:00 2001 From: imterah Date: Sun, 8 Jun 2025 09:19:04 -0400 Subject: [PATCH] feature: Add ability to draw to display directly from Python --- .gitmodules | 3 +++ README.md | 12 ++++++++++-- main.py | 10 +++++++--- raylib-python-cffi | 1 + render.py | 18 +----------------- requirements.txt | 3 ++- shell.nix | 10 +++++++--- unrealxr | 2 +- 8 files changed, 32 insertions(+), 27 deletions(-) create mode 160000 raylib-python-cffi diff --git a/.gitmodules b/.gitmodules index e62726d..e7914c6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "evdi"] path = evdi url = https://github.com/DisplayLink/evdi +[submodule "raylib-python-cffi"] + path = raylib-python-cffi + url = https://git.terah.dev/imterah/raylib-python-cffi diff --git a/README.md b/README.md index 1bd48eb..626b862 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,13 @@ UnrealXR is a display multiplexer for the original Nreal Air (other devices may 2. Initialize the development shell: `nix-shell` 3. Create a virtual environment: `python3 -m venv .venv; source .venv/bin/activate` 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` diff --git a/main.py b/main.py index 0fc9658..53a1447 100755 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ from platformdirs import user_data_dir, user_config_dir from loguru import logger import PyEvdi import pyedid +import pyray import time from render import render_loop @@ -160,6 +161,11 @@ def main(): atexit.register(unload_custom_fw) 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") cards = [] @@ -173,9 +179,7 @@ def main(): atexit.register(lambda: card.close()) - logger.info("Initialized displays") - logger.info("Beginning rendering") - + logger.info("Initialized displays. Entering rendering loop") render_loop(edid, cards) if __name__ == "__main__": print("Welcome to UnrealXR!\n") diff --git a/raylib-python-cffi b/raylib-python-cffi new file mode 160000 index 0000000..1e195e4 --- /dev/null +++ b/raylib-python-cffi @@ -0,0 +1 @@ +Subproject commit 1e195e4ac9b1dcd834fcf9c3d7bfc4e91bc10ecc diff --git a/render.py b/render.py index 9bf4083..b462c74 100644 --- a/render.py +++ b/render.py @@ -5,26 +5,10 @@ import pyray from libunreal import EvdiDisplaySpec 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(): - # 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.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() logger.info("Goodbye!") diff --git a/requirements.txt b/requirements.txt index 7c56f4a..c2a938b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pybind11==2.13.6 pyedid==1.0.3 loguru==0.7.3 platformdirs==4.3.8 -raylib==5.5.0.2 +cffi=1.17.1 +wheel=0.45.1 diff --git a/shell.nix b/shell.nix index a9e7e2f..589c090 100644 --- a/shell.nix +++ b/shell.nix @@ -11,18 +11,22 @@ linuxHeaders # raylib build dependencies + cmake + clang-tools + pkg-config + wayland libGL + libgbm + libdrm xorg.libXi xorg.libXcursor xorg.libXrandr xorg.libXinerama xorg.libX11 - waylandpp - libxkbcommon ]; 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" export UNREALXR_CONFIG_PATH="$PWD/data/config" export UNREALXR_DATA_PATH="$PWD/data/data" diff --git a/unrealxr b/unrealxr index 3872682..3af86ef 100755 --- a/unrealxr +++ b/unrealxr @@ -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