diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.c b/src/video/kmsdrm/SDL_kmsdrmmouse.c index a841d947f..5c7b6ec4d 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.c +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.c @@ -19,6 +19,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ + #include "../../SDL_internal.h" #if SDL_VIDEO_DRIVER_KMSDRM @@ -26,6 +27,7 @@ #include "SDL_kmsdrmvideo.h" #include "SDL_kmsdrmmouse.h" #include "SDL_kmsdrmdyn.h" +#include "SDL_assert.h" #include "../../events/SDL_mouse_c.h" #include "../../events/default_cursor.h" @@ -54,8 +56,10 @@ drm_atomic_movecursor(KMSDRM_CursorData *curdata, uint16_t x, uint16_t y) if (!dispdata->atomic_req) dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc(); - add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_X", x - curdata->hot_x); - add_plane_property(dispdata->atomic_req, dispdata->cursor_plane, "CRTC_Y", y - curdata->hot_y); + add_plane_property(dispdata->atomic_req, + dispdata->cursor_plane, "CRTC_X", x - curdata->hot_x); + add_plane_property(dispdata->atomic_req, + dispdata->cursor_plane, "CRTC_Y", y - curdata->hot_y); return 0; } @@ -66,8 +70,9 @@ drm_atomic_movecursor(KMSDRM_CursorData *curdata, uint16_t x, uint16_t y) /* Converts a pixel from straight-alpha [AA, RR, GG, BB], which the SDL cursor surface has, to premultiplied-alpha [AA. AA*RR, AA*GG, AA*BB]. - These multiplications have to be done with floats instead of uint32_t's, and the resulting values have - to be converted to be relative to the 0-255 interval, where 255 is 1.00 and anything between 0 and 255 is 0.xx. */ + These multiplications have to be done with floats instead of uint32_t's, + and the resulting values have to be converted to be relative to the 0-255 interval, + where 255 is 1.00 and anything between 0 and 255 is 0.xx. */ void alpha_premultiply_ARGB8888 (uint32_t *pixel) { uint32_t A, R, G, B; @@ -93,35 +98,28 @@ KMSDRM_CreateDefaultCursor(void) return SDL_CreateCursor(default_cdata, default_cmask, DEFAULT_CWIDTH, DEFAULT_CHEIGHT, DEFAULT_CHOTX, DEFAULT_CHOTY); } -/* Create a GBM cursor from a surface, which means creating a hardware cursor. - Most programs use software cursors, but protracker-clone for example uses - an optional hardware cursor. */ +/* This simply copies over the cursor bitmap from the SDLSurface we receive to the + cursor GBM BO. Setting up the cursor plane, creating the cursor FB BO, etc.. is + done in KMSDRM_InitMouse(): when we get here. everything must be ready. */ static SDL_Cursor * KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) { - SDL_VideoDevice *dev = SDL_GetVideoDevice(); - SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata); + SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); KMSDRM_CursorData *curdata; SDL_Cursor *cursor; - uint64_t usable_cursor_w, usable_cursor_h; + uint32_t bo_stride, pixel; uint32_t *buffer = NULL; size_t bufsize; unsigned int i, j; - /* All code below assumes ARGB8888 format for the cursor surface, like other backends do. - Also, the GBM BO pixels have to be alpha-premultiplied, but the SDL surface we receive has + /* All code below assumes ARGB8888 format for the cursor surface, + like other backends do. Also, the GBM BO pixels have to be + alpha-premultiplied, but the SDL surface we receive has straight-alpha pixels, so we always have to convert. */ SDL_assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888); SDL_assert(surface->pitch == surface->w * 4); - if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev, GBM_FORMAT_ARGB8888, - GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) - { - SDL_SetError("Unsupported pixel format for cursor"); - return NULL; - } - cursor = (SDL_Cursor *) SDL_calloc(1, sizeof(*cursor)); if (!cursor) { SDL_OutOfMemory(); @@ -134,34 +132,13 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) return NULL; } - /* Find out what GBM cursor size is recommended by the driver. */ - if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_WIDTH, &usable_cursor_w) || - KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT, &usable_cursor_h)) - { - SDL_SetError("Could not get the recommended GBM cursor size"); - goto cleanup; - } - - if (usable_cursor_w == 0 || usable_cursor_h == 0) { - SDL_SetError("Could not get an usable GBM cursor size"); - goto cleanup; - } - /* hox_x and hot_y are the coordinates of the "tip of the cursor" from it's base. */ curdata->hot_x = hot_x; curdata->hot_y = hot_y; - curdata->w = usable_cursor_w; - curdata->h = usable_cursor_h; + curdata->w = dispdata->cursor_w; + curdata->h = dispdata->cursor_h; - curdata->bo = KMSDRM_gbm_bo_create(viddata->gbm_dev, usable_cursor_w, usable_cursor_h, - GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); - - if (!curdata->bo) { - SDL_SetError("Could not create GBM cursor BO"); - goto cleanup; - } - - bo_stride = KMSDRM_gbm_bo_get_stride(curdata->bo); + bo_stride = KMSDRM_gbm_bo_get_stride(dispdata->cursor_bo); bufsize = bo_stride * curdata->h; /* Always use a temp buffer: it serves the purpose of storing the @@ -197,7 +174,7 @@ KMSDRM_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) SDL_UnlockSurface(surface); } - if (KMSDRM_gbm_bo_write(curdata->bo, buffer, bufsize)) { + if (KMSDRM_gbm_bo_write(dispdata->cursor_bo, buffer, bufsize)) { SDL_SetError("Could not write to GBM cursor BO"); goto cleanup; } @@ -218,11 +195,9 @@ cleanup: SDL_free(cursor); } if (curdata) { - if (curdata->bo) { - KMSDRM_gbm_bo_destroy(curdata->bo); - } SDL_free(curdata); } + return NULL; } @@ -263,7 +238,8 @@ KMSDRM_ShowCursor(SDL_Cursor * cursor) and SDL is stored in mouse->cur_cursor. */ if (mouse->cur_cursor && mouse->cur_cursor->driverdata) { if (dispdata && dispdata->cursor_plane) { - info.plane = dispdata->cursor_plane; /* The rest of the members are zeroed. */ + info.plane = dispdata->cursor_plane; + /* The rest of the members are zeroed. */ drm_atomic_set_plane_props(&info); if (drm_atomic_commit(display->device, SDL_TRUE)) return SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor."); @@ -288,18 +264,14 @@ KMSDRM_ShowCursor(SDL_Cursor * cursor) curdata = (KMSDRM_CursorData *) cursor->driverdata; - if (!curdata || !curdata->bo) { + if (!curdata || !dispdata->cursor_bo) { return SDL_SetError("Cursor not initialized properly."); } - curdata->crtc_id = dispdata->crtc->crtc->crtc_id; - curdata->plane = dispdata->cursor_plane; - curdata->video = video_device; - - fb = KMSDRM_FBFromBO(curdata->video, curdata->bo); + fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo); info.plane = dispdata->cursor_plane; - info.crtc_id = curdata->crtc_id; + info.crtc_id = dispdata->crtc->crtc->crtc_id; info.fb_id = fb->fb_id; info.src_w = curdata->w; info.src_h = curdata->h; @@ -313,39 +285,17 @@ KMSDRM_ShowCursor(SDL_Cursor * cursor) if (drm_atomic_commit(display->device, SDL_TRUE)) { return SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor."); } + return 0; } -/* Unset the cursor from the cursor plane, and ONLY WHEN THAT'S DONE, - DONE FOR REAL, and not only requested, destroy it by destroying the curso BO. - Destroying the cursor BO is an special an delicate situation, - because drm_atomic_set_plane_props() returns immediately, and we DON'T - want to get to gbm_bo_destroy() before the prop changes requested - in drm_atomic_set_plane_props() have effectively been done. So we - issue a BLOCKING atomic_commit here to avoid that situation. - REMEMBER you yan issue an atomic_commit whenever you want, and - the changes requested until that moment (for any planes, crtcs, etc.) - will be done. */ +/* We have destroyed the cursor by now, in KMSDRM_DestroyCursor. + This is only for freeing the SDL_cursor.*/ static void KMSDRM_FreeCursor(SDL_Cursor * cursor) { - KMSDRM_CursorData *curdata = NULL; - SDL_VideoDevice *video_device = SDL_GetVideoDevice(); - KMSDRM_PlaneInfo info = {0}; + /* Even if the cursor is not ours, free it. */ if (cursor) { - curdata = (KMSDRM_CursorData *) cursor->driverdata; - if (video_device && curdata->bo && curdata->plane) { - info.plane = curdata->plane; /* The other members are zeroed. */ - drm_atomic_set_plane_props(&info); - /* Wait until the cursor is unset from the cursor plane before destroying it's BO. */ - if (drm_atomic_commit(video_device, SDL_TRUE)) { - SDL_SetError("Failed atomic commit in KMSDRM_FreeCursor."); - } - KMSDRM_gbm_bo_destroy(curdata->bo); - curdata->bo = NULL; - } - - /* Even if the cursor is not ours, free it. */ SDL_free(cursor->driverdata); SDL_free(cursor); } @@ -365,6 +315,7 @@ KMSDRM_WarpMouseGlobal(int x, int y) { KMSDRM_CursorData *curdata; SDL_Mouse *mouse = SDL_GetMouse(); + SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) { /* Update internal mouse position. */ @@ -372,7 +323,7 @@ KMSDRM_WarpMouseGlobal(int x, int y) /* And now update the cursor graphic position on screen. */ curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata; - if (curdata->bo) { + if (dispdata->cursor_bo) { if (drm_atomic_movecursor(curdata, x, y)) { return SDL_SetError("drm_atomic_movecursor() failed."); } @@ -382,7 +333,8 @@ KMSDRM_WarpMouseGlobal(int x, int y) } else { return SDL_SetError("No mouse or current cursor."); } -return 0; + + return 0; } void @@ -392,8 +344,11 @@ KMSDRM_InitMouse(_THIS) * but there's no point in doing so as there's no multimice support...yet! */ + SDL_VideoDevice *dev = SDL_GetVideoDevice(); + SDL_VideoData *viddata = ((SDL_VideoData *)dev->driverdata); SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); SDL_Mouse *mouse = SDL_GetMouse(); + uint64_t usable_cursor_w, usable_cursor_h; mouse->CreateCursor = KMSDRM_CreateCursor; mouse->ShowCursor = KMSDRM_ShowCursor; @@ -402,20 +357,96 @@ KMSDRM_InitMouse(_THIS) mouse->WarpMouse = KMSDRM_WarpMouse; mouse->WarpMouseGlobal = KMSDRM_WarpMouseGlobal; - /* Init cursor plane, if we haven't yet. */ + /***************************************************************************/ + /* REMEMBER TO BE SURE OF UNDOING ALL THESE STEPS PROPERLY BEFORE CALLING */ + /* gbm_device_destroy, OR YOU WON'T BE ABLE TO CREATE A NEW ONE (ERROR -13 */ + /* ON gbm_create_device). */ + /***************************************************************************/ + + /* 1- Init cursor plane, if we haven't yet. */ if (!dispdata->cursor_plane) { setup_plane(_this, &(dispdata->cursor_plane), DRM_PLANE_TYPE_CURSOR); } + /* 2- Create the cursor GBM BO, if we haven't yet. */ + if (!dispdata->cursor_bo) { + + if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev, GBM_FORMAT_ARGB8888, + GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) + { + SDL_SetError("Unsupported pixel format for cursor"); + return; + } + + if (KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_WIDTH, &usable_cursor_w) || + KMSDRM_drmGetCap(viddata->drm_fd, DRM_CAP_CURSOR_HEIGHT, &usable_cursor_h)) + { + SDL_SetError("Could not get the recommended GBM cursor size"); + goto cleanup; + } + + if (usable_cursor_w == 0 || usable_cursor_h == 0) { + SDL_SetError("Could not get an usable GBM cursor size"); + goto cleanup; + } + + dispdata->cursor_w = usable_cursor_w; + dispdata->cursor_h = usable_cursor_h; + + dispdata->cursor_bo = KMSDRM_gbm_bo_create(viddata->gbm_dev, + usable_cursor_w, usable_cursor_h, + GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + + if (!dispdata->cursor_bo) { + SDL_SetError("Could not create GBM cursor BO"); + goto cleanup; + } + } + + /* SDL expects to set the default cursor on screen when we init the mouse. */ SDL_SetDefaultCursor(KMSDRM_CreateDefaultCursor()); + + return; + +cleanup: + if (dispdata->cursor_bo) { + KMSDRM_gbm_bo_destroy(dispdata->cursor_bo); + dispdata->cursor_bo = NULL; + } } void -KMSDRM_QuitMouse(_THIS) +KMSDRM_DeinitMouse(_THIS) { - /* Free the plane on which the cursor was being shown. */ + SDL_VideoDevice *video_device = SDL_GetVideoDevice(); SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); - free_plane(&dispdata->cursor_plane); + KMSDRM_PlaneInfo info = {0}; + + /*******************************************/ + /* UNDO WHAT WE DID IN KMSDRM_InitMouse(). */ + /*******************************************/ + + /* 1- Destroy the curso GBM BO. */ + if (video_device && dispdata->cursor_bo) { + /* Unsethe the cursor BO from the cursor plane. + (The other members of the plane info are zeroed). */ + info.plane = dispdata->cursor_plane; + drm_atomic_set_plane_props(&info); + /* Wait until the cursor is unset from the cursor plane + before destroying it's BO. */ + if (drm_atomic_commit(video_device, SDL_TRUE)) { + SDL_SetError("Failed atomic commit in KMSDRM_DenitMouse."); + } + /* ..and finally destroy the cursor DRM BO! */ + KMSDRM_gbm_bo_destroy(dispdata->cursor_bo); + dispdata->cursor_bo = NULL; + } + + /* 2- Free the cursor plane, on which the cursor was being shown. */ + if (dispdata->cursor_plane) { + free_plane(&dispdata->cursor_plane); + } + } /* This is called when a mouse motion event occurs */ @@ -429,11 +460,13 @@ KMSDRM_MoveCursor(SDL_Cursor * cursor) That's why we move the cursor graphic ONLY. */ if (mouse && mouse->cur_cursor && mouse->cur_cursor->driverdata) { curdata = (KMSDRM_CursorData *) mouse->cur_cursor->driverdata; + /* Some programs expect cursor movement even while they don't do SwapWindow() calls, and since we ride on the atomic_commit() in SwapWindow() for cursor movement, - cursor won't move in these situations. We could do an atomic_commit() for each - cursor movement request, but it cripples the movement to 30FPS, so a future solution - is needed. SDLPoP "QUIT?" menu is an example of this situation. */ + cursor won't move in these situations. We could do an atomic_commit() here + for each cursor movement request, but it cripples the movement to 30FPS, + so a future solution is needed. SDLPoP "QUIT?" menu is an example of this + situation. */ if (drm_atomic_movecursor(curdata, mouse->x, mouse->y)) { SDL_SetError("drm_atomic_movecursor() failed."); diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.h b/src/video/kmsdrm/SDL_kmsdrmmouse.h index f0a337dc7..8f635066a 100644 --- a/src/video/kmsdrm/SDL_kmsdrmmouse.h +++ b/src/video/kmsdrm/SDL_kmsdrmmouse.h @@ -33,19 +33,13 @@ /* Driverdata with driver-side info about the cursor. */ typedef struct _KMSDRM_CursorData { - struct gbm_bo *bo; - struct plane *plane; - uint32_t crtc_id; uint16_t hot_x, hot_y; uint16_t w, h; - /* The video devide implemented on SDL_kmsdrmvideo.c - * to be used as _THIS pointer in SDL_kmsdrmvideo.c - * functions that need it. */ - SDL_VideoDevice *video; + } KMSDRM_CursorData; extern void KMSDRM_InitMouse(_THIS); -extern void KMSDRM_QuitMouse(_THIS); +extern void KMSDRM_DeinitMouse(_THIS); #endif /* SDL_KMSDRM_mouse_h_ */ diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c index a7f9745bf..c9ec554b4 100644 --- a/src/video/kmsdrm/SDL_kmsdrmopengles.c +++ b/src/video/kmsdrm/SDL_kmsdrmopengles.c @@ -59,11 +59,24 @@ KMSDRM_GLES_DefaultProfileConfig(_THIS, int *mask, int *major, int *minor) #endif } - int KMSDRM_GLES_LoadLibrary(_THIS, const char *path) { + /* Just pretend you do this here, but don't do it until KMSDRM_CreateWindow(), + where we do the same library load we would normally do here. + because this gets called by SDL_CreateWindow() before KMSDR_CreateWindow(), + so gbm dev isn't yet created when this is called, AND we can't alter the + call order in SDL_CreateWindow(). */ +#if 0 NativeDisplayType display = (NativeDisplayType)((SDL_VideoData *)_this->driverdata)->gbm_dev; return SDL_EGL_LoadLibrary(_this, path, display, EGL_PLATFORM_GBM_MESA); +#endif + return 0; +} + +void +KMSDRM_GLES_UnloadLibrary(_THIS) { + /* As with KMSDRM_GLES_LoadLibrary(), we define our own unloading function so + we manually unload the library whenever we want. */ } SDL_EGL_CreateContext_impl(KMSDRM) @@ -94,6 +107,7 @@ static EGLSyncKHR create_fence(int fd, _THIS) EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd, EGL_NONE, }; + EGLSyncKHR fence = _this->egl_data->eglCreateSyncKHR (_this->egl_data->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, attrib_list); @@ -342,16 +356,6 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) { SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata); - /* Get the EGL context, now that SDL_CreateRenderer() has already been called, - and call eglMakeCurrent() on it and the EGL surface. */ -#if SDL_VIDEO_OPENGL_EGL - if (windata->egl_context_pending) { - EGLContext egl_context = (EGLContext)SDL_GL_GetCurrentContext(); - SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context); - windata->egl_context_pending = SDL_FALSE; - } -#endif - if (windata->swap_window == NULL) { /* We want the fenced version by default, but it needs extensions. */ if ( (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) || diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.h b/src/video/kmsdrm/SDL_kmsdrmopengles.h index 2718f96c6..f81b0c3dc 100644 --- a/src/video/kmsdrm/SDL_kmsdrmopengles.h +++ b/src/video/kmsdrm/SDL_kmsdrmopengles.h @@ -32,7 +32,6 @@ /* OpenGLES functions */ #define KMSDRM_GLES_GetAttribute SDL_EGL_GetAttribute #define KMSDRM_GLES_GetProcAddress SDL_EGL_GetProcAddress -#define KMSDRM_GLES_UnloadLibrary SDL_EGL_UnloadLibrary #define KMSDRM_GLES_DeleteContext SDL_EGL_DeleteContext #define KMSDRM_GLES_GetSwapInterval SDL_EGL_GetSwapInterval diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c index 5411d2ebc..19e249644 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -41,6 +41,7 @@ #include "SDL_kmsdrmopengles.h" #include "SDL_kmsdrmmouse.h" #include "SDL_kmsdrmdyn.h" +#include "SDL_kmsdrmvulkan.h" #include #include #include @@ -249,19 +250,20 @@ err: } static void -KMSDRM_DestroyDumbBuffer(_THIS, dumb_buffer *buffer) +KMSDRM_DestroyDumbBuffer(_THIS, dumb_buffer **buffer) { SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); struct drm_mode_destroy_dumb destroy = { - .handle = buffer->gem_handles[0], + .handle = (*buffer)->gem_handles[0], }; - KMSDRM_drmModeRmFB(viddata->drm_fd, buffer->fb_id); + KMSDRM_drmModeRmFB(viddata->drm_fd, (*buffer)->fb_id); - munmap(buffer->dumb.mem, buffer->dumb.size); + munmap((*buffer)->dumb.mem, (*buffer)->dumb.size); KMSDRM_drmIoctl(viddata->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); - free(buffer); + free(*buffer); + *buffer = NULL; } /* Using the CPU mapping, fill the dumb buffer with black pixels. */ @@ -304,7 +306,7 @@ static dumb_buffer *KMSDRM_CreateBuffer(_THIS) return ret; err: - KMSDRM_DestroyDumbBuffer(_this, ret); + KMSDRM_DestroyDumbBuffer(_this, &ret); return NULL; } @@ -389,17 +391,19 @@ void print_plane_info(_THIS, drmModePlanePtr plane) drmModeRes *resources; uint32_t type = 0; SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); - int i; drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); /* Search the plane props for the plane type. */ - for (i = 0; i < props->count_props; i++) { - drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[i]); + for (int j = 0; j < props->count_props; j++) { + + drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[j]); + if ((strcmp(p->name, "type") == 0)) { - type = props->prop_values[i]; + type = props->prop_values[j]; } + KMSDRM_drmModeFreeProperty(p); } @@ -428,7 +432,7 @@ void print_plane_info(_THIS, drmModePlanePtr plane) return; printf("--PLANE ID: %d\nPLANE TYPE: %s\nCRTC READING THIS PLANE: %d\nCRTCS SUPPORTED BY THIS PLANE: ", plane->plane_id, plane_type, plane->crtc_id); - for (i = 0; i < resources->count_crtcs; i++) { + for (int i = 0; i < resources->count_crtcs; i++) { if (plane->possible_crtcs & (1 << i)) { uint32_t crtc_id = resources->crtcs[i]; printf ("%d", crtc_id); @@ -557,16 +561,19 @@ setup_plane(_THIS, struct plane **plane, uint32_t plane_type) { uint32_t plane_id; SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); + int ret = 0; *plane = SDL_calloc(1, sizeof(**plane)); if (!(*plane)) { - return SDL_OutOfMemory(); + ret = SDL_OutOfMemory(); + goto cleanup; } /* Get plane ID. */ plane_id = get_plane_id(_this, plane_type); if (!plane_id) { + ret = SDL_SetError("Invalid Plane ID"); goto cleanup; } @@ -582,7 +589,7 @@ setup_plane(_THIS, struct plane **plane, uint32_t plane_type) sizeof(*(*plane)->props_info)); if ( !((*plane)->props_info) ) { - SDL_OutOfMemory(); + ret = SDL_OutOfMemory(); goto cleanup; } @@ -592,12 +599,15 @@ setup_plane(_THIS, struct plane **plane, uint32_t plane_type) } } - return 0; - cleanup: - SDL_free(*plane); - *plane = NULL; - return -1; + + if (ret != 0) { + if (*plane) { + SDL_free(*plane); + *plane = NULL; + } + } + return ret; } /* Free a plane and it's props. */ @@ -673,7 +683,7 @@ int drm_atomic_commit(_THIS, SDL_bool blocking) if (ret) { SDL_SetError("Atomic commit failed, returned %d.", ret); /* Uncomment this for fast-debugging */ - // printf("ATOMIC COMMIT FAILED: %d.\n", ret); + printf("ATOMIC COMMIT FAILED: %s.\n", strerror(errno)); goto out; } @@ -769,8 +779,6 @@ KMSDRM_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hd return 0; } - - static SDL_VideoDevice * KMSDRM_CreateDevice(int devindex) { @@ -846,7 +854,13 @@ KMSDRM_CreateDevice(int devindex) #endif device->PumpEvents = KMSDRM_PumpEvents; device->free = KMSDRM_DeleteDevice; - +#if SDL_VIDEO_VULKAN + device->Vulkan_LoadLibrary = KMSDRM_Vulkan_LoadLibrary; + device->Vulkan_UnloadLibrary = KMSDRM_Vulkan_UnloadLibrary; + device->Vulkan_GetInstanceExtensions = KMSDRM_Vulkan_GetInstanceExtensions; + device->Vulkan_CreateSurface = KMSDRM_Vulkan_CreateSurface; + device->Vulkan_GetDrawableSize = KMSDRM_Vulkan_GetDrawableSize; +#endif return device; cleanup: @@ -915,8 +929,8 @@ KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo) } /* Create framebuffer object for the buffer. - It's VERY important to note that fb_id is what we ise to set the FB_ID prop of a plane - when using the ATOMIC interface, and we get fb_id it here. */ + It's VERY important to note that fb_id is what we use to set the FB_ID prop + of a plane when using the ATOMIC interface, and we get the fb_id here. */ ret = KMSDRM_drmModeAddFB2(viddata->drm_fd, width, height, format, handles, strides, offsets, &fb_info->fb_id, 0); @@ -938,6 +952,475 @@ KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo) /* _this is a SDL_VideoDevice * */ /*****************************************************************************/ +/* Deinitializes the dispdata members needed for KMSDRM operation that are + inoffeensive for VK compatibility. */ +void KMSDRM_DisplayDataDeinit (_THIS, SDL_DisplayData *dispdata) { + /* Free connector */ + if (dispdata && dispdata->connector) { + if (dispdata->connector->connector) { + KMSDRM_drmModeFreeConnector(dispdata->connector->connector); + dispdata->connector->connector = NULL; + } + if (dispdata->connector->props_info) { + SDL_free(dispdata->connector->props_info); + dispdata->connector->props_info = NULL; + } + SDL_free(dispdata->connector); + dispdata->connector = NULL; + } + + /* Free CRTC */ + if (dispdata && dispdata->crtc) { + if (dispdata->crtc->crtc) { + KMSDRM_drmModeFreeCrtc(dispdata->crtc->crtc); + dispdata->crtc->crtc = NULL; + } + if (dispdata->crtc->props_info) { + SDL_free(dispdata->crtc->props_info); + dispdata->crtc->props_info = NULL; + } + SDL_free(dispdata->crtc); + dispdata->crtc = NULL; + } +} + +/* Initializes the dispdata members needed for KMSDRM operation that are + inoffeensive for VK compatibility, except we must leave the drm_fd + closed when we get to the end of this function. + This is to be called early, in VideoInit(), because it gets us + the videomode information, which SDL needs immediately after VideoInit(). */ +int KMSDRM_DisplayDataInit (_THIS, SDL_DisplayData *dispdata) { + SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); + + drmModeRes *resources = NULL; + drmModeEncoder *encoder = NULL; + drmModeConnector *connector = NULL; + drmModeCrtc *crtc = NULL; + + char devname[32]; + int ret = 0; + unsigned i,j; + + dispdata->atomic_flags = 0; + dispdata->atomic_req = NULL; + dispdata->kms_fence = NULL; + dispdata->gpu_fence = NULL; + dispdata->kms_out_fence_fd = -1; + dispdata->dumb_buffer = NULL; + dispdata->modeset_pending = SDL_FALSE; + dispdata->gbm_init = SDL_FALSE; + + dispdata->display_plane = NULL; + dispdata->cursor_plane = NULL; + + dispdata->cursor_bo = NULL; + + /* Open /dev/dri/cardNN */ + SDL_snprintf(viddata->devpath, sizeof(viddata->devpath), "/dev/dri/card%d", viddata->devindex); + + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device %s", devname); + viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC); + + if (viddata->drm_fd < 0) { + ret = SDL_SetError("Could not open %s", devname); + goto cleanup; + } + + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd); + + /* Try ATOMIC compatibility */ + ret = check_atomic_modesetting(viddata->drm_fd); + if (ret) { + ret = SDL_SetError("not compatible with atomic modesetting"); + goto cleanup; + } + + /********************************************/ + /* Block for enabling ATOMIC compatibility. */ + /********************************************/ + + /* Set ATOMIC compatibility */ + /* TODO: We have just tried ATOMIC compatibility, haven't we? */ + ret = KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) { + ret = SDL_SetError("no atomic modesetting support."); + goto cleanup; + } + + /* Set UNIVERSAL PLANES compatibility */ + ret = KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ret) { + ret = SDL_SetError("no universal planes support."); + goto cleanup; + } + + /*******************************************/ + /* Block for getting the ATOMIC resources. */ + /*******************************************/ + + /* Get all of the available connectors / devices / crtcs */ + resources = KMSDRM_drmModeGetResources(viddata->drm_fd); + if (!resources) { + ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd); + goto cleanup; + } + + /* Iterate on the available connectors to find a connected connector. */ + for (i = 0; i < resources->count_connectors; i++) { + drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, + resources->connectors[i]); + + if (!conn) { + continue; + } + + if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) { + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.", + conn->connector_id, conn->count_modes); + connector = conn; + + break; + } + + KMSDRM_drmModeFreeConnector(conn); + } + + if (!connector) { + ret = SDL_SetError("No currently active connector found."); + goto cleanup; + } + + /* Try to find the connector's current encoder */ + for (i = 0; i < resources->count_encoders; i++) { + encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); + + if (!encoder) { + continue; + } + + if (encoder->encoder_id == connector->encoder_id) { + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id); + break; + } + + KMSDRM_drmModeFreeEncoder(encoder); + encoder = NULL; + } + + if (!encoder) { + /* No encoder was connected, find the first supported one */ + for (i = 0; i < resources->count_encoders; i++) { + encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); + + if (!encoder) { + continue; + } + + for (j = 0; j < connector->count_encoders; j++) { + if (connector->encoders[j] == encoder->encoder_id) { + break; + } + } + + if (j != connector->count_encoders) { + break; + } + + KMSDRM_drmModeFreeEncoder(encoder); + encoder = NULL; + } + } + + if (!encoder) { + ret = SDL_SetError("No connected encoder found."); + goto cleanup; + } + + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id); + + /* Try to find a CRTC connected to this encoder */ + crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id); + + /* If no CRTC was connected to the encoder, find the first CRTC + that is supported by the encoder, and use that. */ + if (!crtc) { + for (i = 0; i < resources->count_crtcs; i++) { + if (encoder->possible_crtcs & (1 << i)) { + encoder->crtc_id = resources->crtcs[i]; + crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id); + break; + } + } + } + + if (!crtc) { + ret = SDL_SetError("No CRTC found."); + goto cleanup; + } + + /* Figure out the default mode to be set. */ + dispdata->mode = crtc->mode; + + /* Find the connector's preferred mode, to be used in case the current mode + is not valid, or if restoring the current mode fails. + We can always count on the preferred mode! */ + for (i = 0; i < connector->count_modes; i++) { + if (connector->modes[i].type & DRM_MODE_TYPE_PREFERRED) { + dispdata->preferred_mode = connector->modes[i]; + } + } + + /* If the current CRTC's mode isn't valid, select the preferred + mode of the connector. */ + if (crtc->mode_valid == 0) { + dispdata->mode = dispdata->preferred_mode; + } + + if (dispdata->mode.hdisplay == 0 || dispdata->mode.vdisplay == 0 ) { + ret = SDL_SetError("Couldn't get a valid connector videomode."); + goto cleanup; + } + + /* Get CRTC properties */ + dispdata->crtc->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, + crtc->crtc_id, DRM_MODE_OBJECT_CRTC); + + dispdata->crtc->props_info = SDL_calloc(dispdata->crtc->props->count_props, + sizeof(*dispdata->crtc->props_info)); + + if (!dispdata->crtc->props_info) { + ret = SDL_OutOfMemory(); + goto cleanup; + } + + for (i = 0; i < dispdata->crtc->props->count_props; i++) { + dispdata->crtc->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, + dispdata->crtc->props->props[i]); + } + + /* Get connector properties */ + dispdata->connector->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, + connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); + + dispdata->connector->props_info = SDL_calloc(dispdata->connector->props->count_props, + sizeof(*dispdata->connector->props_info)); + + if (!dispdata->connector->props_info) { + ret = SDL_OutOfMemory(); + goto cleanup; + } + + for (i = 0; i < dispdata->connector->props->count_props; i++) { + dispdata->connector->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, + dispdata->connector->props->props[i]); + } + + /* Store the connector and crtc for future use. This is all we keep from this function, + and these are just structs, inoffensive to VK. */ + dispdata->connector->connector = connector; + dispdata->crtc->crtc = crtc; + + /***********************************/ + /* Block fpr Vulkan compatibility. */ + /***********************************/ + + /* THIS IS FOR VULKAN! Leave the FD closed, so VK can work. + Will reopen this in CreateWindow, but only if requested a non-VK window. */ + KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 0); + KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0); + close (viddata->drm_fd); + viddata->drm_fd = -1; + +cleanup: + // TODO Use it as a list to see if everything we use here is freed. + if (encoder) + KMSDRM_drmModeFreeEncoder(encoder); + if (resources) + KMSDRM_drmModeFreeResources(resources); + if (ret != 0) { + /* Error (complete) cleanup */ + if (dispdata->connector->connector) { + KMSDRM_drmModeFreeConnector(dispdata->connector->connector); + dispdata->connector->connector = NULL; + } + if (dispdata->crtc->crtc) { + KMSDRM_drmModeFreeCrtc(dispdata->crtc->crtc); + dispdata->crtc->crtc = NULL; + } + if (viddata->drm_fd >= 0) { + close(viddata->drm_fd); + viddata->drm_fd = -1; + } + SDL_free(dispdata); + } + + return ret; +} + +/* Init the Vulkan-INCOMPATIBLE stuff: + Reopen FD, create gbm dev, create dumb buffer and setup display plane. + This is to be called late, in WindowCreate(), and ONLY if this is not + a Vulkan window. + We are doing this so late to allow Vulkan to work if we build a VK window. + These things are incompatible with Vulkan, which accesses the same resources + internally so they must be free when trying to build a Vulkan surface. +*/ +int +KMSDRM_GBMInit (_THIS, SDL_DisplayData *dispdata) +{ + SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata; + int ret = 0; + + /* Reopen the FD! */ + viddata->drm_fd = open(viddata->devpath, O_RDWR | O_CLOEXEC); + KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 1); + KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + + /* Create aux dumb buffer. It's only useful to keep the PRIMARY PLANE occupied + when we destroy the GBM surface and it's KMS buffers, so not being able to + create it is not fatal. */ + dispdata->dumb_buffer = KMSDRM_CreateBuffer(_this); + if (!dispdata->dumb_buffer) { + ret = SDL_SetError("can't create dumb buffer."); + } else { + /* Fill the dumb buffer with black pixels. */ + KMSDRM_FillDumbBuffer(dispdata->dumb_buffer); + } + + /* Create the GBM device. */ + viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd); + if (!viddata->gbm_dev) { + ret = SDL_SetError("Couldn't create gbm device."); + } + + /* Setup the display plane. ONLY do this after dispdata has the right + crtc and connector, because these are used in this function. */ + ret = setup_plane(_this, &(dispdata->display_plane), DRM_PLANE_TYPE_PRIMARY); + if (ret) { + ret = SDL_SetError("can't find suitable display plane."); + } + + dispdata->gbm_init = SDL_TRUE; + + return ret; +} + +/* Deinit the Vulkan-incompatible KMSDRM stuff. */ +void +KMSDRM_GBMDeinit (_THIS, SDL_DisplayData *dispdata) +{ + KMSDRM_PlaneInfo plane_info = {0}; + SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); + drmModeModeInfo mode; + + uint32_t blob_id; + + /*****************************************************************/ + /* */ + /* BLOCK to safely destroy the DUMB BUFFER. */ + /* */ + /* We come from DestroyWindow(), where both DestroySurfaces() */ + /* and this GBMDeinit() are called, one after another. */ + /* So the GBM/EGL surfaces and buffers of the windows have */ + /* already been destroyed already and, because of that, the */ + /* PRIMARY PLANE is using the DUMB BUFFER. BUT the DUMB BUFFER */ + /* we use to keep the PRIMARY PLANE occupied when we do */ + /* DestroySurfaces calls is going to be destroyed one way or */ + /* another when the program quits so, to prevent the the kernel */ + /* from disabling the CRTC when it detects the deletion of a */ + /* buffer that IS IN USE BY THE PRIMARY PLANE, we do one of */ + /* these: */ + /* */ + /* -In AMDGPU, where manually disabling the CRTC and */ + /* disconnecting the CONNECTOR from the CRTC is an */ + /* unrecoverable situation, we point the PRIMARY PLANE to */ + /* the original TTY buffer (not guaranteed to be there for us!) */ + /* and then destroy the DUMB BUFFER). */ + /* */ + /* -In other drivers, we disconnect the CONNECTOR from the CRTC */ + /* (remember: several connectors can read a CRTC), deactivate */ + /* the CRTC, and set the PRIMARY PLANE props CRTC_ID and FB_ID */ + /* to 0. Then we destroy the DUMB BUFFER. */ + /* We can leave all like this if we are exiting the program: */ + /* FBCON or whatever will reconfigure things as it needs. */ + /* */ + /*****************************************************************/ + + /* dispdata->crtc->crtc->mode is the original video mode that was + configured on the connector when we lauched the program, + dispdata->mode is the current video mode, which could be different, + and dispdata->preferred_mode is the native display mode. */ + mode = dispdata->crtc->crtc->mode; + +#if AMDGPU_COMPAT + /* This won't work if the console wasn't correctly restored when a prevous + program exited, because in that case the TTY buffer won't be in + crtc->crtc->buffer_id, so the following atomic commit will fail. */ + plane_info.plane = dispdata->display_plane; + plane_info.crtc_id = dispdata->crtc->crtc->crtc_id; + plane_info.fb_id = dispdata->crtc->crtc->buffer_id; + plane_info.src_w = mode.hdisplay; + plane_info.src_h = mode.vdisplay; + plane_info.crtc_w = mode.hdisplay; + plane_info.crtc_h = mode.vdisplay; + + drm_atomic_set_plane_props(&plane_info); +#else + add_connector_property(dispdata->atomic_req, dispdata->connector , "CRTC_ID", 0); + add_crtc_property(dispdata->atomic_req, dispdata->crtc , "ACTIVE", 0); + + /* Since we initialize plane_info to all zeros, + ALL PRIMARY PLANE props are set to 0 with this, + including FB_ID and CRTC_ID. + Not all drivers like FB_ID and CRTC_ID to 0 yet. */ + plane_info.plane = dispdata->display_plane; + drm_atomic_set_plane_props(&plane_info); +#endif + + /* Set the props that restore the original video mode. */ + dispdata->atomic_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + add_connector_property(dispdata->atomic_req, dispdata->connector, "CRTC_ID", dispdata->crtc->crtc->crtc_id); + KMSDRM_drmModeCreatePropertyBlob(viddata->drm_fd, &mode, sizeof(mode), &blob_id); + add_crtc_property(dispdata->atomic_req, dispdata->crtc, "MODE_ID", blob_id); + add_crtc_property(dispdata->atomic_req, dispdata->crtc, "ACTIVE", 1); + + /* Issue blocking atomic commit. */ + if (drm_atomic_commit(_this, SDL_TRUE)) { + SDL_SetError("Failed to issue atomic commit on video quitting."); + } + + /* Destroy the DUMB buffer if it exists, now that it's not being + used anymore by the PRIMARY PLANE. */ + if (dispdata->dumb_buffer) { + KMSDRM_DestroyDumbBuffer(_this, &dispdata->dumb_buffer); + } + + /*************************************************/ + /* BLOCK to safely destroy the dumb buffer ENDS. */ + /*************************************************/ + + /* Free display plane */ + free_plane(&dispdata->display_plane); + + /* Free cursor plane (if still not freed) */ + free_plane(&dispdata->cursor_plane); + + /* Destroy GBM device. GBM surface is destroyed by DestroySurfaces(), + already called when we get here. */ + if (viddata->gbm_dev) { + KMSDRM_gbm_device_destroy(viddata->gbm_dev); + viddata->gbm_dev = NULL; + } + + /* Finally close DRM FD. May be reopen on next non-vulkan window creation. */ + if (viddata->drm_fd >= 0) { + close(viddata->drm_fd); + viddata->drm_fd = -1; + } + + dispdata->gbm_init = SDL_FALSE; +} + /* Destroy the window surfaces and buffers. Have the PRIMARY PLANE disconnected from these buffers before doing so, or have the PRIMARY PLANE reading the original FB where the TTY lives, before doing this, or CRTC will @@ -955,7 +1438,7 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window) /********************************************************************/ /* BLOCK 1: protect the PRIMARY PLANE before destroying the buffers */ - /* it's using. */ + /* it's using, by making it point to the dumb buffer. */ /********************************************************************/ plane_info.plane = dispdata->display_plane; @@ -970,7 +1453,7 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window) /* Issue blocking atomic commit. */ if (drm_atomic_commit(_this, SDL_TRUE)) { - SDL_SetError("Failed to issue atomic commit on window destruction."); + SDL_SetError("Failed to issue atomic commit on surfaces destruction."); } /****************************************************************************/ @@ -990,9 +1473,9 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window) windata->next_bo = NULL; } - /* Destroy the EGL surface. */ -#if SDL_VIDEO_OPENGL_EGL + /***************************************************************************/ + /* Destroy the EGL surface. */ /* In this eglMakeCurrent() call, we disable the current EGL surface */ /* because we're going to destroy it, but DON'T disable the EGL context, */ /* because it won't be enabled again until the programs ask for a pageflip */ @@ -1001,19 +1484,22 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window) /* the context version info before calling for a pageflip, the program */ /* will get wrong info and we will be in trouble. */ /***************************************************************************/ + +#if SDL_VIDEO_OPENGL_EGL egl_context = (EGLContext)SDL_GL_GetCurrentContext(); SDL_EGL_MakeCurrent(_this, EGL_NO_SURFACE, egl_context); if (windata->egl_surface != EGL_NO_SURFACE) { SDL_EGL_DestroySurface(_this, windata->egl_surface); windata->egl_surface = EGL_NO_SURFACE; - } + } #endif if (windata->gs) { KMSDRM_gbm_surface_destroy(windata->gs); windata->gs = NULL; } + } int @@ -1026,8 +1512,12 @@ KMSDRM_CreateSurfaces(_THIS, SDL_Window * window) uint32_t surface_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; uint32_t width, height; + EGLContext egl_context; + /* Destroy the surfaces and buffers before creating the new ones. */ - KMSDRM_DestroySurfaces(_this, window); + // TODO REENABLE THIS IF CGENIUS FAILS BECAUSE IT CREATES A NEW WINDOW + // W/O DESTRYING THE PREVIOUS ONE. + //KMSDRM_DestroySurfaces(_this, window); if (((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) || ((window->flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)) { @@ -1049,7 +1539,7 @@ KMSDRM_CreateSurfaces(_THIS, SDL_Window * window) return SDL_SetError("Could not create GBM surface"); } -#if SDL_VIDEO_OPENGL_EGL +#if SDL_VIDEO_OPENGL_EGL //TODO Restore this lo LibraryLoad and Unload calls. /* We can't get the EGL context yet because SDL_CreateRenderer has not been called, but we need an EGL surface NOW, or GL won't be able to render into any surface and we won't see the first frame. */ @@ -1060,10 +1550,12 @@ KMSDRM_CreateSurfaces(_THIS, SDL_Window * window) return SDL_SetError("Could not create EGL window surface"); } - /* Take note that we're still missing the EGL contex, - so we can get it in SwapWindow, when SDL_CreateRenderer() - has already been called. */ - windata->egl_context_pending = SDL_TRUE; + /* Current context passing to EGL is now done here. If something fails, + go back to delayed SDL_EGL_MakeCurrent() call in SwapWindow. */ + /* TODO Errorcheck on this (may lead to delayed call again...) */ + egl_context = (EGLContext)SDL_GL_GetCurrentContext(); + SDL_EGL_MakeCurrent(_this, windata->egl_surface, egl_context); + #endif return 0; @@ -1073,19 +1565,52 @@ void KMSDRM_DestroyWindow(_THIS, SDL_Window *window) { SDL_WindowData *windata = (SDL_WindowData *) window->driverdata; - SDL_VideoData *viddata; + SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata; + SDL_VideoData *viddata = windata->viddata; + SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; /* Is this a VK window? */ unsigned int i, j; if (!windata) { return; } - KMSDRM_DestroySurfaces(_this, window); + if (!is_vulkan) { + + KMSDRM_DestroySurfaces(_this, window); + + KMSDRM_DeinitMouse(_this); + + if (_this->egl_data) { + SDL_EGL_UnloadLibrary(_this); + } + + if (dispdata->gbm_init) { + KMSDRM_GBMDeinit(_this, dispdata); + } + } + +#if AMDGPU_COMPAT + /* Since vkDestroySurfaceKHR does not destroy the native surface (only the Vulkan one), + runnin Vulkan programs leave the last buffer connected to the primary plane, + at least on AMDGPU, and the TTY buffer isn't restored. + That's not a big problem, but running a GLES program after that will cause the + atomic commit we do for restoring the TTY buffer to fail, because the TTY buffer + isn't there when we launch the GLES program. + So what we do here is "hack" Vulkan and restore the TTY buffer here, as if + we were running a GLES program. We get here after the program's vkDestroySurfaceKHR + has been called, which allows us to think as if we were in the beginig + of a GLES program. + THIS IS DONE IN A SEPARATE BLOCK FOR POSSIBLE EASY FUTURE REMOVAL. DON'T OPTIMIZE. */ + + if (is_vulkan) { + KMSDRM_GBMInit(_this, dispdata); + KMSDRM_GBMDeinit(_this, dispdata); + } +#endif /********************************************/ /* Remove from the internal SDL window list */ /********************************************/ - viddata = windata->viddata; for (i = 0; i < viddata->num_windows; i++) { if (viddata->windows[i] == window) { @@ -1115,6 +1640,7 @@ static int KMSDRM_ReconfigureWindow( _THIS, SDL_Window * window) { SDL_WindowData *windata = window->driverdata; SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata; + SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; /* Is this a VK window? */ float ratio; if (((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) || @@ -1139,25 +1665,25 @@ KMSDRM_ReconfigureWindow( _THIS, SDL_Window * window) { } - if (KMSDRM_CreateSurfaces(_this, window)) { - return -1; - } - + if (!is_vulkan) { + if (KMSDRM_CreateSurfaces(_this, window)) { + return -1; + } + } return 0; } int KMSDRM_VideoInit(_THIS) { - unsigned int i, j; int ret = 0; + SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); SDL_DisplayData *dispdata = NULL; - drmModeRes *resources = NULL; - drmModeEncoder *encoder = NULL; - char devname[32]; SDL_VideoDisplay display = {0}; + SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()"); + viddata->video_init = SDL_FALSE; dispdata = (SDL_DisplayData *) SDL_calloc(1, sizeof(SDL_DisplayData)); @@ -1165,182 +1691,35 @@ KMSDRM_VideoInit(_THIS) return SDL_OutOfMemory(); } + /* Alloc memory for these. */ dispdata->display_plane = SDL_calloc(1, sizeof(*dispdata->display_plane)); dispdata->crtc = SDL_calloc(1, sizeof(*dispdata->crtc)); dispdata->connector = SDL_calloc(1, sizeof(*dispdata->connector)); if (!(dispdata->display_plane) || !(dispdata->crtc) || !(dispdata->connector)) { - return SDL_OutOfMemory(); - } - - dispdata->atomic_flags = 0; - dispdata->atomic_req = NULL; - dispdata->kms_fence = NULL; - dispdata->gpu_fence = NULL; - dispdata->kms_out_fence_fd = -1; - dispdata->dumb_buffer = NULL; - dispdata->modeset_pending = SDL_FALSE; - - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "KMSDRM_VideoInit()"); - - /* Open /dev/dri/cardNN */ - SDL_snprintf(devname, sizeof(devname), "/dev/dri/card%d", viddata->devindex); - - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opening device %s", devname); - viddata->drm_fd = open(devname, O_RDWR | O_CLOEXEC); - - if (viddata->drm_fd < 0) { - ret = SDL_SetError("Could not open %s", devname); + ret = SDL_OutOfMemory(); goto cleanup; } - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd); + /* Setup the single display that's available. + There's no problem with it being still incomplete. */ + display.driverdata = dispdata; - /* Try ATOMIC compatibility */ - - ret = check_atomic_modesetting(viddata->drm_fd); - if (ret) { + /* Get KMSDRM resources info and store what we need. Getting and storing + this info isn't a problem for VK compatibility. + For VK-incompatible initializations we have KMSDRM_GBMInit(), which is + called on window creation, and only when we know it's not a VK window. */ + if (KMSDRM_DisplayDataInit(_this, dispdata)) { + ret = SDL_SetError("error getting KMS/DRM information"); goto cleanup; } - ret = KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - if (ret) { - ret = SDL_SetError("no universal planes support."); - goto cleanup; - } - - /* Create the GBM device */ - - viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd); - if (!viddata->gbm_dev) { - ret = SDL_SetError("Couldn't create gbm device."); - goto cleanup; - } - - /* Get all of the available connectors / devices / crtcs */ - resources = KMSDRM_drmModeGetResources(viddata->drm_fd); - if (!resources) { - ret = SDL_SetError("drmModeGetResources(%d) failed", viddata->drm_fd); - goto cleanup; - } - - /* Iterate on the available connectors to find a connected connector. */ - for (i = 0; i < resources->count_connectors; i++) { - drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]); - - if (!conn) { - continue; - } - - if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) { - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.", - conn->connector_id, conn->count_modes); - dispdata->connector->connector = conn; - break; - } - - KMSDRM_drmModeFreeConnector(conn); - } - - if (!dispdata->connector->connector) { - ret = SDL_SetError("No currently active connector found."); - goto cleanup; - } - - /* Try to find the connector's current encoder */ - for (i = 0; i < resources->count_encoders; i++) { - encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); - - if (!encoder) { - continue; - } - - if (encoder->encoder_id == dispdata->connector->connector->encoder_id) { - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id); - break; - } - - KMSDRM_drmModeFreeEncoder(encoder); - encoder = NULL; - } - - if (!encoder) { - /* No encoder was connected, find the first supported one */ - for (i = 0; i < resources->count_encoders; i++) { - encoder = KMSDRM_drmModeGetEncoder(viddata->drm_fd, resources->encoders[i]); - - if (!encoder) { - continue; - } - - for (j = 0; j < dispdata->connector->connector->count_encoders; j++) { - if (dispdata->connector->connector->encoders[j] == encoder->encoder_id) { - break; - } - } - - if (j != dispdata->connector->connector->count_encoders) { - break; - } - - KMSDRM_drmModeFreeEncoder(encoder); - encoder = NULL; - } - } - - if (!encoder) { - ret = SDL_SetError("No connected encoder found."); - goto cleanup; - } - - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id); - - /* Try to find a CRTC connected to this encoder */ - dispdata->crtc->crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id); - - /* If no CRTC was connected to the encoder, find the first CRTC that is supported by the encoder, and use that. */ - if (!dispdata->crtc->crtc) { - for (i = 0; i < resources->count_crtcs; i++) { - if (encoder->possible_crtcs & (1 << i)) { - encoder->crtc_id = resources->crtcs[i]; - dispdata->crtc->crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id); - break; - } - } - } - - if (!dispdata->crtc->crtc) { - ret = SDL_SetError("No CRTC found."); - goto cleanup; - } - - /* Figure out the default mode to be set. If the current CRTC's mode isn't - valid, select the first mode supported by the connector - - FIXME find first mode that specifies DRM_MODE_TYPE_PREFERRED */ - dispdata->mode = dispdata->crtc->crtc->mode; - - if (dispdata->crtc->crtc->mode_valid == 0) { - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, - "Current mode is invalid, selecting connector's mode #0."); - dispdata->mode = dispdata->connector->connector->modes[0]; - } - - /* Setup the single display that's available */ - display.desktop_mode.w = dispdata->mode.hdisplay; display.desktop_mode.h = dispdata->mode.vdisplay; display.desktop_mode.refresh_rate = dispdata->mode.vrefresh; -#if 1 display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888; -#else - /* FIXME */ - drmModeFB *fb = drmModeGetFB(viddata->drm_fd, dispdata->crtc->buffer_id); - display.desktop_mode.format = drmToSDLPixelFormat(fb->bpp, fb->depth); - drmModeFreeFB(fb); -#endif - display.current_mode = display.desktop_mode; - display.driverdata = dispdata; + + /* Add the display only when it's ready, */ SDL_AddVideoDisplay(&display, SDL_FALSE); /* Use this if you ever need to see info on all available planes. */ @@ -1348,263 +1727,48 @@ KMSDRM_VideoInit(_THIS) get_planes_info(_this); #endif - /* Setup display plane */ - ret = setup_plane(_this, &(dispdata->display_plane), DRM_PLANE_TYPE_PRIMARY); - if (ret) { - ret = SDL_SetError("can't find suitable display plane."); - goto cleanup; - } - - /* Get CRTC properties */ - dispdata->crtc->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, - dispdata->crtc->crtc->crtc_id, DRM_MODE_OBJECT_CRTC); - - dispdata->crtc->props_info = SDL_calloc(dispdata->crtc->props->count_props, - sizeof(*dispdata->crtc->props_info)); - - if (!dispdata->crtc->props_info) { - ret = SDL_OutOfMemory(); - goto cleanup; - } - - for (i = 0; i < dispdata->crtc->props->count_props; i++) { - dispdata->crtc->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, - dispdata->crtc->props->props[i]); - } - - /* Get connector properties */ - dispdata->connector->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, - dispdata->connector->connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); - - dispdata->connector->props_info = SDL_calloc(dispdata->connector->props->count_props, - sizeof(*dispdata->connector->props_info)); - - if (!dispdata->connector->props_info) { - ret = SDL_OutOfMemory(); - goto cleanup; - } - - for (i = 0; i < dispdata->connector->props->count_props; i++) { - dispdata->connector->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, - dispdata->connector->props->props[i]); - } - - /* Create aux dumb buffer. It's only useful to keep the PRIMARY PLANE occupied - when we destroy the GBM surface and it's KMS buffers, so not being able to - create it is not fatal. */ - dispdata->dumb_buffer = KMSDRM_CreateBuffer(_this); - if (!dispdata->dumb_buffer) { - ret = SDL_SetError("can't create dumb buffer."); - } else { - /* Fill the dumb buffer with black pixels. */ - KMSDRM_FillDumbBuffer(dispdata->dumb_buffer); - } - - /*********************/ - /* Atomic block ends */ - /*********************/ - #ifdef SDL_INPUT_LINUXEV SDL_EVDEV_Init(); #endif - KMSDRM_InitMouse(_this); - viddata->video_init = SDL_TRUE; cleanup: - if (encoder) - KMSDRM_drmModeFreeEncoder(encoder); - if (resources) - KMSDRM_drmModeFreeResources(resources); - if (ret != 0) { - /* Error (complete) cleanup */ - if (dispdata->connector->connector) { - KMSDRM_drmModeFreeConnector(dispdata->connector->connector); - dispdata->connector = NULL; - } - if (dispdata->crtc->crtc) { - KMSDRM_drmModeFreeCrtc(dispdata->crtc->crtc); - dispdata->crtc = NULL; - } - if (viddata->gbm_dev) { - KMSDRM_gbm_device_destroy(viddata->gbm_dev); - viddata->gbm_dev = NULL; - } - if (viddata->drm_fd >= 0) { - close(viddata->drm_fd); - viddata->drm_fd = -1; - } - SDL_free(dispdata); + if (ret) { + if (dispdata->display_plane) { + SDL_free(dispdata->display_plane); + } + if (dispdata->crtc) { + SDL_free(dispdata->crtc); + } + if (dispdata->connector) { + SDL_free(dispdata->connector); + } } return ret; } -/* The driverdata pointers, like dispdata, viddata, etc... +/* The driverdata pointers, like dispdata, viddata, windata, etc... are freed by SDL internals, so not our job. */ void KMSDRM_VideoQuit(_THIS) { SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); - SDL_DisplayData *dispdata; + SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); - KMSDRM_PlaneInfo plane_info = {0}; - drmModeModeInfo mode; - - uint32_t blob_id; - - /* Video was not initialized properly, hence SDL internals - called VideoQuit(). We will segault somewhere if we go further. */ - if (!viddata->video_init) { - return; - } - - /* Don't call any SDL_GetDisplay* function until we get sure that - VideoInit() succeeded, because the SDL_GetDisplay* call would - throw it's own SDL_SetError(), overwritting the error set in - the failed VideoInit() call. */ - dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0); - - /*****************************************************************/ - /* */ - /* BLOCK to safely destroy the DUMB BUFFER. */ - /* */ - /* Since the program should already have called DestroyWindow() */ - /* on all the windows by now, there's no need to destroy the */ - /* GBM/EGL surfaces and buffers of the windows here: they have */ - /* already been destroyed, and the PRIMARY PLANE is using the */ - /* DUMB BUFFER. BUT the DUMB BUFFER we use to keep the PRIMARY */ - /* PLANE occupied when we do DestroySurfaces calls is going to */ - /* be destroyed one way or another when the program quits, so */ - /* to avoid the kernel disabling the CRTC when it detects the */ - /* deletion of a buffer that IS IN USE BY THE PRIMARY PLANE, */ - /* we do one of these: */ - /* */ - /* -In AMDGPU, where manually disabling the CRTC and */ - /* disconnecting the CONNECTOR from the CRTC is an */ - /* unrecoverable situation, so we point the PRIMARY PLANE to */ - /* the original TTY buffer (not guaranteed to be there for us!) */ - /* and then destroy the DUMB BUFFER). */ - /* */ - /* -In other drivers, we disconnect the CONNECTOR from the CRTC */ - /* (remember: several connectors can read a CRTC), deactivate */ - /* the CRTC, and set the PRIMARY PLANE props CRTC_ID and FB_ID */ - /* to 0. Then we destroy the DUMB BUFFER. */ - /* We can leave all like this if we are exiting the program: */ - /* FBCON or whatever will reconfigure things as it needs. */ - /* */ - /*****************************************************************/ - - mode = dispdata->crtc->crtc->mode; - -#if AMDGPU_COMPAT - plane_info.plane = dispdata->display_plane; - plane_info.crtc_id = dispdata->crtc->crtc->crtc_id; - plane_info.fb_id = dispdata->crtc->crtc->buffer_id; - plane_info.src_w = mode.hdisplay; - plane_info.src_h = mode.vdisplay; - plane_info.crtc_w = mode.hdisplay; - plane_info.crtc_h = mode.vdisplay; - - drm_atomic_set_plane_props(&plane_info); - -#else - - add_connector_property(dispdata->atomic_req, dispdata->connector , "CRTC_ID", 0); - add_crtc_property(dispdata->atomic_req, dispdata->crtc , "ACTIVE", 0); - - /* Since we initialize plane_info to all zeros, ALL PRIMARY PLANE props are set to 0 with this, - including FB_ID and CRTC_ID. Not all drivers like FB_ID and CRTC_ID to 0 yet. */ - plane_info.plane = dispdata->display_plane; - - drm_atomic_set_plane_props(&plane_info); + KMSDRM_DisplayDataDeinit(_this, dispdata); +#ifdef SDL_INPUT_LINUXEV + SDL_EVDEV_Quit(); #endif - /* Set props that restore the original video mode. */ - dispdata->atomic_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; - add_connector_property(dispdata->atomic_req, dispdata->connector, "CRTC_ID", dispdata->crtc->crtc->crtc_id); - KMSDRM_drmModeCreatePropertyBlob(viddata->drm_fd, &mode, sizeof(mode), &blob_id); - add_crtc_property(dispdata->atomic_req, dispdata->crtc, "MODE_ID", blob_id); - add_crtc_property(dispdata->atomic_req, dispdata->crtc, "ACTIVE", 1); - - /* Issue blocking atomic commit. */ - if (drm_atomic_commit(_this, SDL_TRUE)) { - SDL_SetError("Failed to issue atomic commit on video quitting."); - } - - /* Destroy the DUMB buffer if it exists, now that it's not being - used anymore by the PRIMARY PLANE. */ - if (dispdata->dumb_buffer) { - KMSDRM_DestroyDumbBuffer(_this, dispdata->dumb_buffer); - } - - /***************/ - /* BLOCK ENDS. */ - /***************/ - /* Clear out the window list */ SDL_free(viddata->windows); viddata->windows = NULL; viddata->max_windows = 0; viddata->num_windows = 0; - -#if SDL_VIDEO_OPENGL_EGL - if (_this->gl_config.driver_loaded) { - SDL_GL_UnloadLibrary(); - } -#endif - - /* Free connector */ - if (dispdata && dispdata->connector) { - if (dispdata->connector->connector) { - KMSDRM_drmModeFreeConnector(dispdata->connector->connector); - dispdata->connector->connector = NULL; - } - if (dispdata->connector->props_info) { - SDL_free(dispdata->connector->props_info); - dispdata->connector->props_info = NULL; - } - SDL_free(dispdata->connector); - dispdata->connector = NULL; - } - - /* Free CRTC */ - if (dispdata && dispdata->crtc) { - if (dispdata->crtc->crtc) { - KMSDRM_drmModeFreeCrtc(dispdata->crtc->crtc); - dispdata->crtc->crtc = NULL; - } - if (dispdata->crtc->props_info) { - SDL_free(dispdata->crtc->props_info); - dispdata->crtc->props_info = NULL; - } - SDL_free(dispdata->crtc); - dispdata->crtc = NULL; - } - - /* Free display plane */ - free_plane(&dispdata->display_plane); - - /* Free cursor plane (if still not freed) */ - free_plane(&dispdata->cursor_plane); - - /* Destroy GBM device. GBM surface is destroyed by DestroySurfaces(), - already called by DestroyWindow() when we get here. */ - if (viddata->gbm_dev) { - KMSDRM_gbm_device_destroy(viddata->gbm_dev); - viddata->gbm_dev = NULL; - } - if (viddata->drm_fd >= 0) { - close(viddata->drm_fd); - SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Closed DRM FD %d", viddata->drm_fd); - viddata->drm_fd = -1; - } -#ifdef SDL_INPUT_LINUXEV - SDL_EVDEV_Quit(); -#endif viddata->video_init = SDL_FALSE; } @@ -1690,19 +1854,36 @@ KMSDRM_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) int KMSDRM_CreateWindow(_THIS, SDL_Window * window) { - SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata; - SDL_VideoDisplay *display = NULL; - SDL_DisplayData *dispdata = NULL; SDL_WindowData *windata = NULL; + SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata; + SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); + SDL_DisplayData *dispdata = display->driverdata; + SDL_bool is_vulkan = window->flags & SDL_WINDOW_VULKAN; /* Is this a VK window? */ + NativeDisplayType egl_display; + float ratio; -#if SDL_VIDEO_OPENGL_EGL - if (!_this->egl_data) { - if (SDL_GL_LoadLibrary(NULL) < 0) { - goto error; - } + if ( !(dispdata->gbm_init) && (!is_vulkan)) { + /* Reopen FD, create gbm dev, setup display plane, etc,. + but only when we come here for the first time, + and only if it's not a VK window. */ + KMSDRM_GBMInit(_this, dispdata); + + /* Manually load the EGL library. KMSDRM_EGL_LoadLibrary() has already + been called by SDL_CreateWindow() but we don't do anything there, + precisely to be able to load it here. + If we let SDL_CreateWindow() load the lib, it will be loaded + before we call KMSDRM_GBMInit(), causing GLES programs to fail. */ + // TODO errorcheck the return value of this. + if (!_this->egl_data) { + egl_display = (NativeDisplayType)((SDL_VideoData *)_this->driverdata)->gbm_dev; + SDL_EGL_LoadLibrary(_this, NULL, egl_display, EGL_PLATFORM_GBM_MESA); + } + + /* Can't init mouse sooner because planes are not ready. */ + // TODO Add a mouse_init bool and use it to avoid double intializations. + KMSDRM_InitMouse(_this); } -#endif /* Allocate window internal data */ windata = (SDL_WindowData *)SDL_calloc(1, sizeof(SDL_WindowData)); @@ -1711,9 +1892,6 @@ KMSDRM_CreateWindow(_THIS, SDL_Window * window) goto error; } - display = SDL_GetDisplayForWindow(window); - dispdata = display->driverdata; - if (((window->flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP) || ((window->flags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)) { @@ -1744,8 +1922,10 @@ KMSDRM_CreateWindow(_THIS, SDL_Window * window) windata->viddata = viddata; window->driverdata = windata; - if (KMSDRM_CreateSurfaces(_this, window)) { - goto error; + if (!is_vulkan) { + if (KMSDRM_CreateSurfaces(_this, window)) { + goto error; + } } /* Add window to the internal list of tracked windows. Note, while it may diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.h b/src/video/kmsdrm/SDL_kmsdrmvideo.h index 6bec4e31d..25570b035 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.h +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.h @@ -85,6 +85,8 @@ typedef struct SDL_VideoData { int devindex; /* device index that was passed on creation */ int drm_fd; /* DRM file desc */ + char devpath[32]; /* DRM dev path. */ + struct gbm_device *gbm_dev; SDL_Window **windows; @@ -117,6 +119,7 @@ typedef struct connector { typedef struct SDL_DisplayData { drmModeModeInfo mode; + drmModeModeInfo preferred_mode; uint32_t atomic_flags; plane *display_plane; @@ -141,6 +144,14 @@ typedef struct SDL_DisplayData dumb_buffer *dumb_buffer; SDL_bool modeset_pending; + SDL_bool gbm_init; + + /* DRM & GBM cursor stuff lives here, not in an SDL_Cursor's driverdata struct, + because setting/unsetting up these is done on window creation/destruction, + where we may not have an SDL_Cursor at all (so no SDL_Cursor driverdata). + There's only one cursor GBM BO because we only support one cursor. */ + struct gbm_bo *cursor_bo; + uint64_t cursor_w, cursor_h; } SDL_DisplayData; @@ -167,12 +178,9 @@ typedef struct SDL_WindowData int32_t output_h; int32_t output_x; - /* This is for deferred eglMakeCurrent() call: we can't call it until - the EGL context is available, but we need the EGL surface sooner. */ - SDL_bool egl_context_pending; - /* This dictates what approach we'll use for SwapBuffers. */ int (*swap_window)(_THIS, SDL_Window * window); + } SDL_WindowData; typedef struct SDL_DisplayModeData diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/src/video/kmsdrm/SDL_kmsdrmvulkan.c new file mode 100644 index 000000000..49d4cf717 --- /dev/null +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.c @@ -0,0 +1,395 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * @author Manuel Alfayate Corchere . + * Based on Jacob Lifshay's SDL_x11vulkan.c. + */ + +#include "../../SDL_internal.h" + +#if SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_KMSDRM + +#include "SDL_kmsdrmvideo.h" +#include "SDL_kmsdrmdyn.h" +#include "SDL_assert.h" + +#include "SDL_loadso.h" +#include "SDL_kmsdrmvulkan.h" +#include "SDL_syswm.h" +#include "sys/ioctl.h" + +#if defined(__OpenBSD__) +#define DEFAULT_VULKAN "libvulkan.so" +#else +#define DEFAULT_VULKAN "libvulkan.so.1" +#endif + +int KMSDRM_Vulkan_LoadLibrary(_THIS, const char *path) +{ + VkExtensionProperties *extensions = NULL; + Uint32 i, extensionCount = 0; + SDL_bool hasSurfaceExtension = SDL_FALSE; + SDL_bool hasDisplayExtension = SDL_FALSE; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; + + if(_this->vulkan_config.loader_handle) + return SDL_SetError("Vulkan already loaded"); + + /* Load the Vulkan library */ + if(!path) + path = SDL_getenv("SDL_VULKAN_LIBRARY"); + if(!path) + path = DEFAULT_VULKAN; + + _this->vulkan_config.loader_handle = SDL_LoadObject(path); + + if(!_this->vulkan_config.loader_handle) + return -1; + + SDL_strlcpy(_this->vulkan_config.loader_path, path, + SDL_arraysize(_this->vulkan_config.loader_path)); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( + _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); + + if(!vkGetInstanceProcAddr) + goto fail; + + _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr; + _this->vulkan_config.vkEnumerateInstanceExtensionProperties = + (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( + VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + + if(!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) + goto fail; + + extensions = SDL_Vulkan_CreateInstanceExtensionsList( + (PFN_vkEnumerateInstanceExtensionProperties) + _this->vulkan_config.vkEnumerateInstanceExtensionProperties, + &extensionCount); + + if(!extensions) + goto fail; + + for(i = 0; i < extensionCount; i++) + { + if(SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) + hasSurfaceExtension = SDL_TRUE; + else if(SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) + hasDisplayExtension = SDL_TRUE; + } + + SDL_free(extensions); + + if(!hasSurfaceExtension) + { + SDL_SetError("Installed Vulkan doesn't implement the " + VK_KHR_SURFACE_EXTENSION_NAME " extension"); + goto fail; + } + else if(!hasDisplayExtension) + { + SDL_SetError("Installed Vulkan doesn't implement the " + VK_KHR_DISPLAY_EXTENSION_NAME "extension"); + goto fail; + } + + return 0; + +fail: + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + return -1; +} + +void KMSDRM_Vulkan_UnloadLibrary(_THIS) +{ + if(_this->vulkan_config.loader_handle) + { + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + } +} + +/*********************************************************************/ +/* Here we can put whatever Vulkan extensions we want to be enabled */ +/* at instance creation, which is done in the programs, not in SDL. */ +/* So: programs call SDL_Vulkan_GetInstanceExtensions() and here */ +/* we put the extensions specific to this backend so the programs */ +/* get a list with the extension we want, so they can include that */ +/* list in the ppEnabledExtensionNames and EnabledExtensionCount */ +/* members of the VkInstanceCreateInfo struct passed to */ +/* vkCreateInstance(). */ +/*********************************************************************/ +SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(_THIS, + SDL_Window *window, + unsigned *count, + const char **names) +{ + static const char *const extensionsForKMSDRM[] = { + VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME + }; + if(!_this->vulkan_config.loader_handle) + { + SDL_SetError("Vulkan is not loaded"); + return SDL_FALSE; + } + return SDL_Vulkan_GetInstanceExtensions_Helper( + count, names, SDL_arraysize(extensionsForKMSDRM), + extensionsForKMSDRM); +} + +void KMSDRM_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h) +{ + if (w) { + *w = window->w; + } + + if (h) { + *h = window->h; + } +} + +/***********************************************************************/ +/* First thing to know is that we don't call vkCreateInstance() here. */ +/* Instead, programs using SDL and Vulkan create their Vulkan instance */ +/* and we get it here, ready to use. */ +/* Extensions specific for this platform are activated in */ +/* KMSDRM_Vulkan_GetInstanceExtensions(), like we do with */ +/* VK_KHR_DISPLAY_EXTENSION_NAME, which is what we need for x-less VK. */ +/***********************************************************************/ +SDL_bool KMSDRM_Vulkan_CreateSurface(_THIS, + SDL_Window *window, + VkInstance instance, + VkSurfaceKHR *surface) +{ + VkPhysicalDevice gpu; + uint32_t gpu_count; + uint32_t display_count; + uint32_t mode_count; + uint32_t plane_count; + + VkPhysicalDevice *physical_devices = NULL; + VkDisplayPropertiesKHR *displays_props = NULL; + VkDisplayModePropertiesKHR *modes_props = NULL; + VkDisplayPlanePropertiesKHR *planes_props = NULL; + + VkDisplayModeCreateInfoKHR display_mode_create_info; + VkDisplaySurfaceCreateInfoKHR display_plane_surface_create_info; + + VkExtent2D image_size; + VkDisplayModeKHR display_mode; + VkDisplayModePropertiesKHR display_mode_props = {0}; + + VkResult result; + SDL_bool ret = SDL_FALSE; + + /* Get the function pointers for the functions we will use. */ + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + + PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = + (PFN_vkCreateDisplayPlaneSurfaceKHR)vkGetInstanceProcAddr( + instance, "vkCreateDisplayPlaneSurfaceKHR"); + + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = + (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr( + instance, "vkEnumeratePhysicalDevices"); + + PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = + (PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetPhysicalDeviceDisplayPropertiesKHR"); + + PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = + (PFN_vkGetDisplayModePropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayModePropertiesKHR"); + + PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = + (PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"); + + /*PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = + (PFN_vkGetDisplayPlaneSupportedDisplaysKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayPlaneSupportedDisplaysKHR"); + + PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = + (PFN_vkGetDisplayPlaneCapabilitiesKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayPlaneCapabilitiesKHR"); + */ + + PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = + (PFN_vkCreateDisplayModeKHR)vkGetInstanceProcAddr( + instance, "vkCreateDisplayModeKHR"); + + if(!_this->vulkan_config.loader_handle) + { + SDL_SetError("Vulkan is not loaded"); + goto clean; + } + + /*************************************/ + /* Block for vulkan surface creation */ + /*************************************/ + + /****************************************************************/ + /* If we got vkCreateDisplayPlaneSurfaceKHR() pointer, it means */ + /* that the VK_KHR_Display extension is active on the instance. */ + /* That's the central extension we need for x-less VK! */ + /****************************************************************/ + if(!vkCreateDisplayPlaneSurfaceKHR) + { + SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME + " extension is not enabled in the Vulkan instance."); + goto clean; + } + + /* Get the physical device count. */ + vkEnumeratePhysicalDevices(instance, &gpu_count, NULL); + + if (gpu_count == 0) { + SDL_SetError("Vulkan can't find physical devices (gpus)."); + goto clean; + } + + /* Get the physical devices. */ + physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count); + vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices); + + /* For now, just grab the first physical device (gpu = physical_device in vkcube example).*/ + /* TODO What happens on video systems with multiple display ports like the Rpi4 ? */ + gpu = physical_devices[0]; + + /* Get the display count of the phsysical device. */ + vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL); + if (display_count == 0) { + SDL_SetError("Vulkan can't find any displays."); + goto clean; + } + + /* Get the props of the displays of the physical device. */ + displays_props = (VkDisplayPropertiesKHR *) malloc(display_count * sizeof(*displays_props)); + vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, + &display_count, + displays_props); + + /* Get the videomode count for the first display. */ + /* TODO What happens on video systems with multiple display ports like the Rpi4 ? */ + vkGetDisplayModePropertiesKHR(gpu, + displays_props[0].display, + &mode_count, NULL); + + if (mode_count == 0) { + SDL_SetError("Vulkan can't find any video modes for display %i (%s)\n", 0, + displays_props[0].displayName); + goto clean; + } + + /* Get the props of the videomodes for the first display. */ + modes_props = (VkDisplayModePropertiesKHR *) malloc(mode_count * sizeof(*modes_props)); + vkGetDisplayModePropertiesKHR(gpu, + displays_props[0].display, + &mode_count, modes_props); + + /* Get the planes count of the physical device. */ + /* TODO: find out if we need other planes. */ + vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, NULL); + if (plane_count == 0) { + SDL_SetError("Vulkan can't find any planes."); + goto clean; + } + + /* Get the props of the planes for the physical device. */ + planes_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); + vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, planes_props); + + /* Get a video mode matching the window size. + REMEMBER: We have to get a small enough videomode for the window size, + because videomode determines how big the scanout region is and we can't + scanout a region bigger than the window (we would be reading past the + buffer, and Vulkan would give us a confusing VK_ERROR_SURFACE_LOST_KHR). */ + for (int i = 0; i < mode_count; i++) { + if (modes_props[i].parameters.visibleRegion.width <= window->w && + modes_props[i].parameters.visibleRegion.height <= window->h) + { + display_mode_props = modes_props[i]; + break; + } + } + + if (display_mode_props.parameters.visibleRegion.width == 0 + || display_mode_props.parameters.visibleRegion.height == 0) + { + SDL_SetError("Vulkan can't find a proper display mode for the window size."); + goto clean; + } + + /* We have the props of the display mode, but we need an actual display mode. */ + display_mode_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR; + display_mode_create_info.parameters = display_mode_props.parameters; + result = vkCreateDisplayModeKHR(gpu, + displays_props[0].display, + &display_mode_create_info, + NULL, &display_mode); + if (result != VK_SUCCESS) { + SDL_SetError("Vulkan can't create the display mode."); + goto clean; + } + + /* Let's finally create the Vulkan surface! */ + + image_size.width = window->w; + image_size.height = window->h; + + display_plane_surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; + display_plane_surface_create_info.displayMode = display_mode; + display_plane_surface_create_info.planeIndex = 0; /* For now, simply use the first plane. */ + display_plane_surface_create_info.imageExtent = image_size; + + result = vkCreateDisplayPlaneSurfaceKHR(instance, + &display_plane_surface_create_info, + NULL, + surface); + + if(result != VK_SUCCESS) + { + SDL_SetError("vkCreateKMSDRMSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); + goto clean; + } + + ret = SDL_TRUE; + +clean: + if (physical_devices) + free (physical_devices); + if (displays_props) + free (displays_props); + if (planes_props) + free (planes_props); + if (modes_props) + free (modes_props); + + return ret; +} + +#endif + +/* vim: set ts=4 sw=4 expandtab: */ diff --git a/src/video/kmsdrm/SDL_kmsdrmvulkan.h b/src/video/kmsdrm/SDL_kmsdrmvulkan.h new file mode 100644 index 000000000..2ee04bb9e --- /dev/null +++ b/src/video/kmsdrm/SDL_kmsdrmvulkan.h @@ -0,0 +1,53 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * @author Manuel Alfayate Corchere . + * Based on Jacob Lifshay's SDL_x11vulkan.c. + */ + +#include "../../SDL_internal.h" + +#ifndef SDL_kmsdrmvulkan_h_ +#define SDL_kmsdrmvulkan_h_ + +#include "../SDL_vulkan_internal.h" +#include "../SDL_sysvideo.h" + +#if SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_KMSDRM + +int KMSDRM_Vulkan_LoadLibrary(_THIS, const char *path); +void KMSDRM_Vulkan_UnloadLibrary(_THIS); +SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(_THIS, + SDL_Window *window, + unsigned *count, + const char **names); +void KMSDRM_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h); +SDL_bool KMSDRM_Vulkan_CreateSurface(_THIS, + SDL_Window *window, + VkInstance instance, + VkSurfaceKHR *surface); + +#endif + +#endif /* SDL_kmsdrmvulkan_h_ */ + +/* vi: set ts=4 sw=4 expandtab: */