diff --git a/glamor/glamor_egl.h b/glamor/glamor_egl.h index 8f6ed7840..3dcf2d7f1 100644 --- a/glamor/glamor_egl.h +++ b/glamor/glamor_egl.h @@ -60,7 +60,7 @@ * like mesa will be able to adverise these (even though it can do EGL 1.5). */ static inline EGLDisplay -glamor_egl_get_display(EGLint type, void *native) +glamor_egl_get_display2(EGLint type, void *native, int platform_fallback) { /* In practise any EGL 1.5 implementation would support the EXT extension */ if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) { @@ -71,7 +71,14 @@ glamor_egl_get_display(EGLint type, void *native) } /* Welp, everything is awful. */ - return eglGetDisplay(native); + return platform_fallback ? eglGetDisplay(native) : NULL; +} + +/* Used by Xephyr */ +static inline EGLDisplay +glamor_egl_get_display(EGLint type, void *native) +{ + return glamor_egl_get_display2(type, native, 1); } #endif diff --git a/hw/kdrive/fbdev/fb_glamor.c b/hw/kdrive/fbdev/fb_glamor.c new file mode 100644 index 000000000..6ab1a0ca3 --- /dev/null +++ b/hw/kdrive/fbdev/fb_glamor.c @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: MIT OR X11 + * + * Copyright © 2026 stefan11111 + */ + +#include + +#include + +#include +#include "scrnintstr.h" +#include "glamor_priv.h" +#include "glamor_egl.h" +#include "glamor_glx_provider.h" +#include "glx_extinit.h" + +#include "fbdev.h" + +const char *fbdev_glvnd_provider = "mesa"; + +static void +fbdev_glamor_egl_cleanup(FbdevScrPriv *scrpriv) +{ + if (scrpriv->display != EGL_NO_DISPLAY) { + if (scrpriv->ctx != EGL_NO_CONTEXT) { + eglDestroyContext(scrpriv->display, scrpriv->ctx); + } + + eglMakeCurrent(scrpriv->display, + EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + /* + * Force the next glamor_make_current call to update the context + * (on hot unplug another GPU may still be using glamor) + */ + lastGLContext = NULL; + eglTerminate(scrpriv->display); + scrpriv->display = EGL_NO_DISPLAY; + } +} + +static void +glamor_egl_make_current(struct glamor_context *glamor_ctx) +{ + /* There's only a single global dispatch table in Mesa. EGL, GLX, + * and AIGLX's direct dispatch table manipulation don't talk to + * each other. We need to set the context to NULL first to avoid + * EGL's no-op context change fast path when switching back to + * EGL. + */ + eglMakeCurrent(glamor_ctx->display, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (!eglMakeCurrent(glamor_ctx->display, + glamor_ctx->surface, glamor_ctx->surface, + glamor_ctx->ctx)) { + FatalError("Failed to make EGL context current\n"); + } +} + +static Bool fbdev_glamor_egl_init(ScreenPtr screen); + +Bool +fbdevInitAccel(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + static Bool vendor_initialized = FALSE; + + if (!fbdev_glamor_egl_init(pScreen)) { + screen->dumb = TRUE; + return FALSE; + } + + if (!glamor_init(pScreen, GLAMOR_USE_EGL_SCREEN | GLAMOR_NO_DRI3)) { + fbdev_glamor_egl_cleanup(scrpriv); + screen->dumb = TRUE; + return FALSE; + } + + if (!vendor_initialized) { + GlxPushProvider(&glamor_provider); + vendor_initialized = TRUE; + } + + return TRUE; +} + +void +fbdevEnableAccel(ScreenPtr screen) +{ + (void)screen; +} + +void +fbdevDisableAccel(ScreenPtr screen) +{ + (void)screen; +} + +void +fbdevFiniAccel(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + + fbdev_glamor_egl_cleanup(scrpriv); +} + +static Bool +glamor_query_devices_ext(EGLDeviceEXT **devices, EGLint *num_devices) +{ + EGLint max_devices = 0; + + *devices = NULL; + *num_devices = 0; + + if (!epoxy_has_egl_extension(NULL, "EGL_EXT_device_query") || + !epoxy_has_egl_extension(NULL, "EGL_EXT_device_enumeration")) { + return FALSE; + } + + if (!eglQueryDevicesEXT(0, NULL, &max_devices)) { + return FALSE; + } + + *devices = calloc(sizeof(EGLDeviceEXT), max_devices); + if (*devices == NULL) { + return FALSE; + } + + if (!eglQueryDevicesEXT(max_devices, *devices, num_devices)) { + free(*devices); + *devices = NULL; + *num_devices = 0; + return FALSE; + } + + if (*num_devices < max_devices) { + /* Shouldn't happen */ + void *tmp = realloc(*devices, *num_devices * sizeof(EGLDeviceEXT)); + if (tmp) { + *devices = tmp; + } + } + + return TRUE; +} + +static inline Bool +glamor_egl_device_matches_config(EGLDeviceEXT device, Bool strict) +{ + if (!strict) { + return TRUE; + } +/** + * For some reason, this isn't part of the epoxy headers. + * It is part of EGL/eglext.h, but we can't include that + * alongside the expoxy headers. + * + * See: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_device_persistent_id.txt + * for the spec where this is defined + */ +#ifndef EGL_DRIVER_NAME_EXT +#define EGL_DRIVER_NAME_EXT 0x335E +#endif + + const char *driver_name = eglQueryDeviceStringEXT(device, EGL_DRIVER_NAME_EXT); + if (!driver_name) { + return FALSE; + } + + if (!strcmp(driver_name, fbdev_glvnd_provider)) { + return TRUE; + } + + /** + * This is not specific to nvidia, + * but I don't know of any gl library vendors + * other than mesa and nvidia + */ + Bool device_is_nvidia = !!strstr(driver_name, "nvidia"); + Bool config_is_nvidia = !!strstr(fbdev_glvnd_provider, "nvidia"); + + return device_is_nvidia == config_is_nvidia; +} + +static Bool +fbdev_glamor_egl_init_display(FbdevScrPriv *scrpriv) +{ + EGLDeviceEXT *devices = NULL; + EGLint num_devices = 0; + +#define GLAMOR_EGL_TRY_PLATFORM(platform, native, platform_fallback) \ + scrpriv->display = glamor_egl_get_display2(platform, native, platform_fallback); \ + if (scrpriv->display != EGL_NO_DISPLAY) { \ + if (eglInitialize(scrpriv->display, NULL, NULL)) { \ + free(devices); \ + return TRUE; \ + } \ + eglTerminate(scrpriv->display); \ + scrpriv->display = EGL_NO_DISPLAY; \ + } + + if (glamor_query_devices_ext(&devices, &num_devices)) { + /* Try a strict match first */ + for (uint32_t i = 0; i < num_devices; i++) { + if (glamor_egl_device_matches_config(devices[i], TRUE)) { + GLAMOR_EGL_TRY_PLATFORM(EGL_PLATFORM_DEVICE_EXT, devices[i], TRUE); + } + } + + /* Try a try a less strict match now */ + for (uint32_t i = 0; i < num_devices; i++) { + if (glamor_egl_device_matches_config(devices[i], FALSE)) { + GLAMOR_EGL_TRY_PLATFORM(EGL_PLATFORM_DEVICE_EXT, devices[i], TRUE); + } + } + } + + GLAMOR_EGL_TRY_PLATFORM(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, FALSE); + + /** + * According to https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_platform_device.txt : + * + * When is EGL_PLATFORM_DEVICE_EXT, must + * be an EGLDeviceEXT object. Platform-specific extensions may + * define other valid values for . + * + * As far as I know, this is the relevant standard, and it has not been superceeded in this regard. + * However, some vendors do allow passing EGL_DEFAULT_DISPLAY as the argument. + * So, while this is incorrect according to the standard, it doesn't hurt, and it actually does + * something with some vendors (notably intel from my testing). + */ + GLAMOR_EGL_TRY_PLATFORM(EGL_PLATFORM_DEVICE_EXT, EGL_DEFAULT_DISPLAY, TRUE); + +#undef GLAMOR_EGL_TRY_PLATFORM + + return FALSE; +} + +static Bool +fbdev_glamor_egl_try_big_gl_api(FbdevScrPriv *scrpriv) +{ + if (eglBindAPI(EGL_OPENGL_API)) { + static const EGLint config_attribs_core[] = { + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_CONTEXT_MAJOR_VERSION_KHR, + GLAMOR_GL_CORE_VER_MAJOR, + EGL_CONTEXT_MINOR_VERSION_KHR, + GLAMOR_GL_CORE_VER_MINOR, + EGL_NONE + }; + static const EGLint config_attribs[] = { + EGL_NONE + }; + + scrpriv->ctx = eglCreateContext(scrpriv->display, + EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, + config_attribs_core); + + if (scrpriv->ctx == EGL_NO_CONTEXT) + scrpriv->ctx = eglCreateContext(scrpriv->display, + EGL_NO_CONFIG_KHR, + EGL_NO_CONTEXT, + config_attribs); + } + + if (scrpriv->ctx != EGL_NO_CONTEXT) { + if (!eglMakeCurrent(scrpriv->display, + EGL_NO_SURFACE, EGL_NO_SURFACE, scrpriv->ctx)) { + return FALSE; + } + + if (epoxy_gl_version() < 21) { + /* Ignoring GL < 2.1, falling back to GLES */ + eglDestroyContext(scrpriv->display, scrpriv->ctx); + scrpriv->ctx = EGL_NO_CONTEXT; + } + } + return TRUE; +} + +static Bool +fbdev_glamor_egl_try_gles_api(FbdevScrPriv *scrpriv) +{ + static const EGLint config_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + return FALSE; + } + + scrpriv->ctx = eglCreateContext(scrpriv->display, + EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, + config_attribs); + + if (scrpriv->ctx != EGL_NO_CONTEXT) { + if (!eglMakeCurrent(scrpriv->display, + EGL_NO_SURFACE, EGL_NO_SURFACE, scrpriv->ctx)) { + return FALSE; + } + } + return TRUE; +} + +static Bool +fbdev_glamor_bind_gl_api(FbdevScrPriv *scrpriv) +{ + if (fbdev_glamor_egl_try_big_gl_api(scrpriv)) { + return TRUE; + } + + return fbdev_glamor_egl_try_gles_api(scrpriv); +} + +static Bool +fbdev_glamor_egl_init(ScreenPtr pScreen) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + + if (!fbdev_glamor_egl_init_display(scrpriv)) { + screen->dumb = TRUE; + return FALSE; + } + + if (!fbdev_glamor_bind_gl_api(scrpriv)) { + fbdev_glamor_egl_cleanup(scrpriv); + screen->dumb = TRUE; + return FALSE; + } + + return TRUE; +} + +/* Actual glamor functionality */ +void +glamor_egl_screen_init(ScreenPtr pScreen, struct glamor_context *glamor_ctx) +{ + KdScreenPriv(pScreen); + KdScreenInfo *screen = pScreenPriv->screen; + FbdevScrPriv *scrpriv = screen->driver; + + /* No dri3 */ + + glamor_ctx->display = scrpriv->display; + glamor_ctx->ctx = scrpriv->ctx; + glamor_ctx->surface = EGL_NO_SURFACE; + glamor_ctx->make_current = glamor_egl_make_current; +} + +/* Stubs for glamor */ +#define SET(ptr, val) if(ptr) { *ptr = val; } +int +glamor_egl_fd_name_from_pixmap(ScreenPtr screen, + PixmapPtr pixmap, + CARD16 *stride, CARD32 *size) +{ + (void)screen; + (void)pixmap; + SET(stride, 0); + SET(size, 0); + return -1; +} + + +int +glamor_egl_fds_from_pixmap(ScreenPtr screen, PixmapPtr pixmap, int *fds, + uint32_t *offsets, uint32_t *strides, + uint64_t *modifier) +{ + (void)screen; + (void)pixmap; + (void)fds; + (void)offsets; + (void)strides; + (void)modifier; + return 0; +} + +int +glamor_egl_fd_from_pixmap(ScreenPtr screen, PixmapPtr pixmap, + CARD16 *stride, CARD32 *size) +{ + (void)screen; + (void)pixmap; + SET(stride, 0); + SET(size, 0); + return -1; +} diff --git a/hw/kdrive/fbdev/fbdev.h b/hw/kdrive/fbdev/fbdev.h index 4ee2a89d4..d3cbeac2f 100644 --- a/hw/kdrive/fbdev/fbdev.h +++ b/hw/kdrive/fbdev/fbdev.h @@ -32,6 +32,10 @@ #include "randrstr.h" #endif +#if defined(GLAMOR) && defined(GLXEXT) +#include +#endif + typedef struct _fbdevPriv { struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; @@ -46,12 +50,22 @@ typedef struct _fbdevPriv { typedef struct _fbdevScrPriv { Rotation randr; Bool shadow; +#if defined(GLAMOR) && defined(GLXEXT) + Bool glamor_initialized; + EGLDisplay display; + EGLContext ctx; + void* glamor_make_current; +#endif } FbdevScrPriv; extern KdCardFuncs fbdevFuncs; extern const char *fbdevDevicePath; extern Bool fbDisableShadow; +#if defined(GLAMOR) && defined(GLXEXT) +extern const char *fbdev_glvnd_provider; +#endif + Bool fbdevCardInit(KdCardInfo * card); Bool fbdevScreenInit(KdScreenInfo * screen); @@ -82,4 +96,14 @@ void fbdevPutColors(ScreenPtr pScreen, int n, xColorItem * pdefs); Bool fbdevMapFramebuffer(KdScreenInfo * screen); +#if defined(GLAMOR) && defined(GLXEXT) +Bool fbdevInitAccel(ScreenPtr screen); + +void fbdevEnableAccel(ScreenPtr screen); + +void fbdevDisableAccel(ScreenPtr screen); + +void fbdevFiniAccel(ScreenPtr screen); +#endif + #endif /* _KDRIVE_FBDEV_H_ */ diff --git a/hw/kdrive/fbdev/fbinit.c b/hw/kdrive/fbdev/fbinit.c index 7bcefd244..54f23e2da 100644 --- a/hw/kdrive/fbdev/fbinit.c +++ b/hw/kdrive/fbdev/fbinit.c @@ -70,6 +70,8 @@ ddxUseMsg(void) ("-fb path Framebuffer device to use. Defaults to /dev/fb0\n"); ErrorF ("-noshadow Disable the ShadowFB layer if possible\n"); + ErrorF + ("-glvendor Suggest what glvnd vendor library should be used\n"); ErrorF("\n"); } @@ -90,6 +92,17 @@ ddxProcessArgument(int argc, char **argv, int i) return 1; } +#if defined(GLAMOR) && defined(GLXEXT) + if (!strcmp(argv[i], "-glvendor")) { + if (i + 1 < argc) { + fbdev_glvnd_provider = argv[i + 1]; + return 2; + } + UseMsg(); + exit(1); + } +#endif + return KdProcessArgument(argc, argv, i); } @@ -107,7 +120,14 @@ KdCardFuncs fbdevFuncs = { .scrfini = fbdevScreenFini, .cardfini = fbdevCardFini, - /* no cursor or accel funcs */ + /* no cursor funcs */ + +#if defined(GLAMOR) && defined(GLXEXT) + .initAccel = fbdevInitAccel, + .enableAccel = fbdevEnableAccel, + .disableAccel = fbdevDisableAccel, + .finiAccel = fbdevFiniAccel, +#endif .getColors = fbdevGetColors, .putColors = fbdevPutColors, diff --git a/hw/kdrive/fbdev/meson.build b/hw/kdrive/fbdev/meson.build index f015f699d..e8542cc19 100644 --- a/hw/kdrive/fbdev/meson.build +++ b/hw/kdrive/fbdev/meson.build @@ -4,6 +4,14 @@ srcs = [ '../../stubs/ddxBeforeReset.c', ] +fbdev_glamor = [] +fbdev_dep = [] +if build_glamor and build_glx + srcs += 'fb_glamor.c' + fbdev_glamor += glamor + fbdev_dep += epoxy_dep +endif + xfbdev_server = executable( 'Xfbdev', srcs, @@ -12,9 +20,10 @@ xfbdev_server = executable( include_directories('../src'), include_directories('../linux'), ], - dependencies: common_dep, + dependencies: [ common_dep, fbdev_dep, ], link_with: [ libxserver_main, + fbdev_glamor, kdrive, linux, libxserver_fb,