Running PySDL3 & OpenGL Applications on Ubuntu (WSL2) Запуск приложений PySDL3 и OpenGL на Ubuntu (WSL2)

This guide shows how to set up and run PySDL3 and PyOpenGL applications on Ubuntu inside Windows Subsystem for Linux (WSLg) without running into common graphics driver errors like Couldn't find matching GLX visual. В этом руководстве показано, как настроить и запустить приложения PySDL3 и PyOpenGL на Ubuntu в Windows Subsystem for Linux (WSLg) без распространенных ошибок графического драйвера, таких как Couldn't find matching GLX visual.

1. Prerequisites 1. Предварительные требования

Install the required Python and graphics development packages in your Ubuntu terminal: Установите необходимые пакеты разработки Python и графики в терминале Ubuntu:

sudo apt update
sudo apt install -y python3-pip python3-venv libgl1-mesa-dev libegl1-mesa-dev

2. Set Up a Virtual Environment 2. Настройка виртуального окружения

Create and activate a Python virtual environment inside your project directory: Создайте и активируйте виртуальное окружение Python в директории вашего проекта:

mkdir -p ~/projects/pysdl3-app
cd ~/projects/pysdl3-app
python3 -m venv venv
source venv/bin/activate

3. Install Python Libraries 3. Установка библиотек Python

Install pysdl3, PyOpenGL, and numpy inside your active virtual environment: Установите pysdl3, PyOpenGL и numpy в вашем активном виртуальном окружении:

pip install pysdl3 PyOpenGL numpy

4. Configure main.py Environment Overrides 4. Настройка переменных окружения в main.py

WSLg routes Linux graphics through Windows using Mesa. To prevent window initialization crashes, set platform environment variables before importing sdl3. WSLg маршрутизирует графику Linux через Windows с помощью Mesa. Чтобы предотвратить сбои при инициализации окна, настройте переменные окружения платформы до импорта sdl3.

import ctypes
import os
import sys

import numpy as np
from OpenGL.GL import *
from OpenGL.GL.shaders import *

# Platform-specific environment setup (must be before importing sdl3)
if sys.platform.startswith("linux"):
    os.environ["SDL_VIDEO_FORCE_EGL"] = "1"
    os.environ["SDL_GL_DRIVER"] = "libEGL.so.1"
    os.environ["SDL_VIDEODRIVER"] = "wayland,x11"
    # Route rendering through software rasterization to bypass broken DRI3/WSL drivers
    os.environ["GALLIUM_DRIVER"] = "llvmpipe"
    os.environ["MESA_LOADER_DRIVER_OVERRIDE"] = "llvmpipe"
elif sys.platform.startswith("win32"):
    os.environ["SDL_VIDEODRIVER"] = "windows"

os.environ["SDL_MAIN_USE_CALLBACKS"] = "1"

import sdl3

glContext = None
window = None

vertexShaderSource = """
    attribute vec2 aPosition;
    void main()
    {
        gl_Position = vec4(aPosition, 0.0, 1.0);
    }
"""
 
fragmentShaderSource = """
    void main()
    {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }
"""

@sdl3.SDL_AppInit_func
def SDL_AppInit(appstate, argc, argv):
    global glContext
    global window

    if not sdl3.SDL_Init(sdl3.SDL_INIT_VIDEO):
        sdl3.SDL_Log("Couldn't initialize SDL: %s".encode() % sdl3.SDL_GetError())
        return sdl3.SDL_APP_FAILURE

    sdl3.SDL_GL_SetAttribute(sdl3.SDL_GL_MULTISAMPLEBUFFERS, 1) # Enable MULTISAMPLE
    sdl3.SDL_GL_SetAttribute(sdl3.SDL_GL_MULTISAMPLESAMPLES, 2) # Can be 2, 4, 8 or 16

    windowTitle = "PySDL3, OpenGL".encode()
    window = sdl3.SDL_CreateWindow(windowTitle, 350, 350, sdl3.SDL_WINDOW_OPENGL)
    if not window:
        sdl3.SDL_Log("Couldn't create a window: %s".encode() % sdl3.SDL_GetError())
        return sdl3.SDL_APP_FAILURE

    # Create an OpenGL context
    glContext = sdl3.SDL_GL_CreateContext(window)
    if not glContext:
        sdl3.SDL_Log("Couldn't create a glContext: %s".encode() % sdl3.SDL_GetError())
        return sdl3.SDL_APP_FAILURE

    sdl3.SDL_GL_SetSwapInterval(1) # Turn on vertical sync

    glClearColor(0.2, 0.2, 0.2, 1)

    vertPositions = np.array([
        -0.5, -0.5,
        0.5, -0.5,
        0, 0.5
    ], dtype=np.float32)
    vertPosBuffer = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vertPosBuffer)
    glBufferData(GL_ARRAY_BUFFER, len(vertPositions) * 4,
        vertPositions, GL_STATIC_DRAW)

    program = compileProgram(
        compileShader(vertexShaderSource, GL_VERTEX_SHADER),
        compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER))
    glUseProgram(program)

    aPositionLocation = glGetAttribLocation(program, "aPosition")
    glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
    glEnableVertexAttribArray(aPositionLocation)

    return sdl3.SDL_APP_CONTINUE

@sdl3.SDL_AppEvent_func
def SDL_AppEvent(appstate, event):
    if sdl3.SDL_DEREFERENCE(event).type == sdl3.SDL_EVENT_QUIT:
        return sdl3.SDL_APP_SUCCESS
    
    return sdl3.SDL_APP_CONTINUE

@sdl3.SDL_AppIterate_func
def SDL_AppIterate(appstate):
    glClear(GL_COLOR_BUFFER_BIT)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    sdl3.SDL_GL_SwapWindow(window)
    return sdl3.SDL_APP_CONTINUE

@sdl3.SDL_AppQuit_func
def SDL_AppQuit(appstate, result):
    global glContext
    sdl3.SDL_GL_DestroyContext(glContext)
    # SDL will clean up the window/renderer for us

5. Run the Application 5. Запуск приложения

Execute your script with your virtual environment activated: Запустите скрипт с активированным виртуальным окружением:

python main.py
Note: You can ignore warnings regarding libEGL warning: DRI3 error in the terminal. The application automatically falls back to llvmpipe CPU software rendering and pops up the native window cleanly. Примечание: Предупреждения libEGL warning: DRI3 error в терминале можно проигнорировать. Приложение автоматически переключается на программный рендеринг CPU llvmpipe и корректно отображает окно.

Support My Work Поддержать проект

If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.

Sberbank (Russia only) Сбербанк (только для РФ)

Sberbank SBP QR Code

Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)

+7 (917) 212-29-59

USDT TRC20

USDT TRC20 QR Code

Support via Cryptocurrency Поддержка криптовалютой

TMtY1YifNf6FKvgeFmqKGQR4NStKr3csGp