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: */