sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_kmsdrmmouse.c (17758B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
      4   Atomic KMSDRM backend by Manuel Alfayate Corchete <redwindwanderer@gmail.com>
      5 
      6   This software is provided 'as-is', without any express or implied
      7   warranty.  In no event will the authors be held liable for any damages
      8   arising from the use of this software.
      9 
     10   Permission is granted to anyone to use this software for any purpose,
     11   including commercial applications, and to alter it and redistribute it
     12   freely, subject to the following restrictions:
     13 
     14   1. The origin of this software must not be misrepresented; you must not
     15      claim that you wrote the original software. If you use this software
     16      in a product, an acknowledgment in the product documentation would be
     17      appreciated but is not required.
     18   2. Altered source versions must be plainly marked as such, and must not be
     19      misrepresented as being the original software.
     20   3. This notice may not be removed or altered from any source distribution.
     21 */
     22 
     23 #include "../../SDL_internal.h"
     24 
     25 #if SDL_VIDEO_DRIVER_KMSDRM
     26 
     27 #include "SDL_kmsdrmvideo.h"
     28 #include "SDL_kmsdrmmouse.h"
     29 #include "SDL_kmsdrmdyn.h"
     30 #include "SDL_assert.h"
     31 
     32 #include "../../events/SDL_mouse_c.h"
     33 #include "../../events/default_cursor.h"
     34 
     35 static SDL_Cursor *KMSDRM_CreateDefaultCursor(void);
     36 static SDL_Cursor *KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y);
     37 static int KMSDRM_ShowCursor(SDL_Cursor * cursor);
     38 static void KMSDRM_MoveCursor(SDL_Cursor * cursor);
     39 static void KMSDRM_FreeCursor(SDL_Cursor * cursor);
     40 static void KMSDRM_WarpMouse(SDL_Window * window, int x, int y);
     41 static int KMSDRM_WarpMouseGlobal(int x, int y);
     42 
     43 /**********************************/
     44 /* Atomic helper functions block. */
     45 /**********************************/
     46 
     47 int
     48 drm_atomic_movecursor(KMSDRM_CursorData *curdata, uint16_t x, uint16_t y)
     49 {
     50     SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
     51 
     52     if (!dispdata->cursor_plane) /* We can't move a non-existing cursor, but that's ok. */
     53         return 0;
     54 
     55     /* Do we have a set of changes already in the making? If not, allocate a new one. */
     56     if (!dispdata->atomic_req)
     57         dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc();
     58     
     59     add_plane_property(dispdata->atomic_req,
     60             dispdata->cursor_plane, "CRTC_X", x - curdata->hot_x);
     61     add_plane_property(dispdata->atomic_req,
     62             dispdata->cursor_plane, "CRTC_Y", y - curdata->hot_y);
     63 
     64     return 0;
     65 }
     66 
     67 /***************************************/
     68 /* Atomic helper functions block ends. */
     69 /***************************************/
     70 
     71 /* Converts a pixel from straight-alpha [AA, RR, GG, BB], which the SDL cursor surface has,
     72    to premultiplied-alpha [AA. AA*RR, AA*GG, AA*BB].
     73    These multiplications have to be done with floats instead of uint32_t's,
     74    and the resulting values have to be converted to be relative to the 0-255 interval,
     75    where 255 is 1.00 and anything between 0 and 255 is 0.xx. */
     76 void alpha_premultiply_ARGB8888 (uint32_t *pixel) {
     77 
     78     uint32_t A, R, G, B;
     79 
     80     /* Component bytes extraction. */
     81     A = (*pixel >> (3 << 3)) & 0xFF;
     82     R = (*pixel >> (2 << 3)) & 0xFF;
     83     G = (*pixel >> (1 << 3)) & 0xFF;
     84     B = (*pixel >> (0 << 3)) & 0xFF;
     85 
     86     /* Alpha pre-multiplication of each component. */
     87     R = (float)A * ((float)R /255);
     88     G = (float)A * ((float)G /255);
     89     B = (float)A * ((float)B /255);
     90 
     91     /* ARGB8888 pixel recomposition. */
     92     (*pixel) = (((uint32_t)A << 24) | ((uint32_t)R << 16) | ((uint32_t)G << 8)) | ((uint32_t)B << 0);
     93 }
     94 
     95 static SDL_Cursor *
     96 KMSDRM_CreateDefaultCursor(void)
     97 {
     98     return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY);
     99 }
    100 
    101 /* This simply gets the cursor soft-buffer ready. We don't copy it to a GBO BO until ShowCursor()
    102    because the cusor GBM BO (living in dispata) is destroyed and recreated when we recreate windows, etc. */
    103 static SDL_Cursor *
    104 KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
    105 {
    106     KMSDRM_CursorData *curdata;
    107     SDL_Cursor *cursor, *ret;
    108 
    109     curdata = NULL;
    110     ret = NULL;
    111 
    112     /* All code below assumes ARGB8888 format for the cursor surface,
    113        like other backends do. Also, the GBM BO pixels have to be
    114        alpha-premultiplied, but the SDL surface we receive has
    115        straight-alpha pixels, so we always have to convert. */ 
    116     SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
    117     SDL_assert(surface->pitch == surface->w * 4);
    118 
    119     cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor));
    120     if (!cursor) {
    121         SDL_OutOfMemory();
    122         goto cleanup;
    123     }
    124     curdata = (KMSDRM_CursorData *) SDL_calloc(1, sizeof(*curdata));
    125     if (!curdata) {
    126         SDL_OutOfMemory();
    127         goto cleanup;
    128     }
    129 
    130     /* hox_x and hot_y are the coordinates of the "tip of the cursor" from it's base. */
    131     curdata->hot_x = hot_x;
    132     curdata->hot_y = hot_y;
    133     curdata->w = surface->w;
    134     curdata->h = surface->h;
    135     curdata->buffer = NULL;
    136 
    137     /* Configure the cursor buffer info.
    138        This buffer has the original size of the cursor surface we are given. */
    139     curdata->buffer_pitch = surface->pitch;
    140     curdata->buffer_size = surface->pitch * surface->h;
    141     curdata->buffer = (uint32_t*)SDL_malloc(curdata->buffer_size);
    142 
    143     if (!curdata->buffer) {
    144         SDL_OutOfMemory();
    145         goto cleanup;
    146     }
    147 
    148     if (SDL_MUSTLOCK(surface)) {
    149         if (SDL_LockSurface(surface) < 0) {
    150             /* Could not lock surface */
    151             goto cleanup;
    152         }
    153     }
    154 
    155     /* Copy the surface pixels to the cursor buffer, for future use in ShowCursor() */
    156     SDL_memcpy(curdata->buffer, surface->pixels, curdata->buffer_size);
    157 
    158     if (SDL_MUSTLOCK(surface)) {
    159         SDL_UnlockSurface(surface);
    160     }
    161 
    162     cursor->driverdata = curdata;
    163 
    164     ret = cursor;
    165 
    166 cleanup:
    167     if (ret == NULL) {
    168 	if (curdata) {
    169 	    if (curdata->buffer) {
    170 		SDL_free(curdata->buffer);
    171 	    }
    172 	    SDL_free(curdata);
    173 	}
    174 	if (cursor) {
    175 	    SDL_free(cursor);
    176 	}
    177     }
    178 
    179     return ret;
    180 }
    181 
    182 /* When we create a window, we have to test if we have to show the cursor,
    183    and explicily do so if necessary.
    184    This is because when we destroy a window, we take the cursor away from the
    185    cursor plane, and destroy the cusror GBM BO. So we have to re-show it,
    186    so to say. */
    187 void
    188 KMSDRM_InitCursor()
    189 {
    190     SDL_Mouse *mouse = NULL;
    191     mouse = SDL_GetMouse();
    192 
    193     if (!mouse) {
    194         return;
    195     }
    196     if  (!(mouse->cur_cursor)) {
    197         return;
    198     }
    199 
    200     if  (!(mouse->cursor_shown)) {
    201         return;
    202     }
    203 
    204     KMSDRM_ShowCursor(mouse->cur_cursor);
    205 }
    206 
    207 /* Show the specified cursor, or hide if cursor is NULL.
    208    cur_cursor is the current cursor, and cursor is the new cursor.
    209    A cursor is displayed on a display, so we have to add a pointer to dispdata
    210    to the driverdata 
    211  */
    212 static int
    213 KMSDRM_ShowCursor(SDL_Cursor * cursor)
    214 {
    215     SDL_VideoDevice *video_device = SDL_GetVideoDevice();
    216     //SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata);
    217     SDL_Mouse *mouse;
    218     KMSDRM_CursorData *curdata;
    219     SDL_VideoDisplay *display = NULL;
    220     SDL_DisplayData *dispdata = NULL;
    221     KMSDRM_FBInfo *fb;
    222     KMSDRM_PlaneInfo info = {0};
    223 
    224     size_t bo_stride;
    225     size_t bufsize;
    226     uint32_t *ready_buffer = NULL;
    227     uint32_t pixel;
    228 
    229     int i,j;
    230     int ret;
    231 
    232     mouse = SDL_GetMouse();
    233     if (!mouse) {
    234         return SDL_SetError("No mouse.");
    235     }
    236 
    237     if (mouse->focus) {
    238         display = SDL_GetDisplayForWindow(mouse->focus);
    239         if (display) {
    240             dispdata = (SDL_DisplayData*) display->driverdata;
    241         }
    242     }
    243 
    244     /**********************************/
    245     /* if cursor == NULL, HIDE cursor */
    246     /**********************************/
    247     if (!cursor) {
    248         /* Hide CURRENT cursor, a cursor that is already on screen
    249            and SDL is stored in mouse->cur_cursor. */
    250         if (mouse->cur_cursor && mouse->cur_cursor->driverdata) {
    251             if (dispdata && dispdata->cursor_plane) {
    252                 info.plane = dispdata->cursor_plane;
    253                 /* The rest of the members are zeroed. */
    254                 drm_atomic_set_plane_props(&info);
    255                 if (drm_atomic_commit(display->device, SDL_TRUE))
    256                     return SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
    257                 }
    258                 return 0;
    259             }
    260         return SDL_SetError("Couldn't find cursor to hide.");
    261     }
    262 
    263     /************************************************/
    264     /* If cursor != NULL, DO show cursor on display */
    265     /************************************************/
    266     if (!display) {
    267         return SDL_SetError("Could not get display for mouse.");
    268     }
    269     if (!dispdata) {
    270         return SDL_SetError("Could not get display driverdata.");
    271     }
    272     if (!dispdata->cursor_plane) {
    273         return SDL_SetError("Hardware cursor plane not initialized.");
    274     }
    275     
    276     curdata = (KMSDRM_CursorData *) cursor->driverdata;
    277 
    278     if (!curdata || !dispdata->cursor_bo) {
    279         return SDL_SetError("Cursor not initialized properly.");
    280     }
    281 
    282     /* Prepare a buffer we can dump to our GBM BO (different size, alpha premultiplication...) */
    283     bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo);
    284     bufsize = bo_stride * curdata->h;
    285 
    286     ready_buffer = (uint32_t*)SDL_malloc(bufsize);
    287     if (!ready_buffer) {
    288         ret = SDL_OutOfMemory();
    289         goto cleanup;
    290     }
    291 
    292     /* Clean the whole buffer we are preparing. */
    293     SDL_memset(ready_buffer, 0x00, bo_stride * curdata->h);
    294 
    295     /* Copy from the cursor buffer to a buffer that we can dump to the GBM BO,
    296        pre-multiplying by alpha each pixel as we go. */
    297     for (i = 0; i < curdata->h; i++) {
    298         for (j = 0; j < curdata->w; j++) {
    299             pixel = ((uint32_t*)curdata->buffer)[i * curdata->w + j];
    300             alpha_premultiply_ARGB8888 (&pixel);
    301             SDL_memcpy(ready_buffer + (i * dispdata->cursor_w) + j, &pixel, 4);
    302         }
    303     }
    304 
    305     /* Dump the cursor buffer to our GBM BO. */
    306     if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, ready_buffer, bufsize)) {
    307         ret = SDL_SetError("Could not write to GBM cursor BO");
    308         goto cleanup;
    309     }
    310 
    311     /* Get the fb_id for the GBM BO so we can show it on the cursor plane. */
    312     fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo);
    313 
    314     /* Show the GBM BO buffer on the cursor plane. */
    315     info.plane = dispdata->cursor_plane;
    316     info.crtc_id = dispdata->crtc->crtc->crtc_id;
    317     info.fb_id = fb->fb_id; 
    318     info.src_w = curdata->w;
    319     info.src_h = curdata->h;
    320     info.crtc_x = mouse->x - curdata->hot_x;
    321     info.crtc_y = mouse->y - curdata->hot_y;
    322     info.crtc_w = curdata->w;
    323     info.crtc_h = curdata->h; 
    324 
    325     drm_atomic_set_plane_props(&info);
    326 
    327     if (drm_atomic_commit(display->device, SDL_TRUE)) {
    328         ret = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
    329         goto cleanup;
    330     }
    331 
    332     ret = 0;
    333 
    334 cleanup:
    335 
    336     if (ready_buffer) {
    337         SDL_free(ready_buffer);
    338     }
    339     return ret;
    340 }
    341 
    342 /* We have destroyed the cursor by now, in KMSDRM_DestroyCursor.
    343    This is only for freeing the SDL_cursor.*/
    344 static void
    345 KMSDRM_FreeCursor(SDL_Cursor * cursor)
    346 {
    347     KMSDRM_CursorData *curdata;
    348 
    349     /* Even if the cursor is not ours, free it. */
    350     if (cursor) {
    351         curdata = (KMSDRM_CursorData *) cursor->driverdata;
    352         /* Free cursor buffer */
    353         if (curdata->buffer) {
    354             SDL_free(curdata->buffer);
    355             curdata->buffer = NULL;
    356         }
    357         /* Free cursor itself */
    358         if (cursor->driverdata) {
    359             SDL_free(cursor->driverdata);
    360         }
    361         SDL_free(cursor);
    362     }
    363 }
    364 
    365 /* Warp the mouse to (x,y) */
    366 static void
    367 KMSDRM_WarpMouse(SDL_Window * window, int x, int y)
    368 {
    369     /* Only one global/fullscreen window is supported */
    370     KMSDRM_WarpMouseGlobal(x, y);
    371 }
    372 
    373 /* Warp the mouse to (x,y) */
    374 static int
    375 KMSDRM_WarpMouseGlobal(int x, int y)
    376 {
    377     KMSDRM_CursorData *curdata;
    378     SDL_Mouse *mouse = SDL_GetMouse();
    379     SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
    380 
    381     if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) {
    382         /* Update internal mouse position. */
    383         SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y);
    384 
    385         /* And now update the cursor graphic position on screen. */
    386         curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata;
    387         if (dispdata->cursor_bo) {
    388             if (drm_atomic_movecursor(curdata, x, y)) {
    389                 return SDL_SetError("drm_atomic_movecursor() failed.");
    390             }
    391         } else {
    392             return SDL_SetError("Cursor not initialized properly.");
    393         }
    394     } else {
    395         return SDL_SetError("No mouse or current cursor.");
    396     }
    397 
    398     return 0;
    399 }
    400 
    401 void
    402 KMSDRM_InitMouse(_THIS)
    403 {
    404     /* FIXME: Using UDEV it should be possible to scan all mice
    405      * but there's no point in doing so as there's no multimice support...yet!
    406      */
    407 
    408     SDL_VideoDevice *dev = SDL_GetVideoDevice();
    409     SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata);
    410     SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
    411     SDL_Mouse *mouse = SDL_GetMouse();
    412     uint64_t usable_cursor_w, usable_cursor_h;
    413 
    414     mouse->CreateCursor = KMSDRM_CreateCursor;
    415     mouse->ShowCursor = KMSDRM_ShowCursor;
    416     mouse->MoveCursor = KMSDRM_MoveCursor;
    417     mouse->FreeCursor = KMSDRM_FreeCursor;
    418     mouse->WarpMouse = KMSDRM_WarpMouse;
    419     mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal;
    420 
    421     /***************************************************************************/
    422     /* REMEMBER TO BE SURE OF UNDOING ALL THESE STEPS PROPERLY BEFORE CALLING  */
    423     /* gbm_device_destroy, OR YOU WON'T BE ABLE TO CREATE A NEW ONE (ERROR -13 */
    424     /* ON gbm_create_device).                                                  */
    425     /***************************************************************************/
    426 
    427     /* 1- Init cursor plane, if we haven't yet. */
    428     if (!dispdata->cursor_plane) {
    429         setup_plane(_this, &(dispdata->cursor_plane), DRM_PLANE_TYPE_CURSOR);
    430     }
    431 
    432     /* 2- Create the cursor GBM BO, if we haven't yet. */
    433     if (!dispdata->cursor_bo) {
    434 
    435         if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev, GBM_FORMAT_ARGB8888,
    436                                                GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE))
    437         {
    438             SDL_SetError("Unsupported pixel format for cursor");
    439             return;
    440         }
    441 
    442 	if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_WIDTH,  &usable_cursor_w) ||
    443 	    KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT, &usable_cursor_h))
    444 	{
    445 	    SDL_SetError("Could not get the recommended GBM cursor size");
    446 	    goto cleanup;
    447 	}
    448 
    449 	if (usable_cursor_w == 0 || usable_cursor_h == 0) {
    450 	    SDL_SetError("Could not get an usable GBM cursor size");
    451 	    goto cleanup;
    452 	}
    453 
    454         dispdata->cursor_w = usable_cursor_w;
    455         dispdata->cursor_h = usable_cursor_h;
    456 
    457 	dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev,
    458 	    usable_cursor_w, usable_cursor_h,
    459 	    GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
    460 
    461 	if (!dispdata->cursor_bo) {
    462 	    SDL_SetError("Could not create GBM cursor BO");
    463 	    goto cleanup;
    464 	}
    465     }
    466 
    467     /* SDL expects to set the default cursor on screen when we init the mouse. */
    468     SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor());
    469 
    470     return;
    471 
    472 cleanup:
    473     if (dispdata->cursor_bo) {
    474 	KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
    475 	dispdata->cursor_bo = NULL;
    476     }
    477 }
    478 
    479 void
    480 KMSDRM_DeinitMouse(_THIS)
    481 {
    482     SDL_VideoDevice *video_device = SDL_GetVideoDevice();
    483     SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
    484     KMSDRM_PlaneInfo info = {0};
    485  
    486     /*******************************************/
    487     /* UNDO WHAT WE DID IN KMSDRM_InitMouse(). */
    488     /*******************************************/
    489     
    490     /* 1- Destroy the curso GBM BO. */
    491     if (video_device && dispdata->cursor_bo) {
    492 	/* Unsethe the cursor BO from the cursor plane.
    493 	   (The other members of the plane info are zeroed). */
    494 	info.plane = dispdata->cursor_plane;
    495 	drm_atomic_set_plane_props(&info);
    496 	/* Wait until the cursor is unset from the cursor plane
    497 	   before destroying it's BO. */
    498 	if (drm_atomic_commit(video_device, SDL_TRUE)) {
    499 	    SDL_SetError("Failed atomic commit in KMSDRM_DenitMouse.");
    500 	}
    501 	/* ..and finally destroy the cursor DRM BO! */
    502 	KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
    503 	dispdata->cursor_bo = NULL;
    504     }
    505 
    506     /* 2- Free the cursor plane, on which the cursor was being shown. */
    507     if (dispdata->cursor_plane) {
    508         free_plane(&dispdata->cursor_plane);
    509     }
    510 
    511 }
    512 
    513 /* This is called when a mouse motion event occurs */
    514 static void
    515 KMSDRM_MoveCursor(SDL_Cursor * cursor)
    516 {
    517     SDL_Mouse *mouse = SDL_GetMouse();
    518     KMSDRM_CursorData *curdata;
    519 
    520     /* We must NOT call SDL_SendMouseMotion() here or we will enter recursivity!
    521        That's why we move the cursor graphic ONLY. */
    522     if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) {
    523         curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata;
    524 
    525         /* Some programs expect cursor movement even while they don't do SwapWindow() calls,
    526            and since we ride on the atomic_commit() in SwapWindow() for cursor movement,
    527            cursor won't move in these situations. We could do an atomic_commit() here
    528            for each cursor movement request, but it cripples the movement to 30FPS,
    529            so a future solution is needed. SDLPoP "QUIT?" menu is an example of this
    530            situation. */
    531 
    532         if (drm_atomic_movecursor(curdata, mouse->x, mouse->y)) {
    533             SDL_SetError("drm_atomic_movecursor() failed.");
    534         }
    535     }
    536 }
    537 
    538 #endif /* SDL_VIDEO_DRIVER_KMSDRM */
    539 
    540 /* vi: set ts=4 sw=4 expandtab: */