diff options
Diffstat (limited to 'unix/xserver/hw/vnc/vncDRI3.c')
-rw-r--r-- | unix/xserver/hw/vnc/vncDRI3.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/unix/xserver/hw/vnc/vncDRI3.c b/unix/xserver/hw/vnc/vncDRI3.c new file mode 100644 index 00000000..79b56f10 --- /dev/null +++ b/unix/xserver/hw/vnc/vncDRI3.c @@ -0,0 +1,537 @@ +/* Copyright 2024 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_DIX_CONFIG_H +#include <dix-config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <unistd.h> + +#ifdef HAVE_GBM +#include <gbm.h> +#endif + +#include "vncDRI3.h" +#include <dri3.h> +#include <fb.h> +#include <misyncshm.h> + +#ifdef FB_ACCESS_WRAPPER +#error "This code is not compatible with accessors" +#endif + +const char *renderNode = "auto"; + +static DevPrivateKeyRec vncDRI3ScreenPrivateKey; +static DevPrivateKeyRec vncDRI3PixmapPrivateKey; + +typedef struct vncDRI3ScreenPrivate { + CloseScreenProcPtr CloseScreen; + + DestroyPixmapProcPtr DestroyPixmap; + +#ifdef HAVE_GBM + const char *devicePath; + struct gbm_device *device; + int fd; +#endif +} vncDRI3ScreenPrivateRec, *vncDRI3ScreenPrivatePtr; + +typedef struct vncDRI3PixmapPrivate { +#ifdef HAVE_GBM + struct gbm_bo *bo; +#endif +} vncDRI3PixmapPrivateRec, *vncDRI3PixmapPrivatePtr; + +#define wrap(priv, real, mem, func) {\ + priv->mem = real->mem; \ + real->mem = func; \ +} + +#define unwrap(priv, real, mem) {\ + real->mem = priv->mem; \ +} + +static inline vncDRI3ScreenPrivatePtr vncDRI3ScreenPrivate(ScreenPtr screen) +{ + return (vncDRI3ScreenPrivatePtr)dixLookupPrivate(&(screen)->devPrivates, &vncDRI3ScreenPrivateKey); +} + +static inline vncDRI3PixmapPrivatePtr vncDRI3PixmapPrivate(PixmapPtr pixmap) +{ + return (vncDRI3PixmapPrivatePtr)dixLookupPrivate(&(pixmap)->devPrivates, &vncDRI3PixmapPrivateKey); +} + +static int vncDRI3Open(ScreenPtr screen, RRProviderPtr provider, + int *fd) +{ +#ifdef HAVE_GBM + vncDRI3ScreenPrivatePtr screenPriv = vncDRI3ScreenPrivate(screen); + + *fd = open(screenPriv->devicePath, O_RDWR|O_CLOEXEC); + if (*fd < 0) + return BadAlloc; + + return Success; +#else + return BadAlloc; +#endif +} + +/* Taken from glamor */ +#ifdef HAVE_GBM +static uint32_t gbm_format_for_depth(CARD8 depth) +{ + switch (depth) { + case 16: + return GBM_FORMAT_RGB565; + case 24: + return GBM_FORMAT_XRGB8888; + case 30: + return GBM_FORMAT_ARGB2101010; + default: + ErrorF("unexpected depth: %d\n", depth); + case 32: + return GBM_FORMAT_ARGB8888; + } +} +#endif + +static PixmapPtr vncPixmapFromFd(ScreenPtr screen, int fd, + CARD16 width, CARD16 height, + CARD16 stride, CARD8 depth, + CARD8 bpp) +{ +#ifdef HAVE_GBM + vncDRI3ScreenPrivatePtr screenPriv = vncDRI3ScreenPrivate(screen); + vncDRI3PixmapPrivatePtr pixmapPriv; + + struct gbm_import_fd_data data; + struct gbm_bo *bo; + PixmapPtr pixmap; + + if (bpp != sizeof(FbBits)*8) { + ErrorF("Incompatible bits per pixel given for GPU buffer\n"); + return NULL; + } + + if ((stride % sizeof(FbBits)) != 0) { + ErrorF("Incompatible stride given for GPU buffer\n"); + return NULL; + } + + data.fd = fd; + data.width = width; + data.height = height; + data.stride = stride; + data.format = gbm_format_for_depth(depth); + + bo = gbm_bo_import(screenPriv->device, GBM_BO_IMPORT_FD, &data, + GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + if (bo == NULL) + return NULL; + + pixmap = screen->CreatePixmap(screen, width, height, depth, 0); + + pixmapPriv = vncDRI3PixmapPrivate(pixmap); + pixmapPriv->bo = bo; + + vncDRI3SyncPixmapFromGPU(pixmap); + + return pixmap; +#else + return NULL; +#endif +} + +static Bool vncDRI3DestroyPixmap(PixmapPtr pixmap) +{ + ScreenPtr screen = pixmap->drawable.pScreen; + vncDRI3ScreenPrivatePtr screenPriv = vncDRI3ScreenPrivate(screen); + Bool ret; + +#ifdef HAVE_GBM + if (pixmap->refcnt == 1) { + vncDRI3PixmapPrivatePtr pixmapPriv = vncDRI3PixmapPrivate(pixmap); + + if (pixmapPriv->bo != NULL) { + gbm_bo_destroy(pixmapPriv->bo); + pixmapPriv->bo = NULL; + } + } +#endif + + unwrap(screenPriv, screen, DestroyPixmap); + ret = screen->DestroyPixmap(pixmap); + wrap(screenPriv, screen, DestroyPixmap, vncDRI3DestroyPixmap); + + return ret; +} + +#ifdef HAVE_GBM +static int vncDRI3FdFromPixmapVisitWindow(WindowPtr window, void *data) +{ + ScreenPtr screen = window->drawable.pScreen; + PixmapPtr pixmap = data; + + if ((*screen->GetWindowPixmap)(window) == pixmap) + window->drawable.serialNumber = NEXT_SERIAL_NUMBER; + + return WT_WALKCHILDREN; +} +#endif + +static int vncFdFromPixmap(ScreenPtr screen, PixmapPtr pixmap, + CARD16 *stride, CARD32 *size) +{ +#ifdef HAVE_GBM + vncDRI3ScreenPrivatePtr screenPriv = vncDRI3ScreenPrivate(screen); + vncDRI3PixmapPrivatePtr pixmapPriv = vncDRI3PixmapPrivate(pixmap); + + if (pixmap->drawable.bitsPerPixel != sizeof(FbBits)*8) { + ErrorF("Incompatible bits per pixel given for pixmap\n"); + return -1; + } + + if (pixmapPriv->bo == NULL) { + /* GBM_BO_USE_LINEAR ? */ + pixmapPriv->bo = gbm_bo_create(screenPriv->device, + pixmap->drawable.width, + pixmap->drawable.height, + gbm_format_for_depth(pixmap->drawable.depth), + GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + if (pixmapPriv->bo == NULL) { + ErrorF("Failed to create GPU buffer: %s\n", strerror(errno)); + return -1; + } + + if ((gbm_bo_get_stride(pixmapPriv->bo) % sizeof(FbBits)) != 0) { + ErrorF("Incompatible stride for created GPU buffer\n"); + gbm_bo_destroy(pixmapPriv->bo); + pixmapPriv->bo = NULL; + return -1; + } + + /* Force re-validation of any gc:s */ + pixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER; + WalkTree(screen, vncDRI3FdFromPixmapVisitWindow, pixmap); + } + + vncDRI3SyncPixmapToGPU(pixmap); + + *stride = gbm_bo_get_stride(pixmapPriv->bo); + /* FIXME */ + *size = *stride * gbm_bo_get_height(pixmapPriv->bo); + + return gbm_bo_get_fd(pixmapPriv->bo); +#else + return -1; +#endif +} + +Bool vncDRI3IsHardwarePixmap(PixmapPtr pixmap) +{ +#ifdef HAVE_GBM + vncDRI3PixmapPrivatePtr pixmapPriv = vncDRI3PixmapPrivate(pixmap); + return pixmapPriv->bo != NULL; +#else + return FALSE; +#endif +} + +Bool vncDRI3IsHardwareDrawable(DrawablePtr drawable) +{ + PixmapPtr pixmap; + int xoff, yoff; + + fbGetDrawablePixmap(drawable, pixmap, xoff, yoff); + (void)xoff; + (void)yoff; + + return vncDRI3IsHardwarePixmap(pixmap); +} + +Bool vncDRI3SyncPixmapToGPU(PixmapPtr pixmap) +{ +#ifdef HAVE_GBM + vncDRI3PixmapPrivatePtr pixmapPriv = vncDRI3PixmapPrivate(pixmap); + + int width, height; + + FbBits *bo_data; + uint32_t bo_stride; + void *map_data; + uint32_t bo_bpp; + + FbBits *pixmap_data; + int pixmap_stride; + int pixmap_bpp; + + pixman_bool_t ret; + + if (pixmapPriv->bo == NULL) + return TRUE; + + width = gbm_bo_get_width(pixmapPriv->bo); + height = gbm_bo_get_height(pixmapPriv->bo); + + map_data = NULL; + bo_data = gbm_bo_map(pixmapPriv->bo, 0, 0, width, height, + GBM_BO_TRANSFER_WRITE, &bo_stride, &map_data); + if (bo_data == NULL) { + ErrorF("Could not map GPU buffer: %s\n", strerror(errno)); + return FALSE; + } + + bo_bpp = gbm_bo_get_bpp(pixmapPriv->bo); + assert(bo_bpp == sizeof(FbBits)*8); + assert((bo_stride % (bo_bpp/8)) == 0); + + fbGetPixmapBitsData(pixmap, pixmap_data, + pixmap_stride, pixmap_bpp); + assert(pixmap_data != NULL); + assert(pixmap_bpp == sizeof(FbBits)*8); + + assert(bo_bpp == pixmap_bpp); + + /* Try accelerated copy first */ + ret = pixman_blt((uint32_t*)pixmap_data, (uint32_t*)bo_data, + pixmap_stride, bo_stride / (bo_bpp/8), + pixmap_bpp, bo_bpp, 0, 0, 0, 0, width, height); + if (!ret) { + /* Fall back to slow pure C version */ + fbBlt(pixmap_data, pixmap_stride, 0, + bo_data, bo_stride / (bo_bpp/8), 0, + width * bo_bpp, height, + GXcopy, FB_ALLONES, bo_bpp, FALSE, FALSE); + } + + gbm_bo_unmap(pixmapPriv->bo, map_data); +#endif + + return TRUE; +} + +Bool vncDRI3SyncPixmapFromGPU(PixmapPtr pixmap) +{ +#ifdef HAVE_GBM + vncDRI3PixmapPrivatePtr pixmapPriv = vncDRI3PixmapPrivate(pixmap); + + int width, height; + + FbBits *bo_data; + uint32_t bo_stride; + void *map_data; + uint32_t bo_bpp; + + FbBits *pixmap_data; + int pixmap_stride; + int pixmap_bpp; + + pixman_bool_t ret; + + if (pixmapPriv->bo == NULL) + return TRUE; + + width = gbm_bo_get_width(pixmapPriv->bo); + height = gbm_bo_get_height(pixmapPriv->bo); + + map_data = NULL; + bo_data = gbm_bo_map(pixmapPriv->bo, 0, 0, width, height, + GBM_BO_TRANSFER_READ, &bo_stride, &map_data); + if (bo_data == NULL) { + ErrorF("Could not map GPU buffer: %s\n", strerror(errno)); + return FALSE; + } + + bo_bpp = gbm_bo_get_bpp(pixmapPriv->bo); + assert(bo_bpp == sizeof(FbBits)*8); + assert((bo_stride % (bo_bpp/8)) == 0); + + fbGetPixmapBitsData(pixmap, pixmap_data, + pixmap_stride, pixmap_bpp); + assert(pixmap_data != NULL); + assert(pixmap_bpp == sizeof(FbBits)*8); + + assert(bo_bpp == pixmap_bpp); + + /* Try accelerated copy first */ + ret = pixman_blt((uint32_t*)bo_data, (uint32_t*)pixmap_data, + bo_stride / (bo_bpp/8), pixmap_stride, + bo_bpp, pixmap_bpp, 0, 0, 0, 0, width, height); + if (!ret) { + /* Fall back to slow pure C version */ + fbBlt(bo_data, bo_stride / (bo_bpp/8), 0, + pixmap_data, pixmap_stride, 0, + width * bo_bpp, height, + GXcopy, FB_ALLONES, bo_bpp, FALSE, FALSE); + } + + gbm_bo_unmap(pixmapPriv->bo, map_data); +#endif + + return TRUE; +} + +Bool vncDRI3SyncDrawableToGPU(DrawablePtr drawable) +{ + PixmapPtr pixmap; + int xoff, yoff; + + fbGetDrawablePixmap(drawable, pixmap, xoff, yoff); + (void)xoff; + (void)yoff; + + return vncDRI3SyncPixmapToGPU(pixmap); +} + +Bool vncDRI3SyncDrawableFromGPU(DrawablePtr drawable) +{ + PixmapPtr pixmap; + int xoff, yoff; + + fbGetDrawablePixmap(drawable, pixmap, xoff, yoff); + (void)xoff; + (void)yoff; + + return vncDRI3SyncPixmapFromGPU(pixmap); +} + +static const dri3_screen_info_rec vncDRI3ScreenInfo = { + .version = 1, + + .open = vncDRI3Open, + .pixmap_from_fd = vncPixmapFromFd, + .fd_from_pixmap = vncFdFromPixmap, +}; + +static Bool vncDRI3CloseScreen(ScreenPtr screen) +{ + vncDRI3ScreenPrivatePtr screenPriv = vncDRI3ScreenPrivate(screen); + + unwrap(screenPriv, screen, CloseScreen); + + unwrap(screenPriv, screen, DestroyPixmap); + +#ifdef HAVE_GBM + gbm_device_destroy(screenPriv->device); + screenPriv->device = NULL; + + close(screenPriv->fd); + screenPriv->fd = -1; +#endif + + return (*screen->CloseScreen)(screen); +} + +Bool vncDRI3Init(ScreenPtr screen) +{ + vncDRI3ScreenPrivatePtr screenPriv; + + /* + * We don't queue any gbm operations, so we don't have to do anything + * more than simply activate this extension. + */ +#ifdef HAVE_XSHMFENCE + if (!miSyncShmScreenInit(screen)) + return FALSE; +#endif + + /* Empty render node is interpreted as disabling DRI3 */ + if (renderNode[0] == '\0') + return TRUE; + + if ((renderNode[0] != '/') && + (strcasecmp(renderNode, "auto") != 0)) { + ErrorF("Invalid render node path \"%s\"\n", renderNode); + return FALSE; + } + + if (!dixRegisterPrivateKey(&vncDRI3ScreenPrivateKey, PRIVATE_SCREEN, + sizeof(vncDRI3ScreenPrivateRec))) + return FALSE; + if (!dixRegisterPrivateKey(&vncDRI3PixmapPrivateKey, PRIVATE_PIXMAP, + sizeof(vncDRI3PixmapPrivateRec))) + return FALSE; + + if (!vncDRI3DrawInit(screen)) + return FALSE; + +#ifdef HAVE_GBM + screenPriv = vncDRI3ScreenPrivate(screen); + + if (strcasecmp(renderNode, "auto") == 0) { + glob_t globbuf; + int ret; + + ret = glob("/dev/dri/renderD*", 0, NULL, &globbuf); + if (ret == GLOB_NOMATCH) { + ErrorF("Could not find any render nodes\n"); + return FALSE; + } + if (ret != 0) { + ErrorF("Failure enumerating render nodes\n"); + return FALSE; + } + + screenPriv->devicePath = NULL; + for (size_t i = 0;i < globbuf.gl_pathc;i++) { + if (access(globbuf.gl_pathv[i], R_OK|W_OK) == 0) { + screenPriv->devicePath = strdup(globbuf.gl_pathv[i]); + break; + } + } + + globfree(&globbuf); + + if (screenPriv->devicePath == NULL) { + ErrorF("Could not find any available render nodes\n"); + return FALSE; + } + } else { + screenPriv->devicePath = renderNode; + } + + screenPriv->fd = open(screenPriv->devicePath, O_RDWR|O_CLOEXEC); + if (screenPriv->fd < 0) { + ErrorF("Failed to open \"%s\": %s\n", + screenPriv->devicePath, strerror(errno)); + return FALSE; + } + + screenPriv->device = gbm_create_device(screenPriv->fd); + if (screenPriv->device == NULL) { + close(screenPriv->fd); + screenPriv->fd = -1; + ErrorF("Could create GPU render device\n"); + return FALSE; + } +#else + ErrorF("Built without GBM support\n"); + return FALSE; +#endif + + wrap(screenPriv, screen, CloseScreen, vncDRI3CloseScreen); + + wrap(screenPriv, screen, DestroyPixmap, vncDRI3DestroyPixmap); + + return dri3_screen_init(screen, &vncDRI3ScreenInfo); +} |