瀏覽代碼

kmsdrm: Restore atomic support.

Ryan C. Gordon 11 月之前
父節點
當前提交
1fe926769c

+ 22 - 0
include/SDL3/SDL_hints.h

@@ -2473,6 +2473,28 @@ extern "C" {
  */
 #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER"
 
+/**
+ * A variable that controls whether KMSDRM will use "atomic" functionality.
+ *
+ * The KMSDRM backend can use atomic commits, if both DRM_CLIENT_CAP_ATOMIC
+ * and DRM_CLIENT_CAP_UNIVERSAL_PLANES is supported by the system. As of
+ * SDL 3.4.0, it will favor this functionality, but in case this doesn't
+ * work well on a given system or other surprises, this hint can be used
+ * to disable it.
+ *
+ * This hint can not enable the functionality if it isn't available.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": SDL will not use the KMSDRM "atomic" functionality.
+ * - "1": SDL will allow usage of the KMSDRM "atomic" functionality. (default)
+ *
+ * This hint should be set before SDL is initialized.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_KMSDRM_ATOMIC "SDL_KMSDRM_ATOMIC"
+
 /**
  * A variable controlling the default SDL log levels.
  *

+ 112 - 25
src/video/kmsdrm/SDL_kmsdrmmouse.c

@@ -67,6 +67,23 @@ void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
 
     // Destroy the curso GBM BO.
     if (dispdata->cursor_bo) {
+        SDL_VideoData *viddata = (SDL_VideoData *) _this->internal;
+        if (viddata->is_atomic) {
+            if (dispdata->cursor_plane) {
+                // Unset the the cursor BO from the cursor plane.
+                KMSDRM_PlaneInfo info;
+                SDL_zero(info);
+                info.plane = dispdata->cursor_plane;
+                drm_atomic_set_plane_props(dispdata, &info);
+                // Wait until the cursor is unset from the cursor plane before destroying it's BO.
+                if (drm_atomic_commit(_this, dispdata, true, false)) {
+                    SDL_SetError("Failed atomic commit in KMSDRM_DenitMouse.");
+                }
+                // Free the cursor plane, on which the cursor was being shown.
+                free_plane(&dispdata->cursor_plane);
+            }
+        }
+
         KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
         dispdata->cursor_bo = NULL;
         dispdata->cursor_bo_drm_fd = -1;
@@ -78,11 +95,14 @@ void KMSDRM_DestroyCursorBO(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
    build a window and assign a display to it. */
 bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
 {
-
     SDL_VideoDevice *dev = SDL_GetVideoDevice();
     SDL_VideoData *viddata = dev->internal;
     SDL_DisplayData *dispdata = display->internal;
 
+    if (viddata->is_atomic) {
+        setup_plane(dev, dispdata, &dispdata->cursor_plane, DRM_PLANE_TYPE_CURSOR);
+    }
+
     if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
                                                GBM_FORMAT_ARGB8888,
                                                GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
@@ -121,15 +141,29 @@ static bool KMSDRM_RemoveCursorFromBO(SDL_VideoDisplay *display)
     SDL_VideoDevice *video_device = SDL_GetVideoDevice();
     SDL_VideoData *viddata = video_device->internal;
 
-    const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id, 0, 0, 0);
-    if (rc < 0) {
-        result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc));
+    if (viddata->is_atomic) {
+        if (dispdata->cursor_plane) {
+            KMSDRM_PlaneInfo info;
+            SDL_zero(info);
+            info.plane = dispdata->cursor_plane;
+            // The rest of the members are zeroed, so this takes away the cursor from the cursor plane.
+            drm_atomic_set_plane_props(dispdata, &info);
+            if (drm_atomic_commit(video_device, dispdata, true, false)) {
+                result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
+            }
+        }
+    } else {
+        const int rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, 0, 0, 0);
+        if (rc < 0) {
+            result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc));
+        }
     }
+
     return result;
 }
 
 // Dump a cursor buffer to a display's DRM cursor BO.
-static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor)
+static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Mouse *mouse, SDL_Cursor *cursor)
 {
     SDL_DisplayData *dispdata = display->internal;
     SDL_CursorData *curdata = cursor->internal;
@@ -173,22 +207,42 @@ static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor)
         goto cleanup;
     }
 
-    // Put the GBM BO buffer on screen using the DRM interface.
-    bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
-    if (curdata->hot_x == 0 && curdata->hot_y == 0) {
-        rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id,
-                                      bo_handle, dispdata->cursor_w, dispdata->cursor_h);
+    if (viddata->is_atomic) {
+        // Get the fb_id for the GBM BO so we can show it on the cursor plane.
+        KMSDRM_FBInfo *fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo);
+        KMSDRM_PlaneInfo info;
+
+        // Show the GBM BO buffer on the cursor plane.
+        SDL_zero(info);
+        info.plane = dispdata->cursor_plane;
+        info.crtc_id = dispdata->crtc.crtc->crtc_id;
+        info.fb_id = fb->fb_id;
+        info.src_w = dispdata->cursor_w;
+        info.src_h = dispdata->cursor_h;
+        info.crtc_x = ((int32_t) SDL_roundf(mouse->x)) - curdata->hot_x;
+        info.crtc_y = ((int32_t) SDL_roundf(mouse->y)) - curdata->hot_y;
+        info.crtc_w = curdata->w;
+        info.crtc_h = curdata->h;
+        drm_atomic_set_plane_props(dispdata, &info);
+        if (drm_atomic_commit(video_device, dispdata, true, false)) {
+            result = SDL_SetError("Failed atomic commit in KMSDRM_ShowCursor.");
+            goto cleanup;
+        }
     } else {
-        rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc->crtc_id,
-                                       bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y);
-    }
-    if (rc < 0) {
-        result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc));
-        goto cleanup;
+        // Put the GBM BO buffer on screen using the DRM interface.
+        bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
+        if (curdata->hot_x == 0 && curdata->hot_y == 0) {
+            rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h);
+        } else {
+            rc = KMSDRM_drmModeSetCursor2(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y);
+        }
+        if (rc < 0) {
+            result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc));
+            goto cleanup;
+        }
     }
 
 cleanup:
-
     if (ready_buffer) {
         SDL_free(ready_buffer);
     }
@@ -316,7 +370,7 @@ static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
             if (cursor) {
                 /* Dump the cursor to the display DRM cursor BO so it becomes visible
                    on that display. */
-                result = KMSDRM_DumpCursorToBO(display, cursor);
+                result = KMSDRM_DumpCursorToBO(display, mouse, cursor);
             } else {
                 // Hide the cursor on that display.
                 result = KMSDRM_RemoveCursorFromBO(display);
@@ -327,6 +381,18 @@ static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
     return result;
 }
 
+static void drm_atomic_movecursor(SDL_DisplayData *dispdata, const SDL_CursorData *curdata, uint16_t x, uint16_t y)
+{
+    if (dispdata->cursor_plane) {  // We can't move a non-existing cursor, but that's ok.
+        // Do we have a set of changes already in the making? If not, allocate a new one.
+        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);
+    }
+}
+
 static bool KMSDRM_WarpMouseGlobal(float x, float y)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
@@ -340,17 +406,25 @@ static bool KMSDRM_WarpMouseGlobal(float x, float y)
 
         // And now update the cursor graphic position on screen.
         if (dispdata->cursor_bo) {
-            const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)x, (int)y);
-            if (rc < 0) {
-                return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
+            SDL_VideoDevice *dev = SDL_GetVideoDevice();
+            SDL_VideoData *viddata = dev->internal;
+            if (viddata->is_atomic) {
+                const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal;
+                drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) x, (uint16_t) (int) y);
+            } else {
+                const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)x, (int)y);
+                if (rc < 0) {
+                    return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
+                }
             }
-            return true;
         } else {
             return SDL_SetError("Cursor not initialized properly.");
         }
     } else {
         return SDL_SetError("No mouse or current cursor.");
     }
+
+    return true;
 }
 
 static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y)
@@ -394,14 +468,27 @@ static bool KMSDRM_MoveCursor(SDL_Cursor *cursor)
     if (mouse && mouse->cur_cursor && mouse->focus) {
         SDL_Window *window = mouse->focus;
         SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
+        SDL_VideoDevice *dev = SDL_GetVideoDevice();
+        SDL_VideoData *viddata = dev->internal;
 
         if (!dispdata->cursor_bo) {
             return SDL_SetError("Cursor not initialized properly.");
         }
 
-        const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc->crtc_id, (int)mouse->x, (int)mouse->y);
-        if (rc < 0) {
-            return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
+        if (viddata->is_atomic) {
+            /* !!! FIXME: 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() 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. */
+            const SDL_CursorData *curdata = (const SDL_CursorData *) mouse->cur_cursor->internal;
+            drm_atomic_movecursor(dispdata, curdata, (uint16_t) (int) mouse->x, (uint16_t) (int) mouse->y);
+        } else {
+            const int rc = KMSDRM_drmModeMoveCursor(dispdata->cursor_bo_drm_fd, dispdata->crtc.crtc->crtc_id, (int)mouse->x, (int)mouse->y);
+            if (rc < 0) {
+                return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc));
+            }
         }
     }
     return true;

+ 324 - 6
src/video/kmsdrm/SDL_kmsdrmopengles.c

@@ -28,10 +28,25 @@
 #include "SDL_kmsdrmdyn.h"
 #include <errno.h>
 
+#define VOID2U64(x) ((uint64_t)(size_t)(x))
+
 #ifndef EGL_PLATFORM_GBM_MESA
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #endif
 
+#ifndef EGL_SYNC_NATIVE_FENCE_ANDROID
+#define EGL_SYNC_NATIVE_FENCE_ANDROID     0x3144
+#endif
+
+#ifndef EGL_SYNC_NATIVE_FENCE_FD_ANDROID
+#define EGL_SYNC_NATIVE_FENCE_FD_ANDROID  0x3145
+#endif
+
+#ifndef EGL_NO_NATIVE_FENCE_FD_ANDROID
+#define EGL_NO_NATIVE_FENCE_FD_ANDROID    -1
+#endif
+
+
 // EGL implementation of SDL OpenGL support
 
 void KMSDRM_GLES_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor)
@@ -83,7 +98,291 @@ bool KMSDRM_GLES_SetSwapInterval(SDL_VideoDevice *_this, int interval)
     return true;
 }
 
-bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
+static EGLSyncKHR create_fence(SDL_VideoDevice *_this, int fd)
+{
+    EGLint attrib_list[] = {
+        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);
+
+    SDL_assert(fence);
+    return fence;
+}
+
+/***********************************************************************************/
+/* Comments about buffer access protection mechanism (=fences) are the ones boxed. */
+/* Also, DON'T remove the asserts: if a fence-related call fails, it's better that */
+/* program exits immediately, or we could leave KMS waiting for a failed/missing   */
+/* fence forever.                                                                  */
+/***********************************************************************************/
+static bool KMSDRM_GLES_SwapWindowFenced(SDL_VideoDevice *_this, SDL_Window * window)
+{
+    SDL_WindowData *windata = ((SDL_WindowData *) window->internal);
+    SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
+    KMSDRM_FBInfo *fb;
+    KMSDRM_PlaneInfo info;
+    bool modesetting = false;
+
+    SDL_zero(info);
+
+    /******************************************************************/
+    /* Create the GPU-side FENCE OBJECT. It will be inserted into the */
+    /* GL CMDSTREAM exactly at the end of the gl commands that form a */
+    /* frame.(KMS will have to wait on it before doing a pageflip.)   */
+    /******************************************************************/
+    dispdata->gpu_fence = create_fence(_this, EGL_NO_NATIVE_FENCE_FD_ANDROID);
+    SDL_assert(dispdata->gpu_fence);
+
+    /******************************************************************/
+    /* eglSwapBuffers flushes the fence down the GL CMDSTREAM, so we  */
+    /* know for sure it's there now.                                  */
+    /* Also it marks, at EGL level, the buffer that we want to become */
+    /* the new front buffer. (Remember that won't really happen until */
+    /* we request a pageflip at the KMS level and it completes.       */
+    /******************************************************************/
+    if (! _this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface)) {
+        return SDL_EGL_SetError("Failed to swap EGL buffers", "eglSwapBuffers");
+    }
+
+    /******************************************************************/
+    /* EXPORT the GPU-side FENCE OBJECT to the fence INPUT FD, so we  */
+    /* can pass it into the kernel. Atomic ioctl will pass the        */
+    /* in-fence fd into the kernel, thus telling KMS that it has to   */
+    /* wait for GPU to finish rendering the frame (remember where we  */
+    /* put the fence in the GL CMDSTREAM) before doing the changes    */
+    /* requested in the atomic ioct (the pageflip in this case).      */
+    /* (We export the GPU-side FENCE OJECT to the fence INPUT FD now, */
+    /* not sooner, because now we are sure that the GPU-side fence is */
+    /* in the CMDSTREAM to be lifted when the CMDSTREAM to this point */
+    /* is completed).                                                 */
+    /******************************************************************/
+    dispdata->kms_in_fence_fd = _this->egl_data->eglDupNativeFenceFDANDROID (_this->egl_data->egl_display, dispdata->gpu_fence);
+
+    _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, dispdata->gpu_fence);
+    SDL_assert(dispdata->kms_in_fence_fd != -1);
+
+    /* Lock the buffer that is marked by eglSwapBuffers() to become the
+       next front buffer (so it can not be chosen by EGL as back buffer
+       to draw on), and get a handle to it to request the pageflip on it.
+       REMEMBER that gbm_surface_lock_front_buffer() ALWAYS has to be
+       called after eglSwapBuffers(). */
+    windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
+    if (!windata->next_bo) {
+        return SDL_SetError("Failed to lock frontbuffer");
+    }
+    fb = KMSDRM_FBFromBO(_this, windata->next_bo);
+    if (!fb) {
+        return SDL_SetError("Failed to get a new framebuffer from BO");
+    }
+
+    if (!windata->bo) {
+        /* On the first swap, immediately present the new front buffer. Before
+           drmModePageFlip can be used the CRTC has to be configured to use
+           the current connector and mode with drmModeSetCrtc */
+        SDL_VideoData *viddata = _this->internal;
+        const int ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd,
+                                    dispdata->crtc.crtc->crtc_id, fb->fb_id, 0, 0,
+                                    &dispdata->connector.connector->connector_id, 1, &dispdata->mode);
+
+        if (ret) {
+            return SDL_SetError("Could not set videomode on CRTC.");
+        }
+    }
+
+    /* Add the pageflip to the request list. */
+    info.plane = dispdata->display_plane;
+    info.crtc_id = dispdata->crtc.crtc->crtc_id;
+    info.fb_id = fb->fb_id;
+    info.src_w = window->w;  // !!! FIXME: was windata->src_w in the original atomic patch
+    info.src_h = window->h;  // !!! FIXME: was windata->src_h in the original atomic patch
+    info.crtc_w = dispdata->mode.hdisplay;  // !!! FIXME: was windata->output_w in the original atomic patch
+    info.crtc_h = dispdata->mode.vdisplay;  // !!! FIXME: was windata->output_h in the original atomic patch
+    info.crtc_x = 0;  // !!! FIXME: was windata->output_x in the original atomic patch
+
+    drm_atomic_set_plane_props(dispdata, &info);
+
+    /*****************************************************************/
+    /* Tell the display (KMS) that it will have to wait on the fence */
+    /* for the GPU-side FENCE.                                       */
+    /*                                                               */
+    /* Since KMS is a kernel thing, we have to pass an FD into       */
+    /* the kernel, and get another FD out of the kernel.             */
+    /*                                                               */
+    /* 1) To pass the GPU-side fence into the kernel, we set the     */
+    /* INPUT FD as the IN_FENCE_FD prop of the PRIMARY PLANE.        */
+    /* This FD tells KMS (the kernel) to wait for the GPU-side fence.*/
+    /*                                                               */
+    /* 2) To get the KMS-side fence out of the kernel, we set the    */
+    /* OUTPUT FD as the OUT_FEWNCE_FD prop of the CRTC.              */
+    /* This FD will be later imported as a FENCE OBJECT which will be*/
+    /* used to tell the GPU to wait for KMS to complete the changes  */
+    /* requested in atomic_commit (the pageflip in this case).       */
+    /*****************************************************************/
+    if (dispdata->kms_in_fence_fd != -1)
+    {
+        add_plane_property(dispdata->atomic_req, dispdata->display_plane,
+            "IN_FENCE_FD", dispdata->kms_in_fence_fd);
+        add_crtc_property(dispdata->atomic_req, &dispdata->crtc,
+            "OUT_FENCE_PTR", VOID2U64(&dispdata->kms_out_fence_fd));
+    }
+ 
+    /* Do we have a pending modesetting? If so, set the necessary
+       props so it's included in the incoming atomic commit. */
+    if (windata->egl_surface_dirty) {
+        // !!! FIXME: this CreateSurfaces call is what the legacy path does; it's not clear to me if the atomic paths need to do it too.
+        KMSDRM_CreateSurfaces(_this, window);
+
+        uint32_t blob_id;
+        SDL_VideoData *viddata = (SDL_VideoData *)_this->internal;
+
+        add_connector_property(dispdata->atomic_req, &dispdata->connector, "CRTC_ID", dispdata->crtc.crtc->crtc_id);
+        KMSDRM_drmModeCreatePropertyBlob(viddata->drm_fd, &dispdata->mode, sizeof(dispdata->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);
+        modesetting = true;
+    }
+
+    /*****************************************************************/
+    /* Issue a non-blocking atomic commit: for triple buffering,     */
+    /* this must not block so the game can start building another    */
+    /* frame, even if the just-requested pageflip hasnt't completed. */
+    /*****************************************************************/
+    if (drm_atomic_commit(_this, dispdata, false, modesetting)) {
+        return SDL_SetError("Failed to issue atomic commit on pageflip");
+    }
+
+    /* Release the previous front buffer so EGL can chose it as back buffer
+       and render on it again. */
+    if (windata->bo) {
+        KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
+    }
+    /* Take note of the buffer about to become front buffer, so next
+       time we come here we can free it like we just did with the previous
+       front buffer. */
+    windata->bo = windata->next_bo;
+
+    /****************************************************************/
+    /* Import the KMS-side FENCE OUTPUT FD from the kernel to the   */
+    /* KMS-side FENCE OBJECT so we can use use it to fence the GPU. */
+    /****************************************************************/
+    dispdata->kms_fence = create_fence(_this, dispdata->kms_out_fence_fd);
+    SDL_assert(dispdata->kms_fence);
+
+    /****************************************************************/
+    /* "Delete" the fence OUTPUT FD, because we already have the    */
+    /* KMS FENCE OBJECT, the fence itself is away from us, on the   */
+    /* kernel side.                                                 */
+    /****************************************************************/
+    dispdata->kms_out_fence_fd = -1;
+
+    /*****************************************************************/
+    /* Tell the GPU to wait on the fence for the KMS-side FENCE,     */
+    /* which means waiting until the requested pageflip is completed.*/
+    /*****************************************************************/
+    _this->egl_data->eglWaitSyncKHR(_this->egl_data->egl_display, dispdata->kms_fence, 0);
+
+    return true;
+}
+
+static bool KMSDRM_GLES_SwapWindowDoubleBuffered(SDL_VideoDevice *_this, SDL_Window * window)
+{
+    SDL_WindowData *windata = ((SDL_WindowData *) window->internal);
+    SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
+    KMSDRM_FBInfo *fb;
+    KMSDRM_PlaneInfo info;
+    bool modesetting = false;
+
+    SDL_zero(info);
+
+    /**********************************************************************************/
+    /* In double-buffer mode, atomic_commit will always be synchronous/blocking (ie:  */
+    /* won't return until the requested changes are really done).                     */
+    /* Also, there's no need to fence KMS or the GPU, because we won't be entering    */
+    /* game loop again (hence not building or executing a new cmdstring) until        */
+    /* pageflip is done, so we don't need to protect the KMS/GPU access to the buffer.*/
+    /**********************************************************************************/
+
+    /* Mark, at EGL level, the buffer that we want to become the new front buffer.
+       It won't really happen until we request a pageflip at the KMS level and it
+       completes. */
+    if (! _this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface)) {
+        return SDL_EGL_SetError("Failed to swap EGL buffers", "eglSwapBuffers");
+    }
+    /* Lock the buffer that is marked by eglSwapBuffers() to become the next front buffer
+       (so it can not be chosen by EGL as back buffer to draw on), and get a handle to it,
+       to request the pageflip on it. */
+    windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
+    if (!windata->next_bo) {
+        return SDL_SetError("Failed to lock frontbuffer");
+     }
+    fb = KMSDRM_FBFromBO(_this, windata->next_bo);
+    if (!fb) {
+        return SDL_SetError("Failed to get a new framebuffer BO");
+    }
+
+    if (!windata->bo) {
+        /* On the first swap, immediately present the new front buffer. Before
+           drmModePageFlip can be used the CRTC has to be configured to use
+           the current connector and mode with drmModeSetCrtc */
+        SDL_VideoData *viddata = _this->internal;
+        const int ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd,
+                                    dispdata->crtc.crtc->crtc_id, fb->fb_id, 0, 0,
+                                    &dispdata->connector.connector->connector_id, 1, &dispdata->mode);
+
+        if (ret) {
+            return SDL_SetError("Could not set videomode on CRTC.");
+        }
+    }
+
+    /* Add the pageflip to the request list. */
+    info.plane = dispdata->display_plane;
+    info.crtc_id = dispdata->crtc.crtc->crtc_id;
+    info.fb_id = fb->fb_id;
+    info.src_w = window->w;  // !!! FIXME: was windata->src_w in the original atomic patch
+    info.src_h = window->h;  // !!! FIXME: was windata->src_h in the original atomic patch
+    info.crtc_w = dispdata->mode.hdisplay;  // !!! FIXME: was windata->output_w in the original atomic patch
+    info.crtc_h = dispdata->mode.vdisplay;  // !!! FIXME: was windata->output_h in the original atomic patch
+    info.crtc_x = 0;  // !!! FIXME: was windata->output_x in the original atomic patch
+
+    drm_atomic_set_plane_props(dispdata, &info);
+
+    /* Do we have a pending modesetting? If so, set the necessary
+       props so it's included in the incoming atomic commit. */
+    if (windata->egl_surface_dirty) {
+        // !!! FIXME: this CreateSurfaces call is what the legacy path does; it's not clear to me if the atomic paths need to do it too.
+        KMSDRM_CreateSurfaces(_this, window);
+
+        uint32_t blob_id;
+
+        SDL_VideoData *viddata = (SDL_VideoData *)_this->internal;
+
+        add_connector_property(dispdata->atomic_req, &dispdata->connector, "CRTC_ID", dispdata->crtc.crtc->crtc_id);
+        KMSDRM_drmModeCreatePropertyBlob(viddata->drm_fd, &dispdata->mode, sizeof(dispdata->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);
+        modesetting = true;
+    }
+ 
+    /* Issue the one and only atomic commit where all changes will be requested!
+       Blocking for double buffering: won't return until completed. */
+    if (drm_atomic_commit(_this, dispdata, true, modesetting)) {
+        return SDL_SetError("Failed to issue atomic commit on pageflip");
+    }
+
+    /* Release last front buffer so EGL can chose it as back buffer and render on it again. */
+    if (windata->bo) {
+        KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
+    }
+
+    /* Take note of current front buffer, so we can free it next time we come here. */
+    windata->bo = windata->next_bo;
+
+    return true;
+}
+
+static bool KMSDRM_GLES_SwapWindowLegacy(SDL_VideoDevice *_this, SDL_Window *window)
 {
     SDL_WindowData *windata = window->internal;
     SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
@@ -116,13 +415,12 @@ bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
     // Release the previous front buffer
     if (windata->bo) {
         KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
-        windata->bo = NULL;
     }
 
     windata->bo = windata->next_bo;
 
     /* Mark a buffer to become the next front buffer.
-       This won't happen until pagelip completes. */
+       This won't happen until pageflip completes. */
     if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display,
                                           windata->egl_surface))) {
         return SDL_SetError("eglSwapBuffers failed");
@@ -147,8 +445,8 @@ bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
            drmModePageFlip can be used the CRTC has to be configured to use
            the current connector and mode with drmModeSetCrtc */
         ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd,
-                                    dispdata->crtc->crtc_id, fb_info->fb_id, 0, 0,
-                                    &dispdata->connector->connector_id, 1, &dispdata->mode);
+                                    dispdata->crtc.crtc->crtc_id, fb_info->fb_id, 0, 0,
+                                    &dispdata->connector.connector->connector_id, 1, &dispdata->mode);
 
         if (ret) {
             return SDL_SetError("Could not set videomode on CRTC.");
@@ -170,7 +468,7 @@ bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
             flip_flags |= DRM_MODE_PAGE_FLIP_ASYNC;
         }
 
-        ret = KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc->crtc_id,
+        ret = KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc.crtc->crtc_id,
                                      fb_info->fb_id, flip_flags, &windata->waiting_for_flip);
 
         if (ret == 0) {
@@ -198,6 +496,26 @@ bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
     return true;
 }
 
+bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window * window)
+{
+    SDL_WindowData *windata = (SDL_WindowData *) window->internal;
+
+    if (windata->swap_window == NULL) {
+        SDL_VideoData *viddata = _this->internal;
+        if (viddata->is_atomic) {
+            // We want the fenced version by default, but it needs extensions.
+            if ( (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, false)) || (!SDL_EGL_HasExtension(_this, SDL_EGL_DISPLAY_EXTENSION, "EGL_ANDROID_native_fence_sync")) ) {
+                windata->swap_window = KMSDRM_GLES_SwapWindowDoubleBuffered;
+            } else {
+                windata->swap_window = KMSDRM_GLES_SwapWindowFenced;
+            }
+        } else {
+            windata->swap_window = KMSDRM_GLES_SwapWindowLegacy;
+        }
+    }
+    return windata->swap_window(_this, window);
+}
+
 SDL_EGL_MakeCurrent_impl(KMSDRM)
 
 #endif // SDL_VIDEO_DRIVER_KMSDRM

+ 10 - 0
src/video/kmsdrm/SDL_kmsdrmsym.h

@@ -137,6 +137,16 @@ SDL_KMSDRM_SYM_OPT(uint32_t,gbm_bo_get_offset,(struct gbm_bo *bo, int plane))
 SDL_KMSDRM_SYM_OPT(uint32_t,gbm_bo_get_stride_for_plane,(struct gbm_bo *bo, int plane))
 SDL_KMSDRM_SYM_OPT(union gbm_bo_handle,gbm_bo_get_handle_for_plane,(struct gbm_bo *bo, int plane))
 
+
+SDL_KMSDRM_MODULE(ATOMIC)
+SDL_KMSDRM_SYM(int,drmIoctl,(int fd, unsigned long request, void *arg))
+SDL_KMSDRM_SYM(drmModeAtomicReqPtr,drmModeAtomicAlloc,(void))
+SDL_KMSDRM_SYM(void,drmModeAtomicFree,(drmModeAtomicReqPtr req))
+SDL_KMSDRM_SYM(int,drmModeAtomicCommit,(int fd,drmModeAtomicReqPtr req,uint32_t flags,void *user_data))
+SDL_KMSDRM_SYM(int,drmModeAtomicAddProperty,(drmModeAtomicReqPtr req,uint32_t object_id,uint32_t property_id,uint64_t value))
+SDL_KMSDRM_SYM(int,drmModeCreatePropertyBlob,(int fd,const void *data,size_t size,uint32_t *id))
+
+
 #undef SDL_KMSDRM_MODULE
 #undef SDL_KMSDRM_SYM
 #undef SDL_KMSDRM_SYM_CONST

+ 570 - 45
src/video/kmsdrm/SDL_kmsdrmvideo.c

@@ -66,6 +66,14 @@ static char kmsdrm_dri_devname[8];
 static int kmsdrm_dri_devnamesize = 0;
 static char kmsdrm_dri_cardpath[32];
 
+/* for older KMSDRM headers... */
+#ifndef DRM_FORMAT_MOD_VENDOR_NONE
+#define DRM_FORMAT_MOD_VENDOR_NONE 0
+#endif
+#ifndef DRM_FORMAT_MOD_LINEAR
+#define DRM_FORMAT_MOD_LINEAR fourcc_mod_code(NONE, 0)
+#endif
+
 #ifndef EGL_PLATFORM_GBM_MESA
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #endif
@@ -191,6 +199,391 @@ static void CalculateRefreshRate(drmModeModeInfo *mode, int *numerator, int *den
     }
 }
 
+
+int add_connector_property(drmModeAtomicReq *req, KMSDRM_connector *conn, const char *name, uint64_t value)
+{
+    unsigned int i;
+    int prop_id = 0;
+
+    for (i = 0 ; i < conn->props->count_props ; i++) {
+        if (SDL_strcmp(conn->props_info[i]->name, name) == 0) {
+            prop_id = conn->props_info[i]->prop_id;
+            break;
+        }
+    }
+
+    if (prop_id < 0) {
+        SDL_SetError("no connector property: %s", name);
+        return -EINVAL;
+    }
+
+    return KMSDRM_drmModeAtomicAddProperty(req, conn->connector->connector_id, prop_id, value);
+}
+
+int add_crtc_property(drmModeAtomicReq *req, KMSDRM_crtc *c, const char *name, uint64_t value)
+{
+    unsigned int i;
+    int prop_id = -1;
+
+    for (i = 0 ; i < c->props->count_props ; i++) {
+        if (SDL_strcmp(c->props_info[i]->name, name) == 0) {
+            prop_id = c->props_info[i]->prop_id;
+            break;
+        }
+    }
+
+    if (prop_id < 0) {
+        SDL_SetError("no crtc property: %s", name);
+        return -EINVAL;
+    }
+
+    return KMSDRM_drmModeAtomicAddProperty(req, c->crtc->crtc_id, prop_id, value);
+}
+
+int add_plane_property(drmModeAtomicReq *req, KMSDRM_plane *p, const char *name, uint64_t value)
+{
+    unsigned int i;
+    int prop_id = -1;
+
+    for (i = 0 ; i < p->props->count_props ; i++) {
+        if (SDL_strcmp(p->props_info[i]->name, name) == 0) {
+            prop_id = p->props_info[i]->prop_id;
+            break;
+        }
+    }
+
+    if (prop_id < 0) {
+        SDL_SetError("no plane property: %s", name);
+        return -EINVAL;
+    }
+
+    return KMSDRM_drmModeAtomicAddProperty(req, p->plane->plane_id, prop_id, value);
+}
+
+#ifdef DEBUG_KMSDRM
+static void print_plane_info(SDL_VideoDevice *_this, drmModePlanePtr plane)
+{
+    SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
+    drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
+        plane->plane_id, DRM_MODE_OBJECT_PLANE);
+
+    /* Search the plane props for the plane type. */
+    uint32_t type = 0;
+    for (int i = 0; i < props->count_props; i++) {
+        drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[i]);
+        if ((SDL_strcmp(p->name, "type") == 0)) {
+            type = props->prop_values[i];
+        }
+
+        KMSDRM_drmModeFreeProperty(p);
+    }
+
+    char *plane_type = "unknown";
+    switch (type) {
+        case DRM_PLANE_TYPE_OVERLAY:
+            plane_type = "overlay";
+            break;
+
+        case DRM_PLANE_TYPE_PRIMARY:
+            plane_type = "primary";
+            break;
+
+        case DRM_PLANE_TYPE_CURSOR:
+            plane_type = "cursor";
+            break;
+    }
+
+
+    /* Remember that to present a plane on screen, it has to be
+       connected to a CRTC so the CRTC scans it,
+       scales it, etc... and presents it on screen. */
+
+    /* Now we look for the CRTCs supported by the plane. */
+    drmModeRes *resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
+    if (resources) {
+        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 (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);
+                break;
+            }
+        }
+        printf ("\n\n");
+    }
+}
+
+static void get_planes_info(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
+{
+    SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
+
+    drmModePlaneResPtr plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
+    if (!plane_resources) {
+        printf("drmModeGetPlaneResources failed: %s\n", strerror(errno));
+        return;
+    }
+
+    printf("--Number of planes found: %d-- \n", plane_resources->count_planes);
+    printf("--Usable CRTC that we have chosen: %d-- \n", dispdata->crtc.crtc->crtc_id);
+
+    /* Iterate on all the available planes. */
+    for (uint32_t i = 0; (i < plane_resources->count_planes); i++) {
+        const uint32_t plane_id = plane_resources->planes[i];
+
+        drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
+        if (!plane) {
+            printf("drmModeGetPlane(%u) failed: %s\n", plane_id, strerror(errno));
+            continue;
+        }
+
+        /* Print plane info. */
+        print_plane_info(_this, plane);
+        KMSDRM_drmModeFreePlane(plane);
+    }
+
+    KMSDRM_drmModeFreePlaneResources(plane_resources);
+}
+#endif
+
+/* Get the plane_id of a plane that is of the specified plane type (primary,
+   overlay, cursor...) and can use specified CRTC. */
+static int get_plane_id(SDL_VideoDevice *_this, unsigned int crtc_id, uint32_t plane_type)
+{
+    drmModeRes *resources = NULL;
+    drmModePlaneResPtr plane_resources = NULL;
+    uint32_t i, j;
+    unsigned int crtc_index = 0;
+    int ret = -EINVAL;
+    int found = 0;
+
+    SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
+
+    resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
+
+    /* Get the crtc_index for the current CRTC.
+       It's needed to find out if a plane supports the CRTC. */
+    for (i = 0; i < resources->count_crtcs; i++) {
+        if (resources->crtcs[i] == crtc_id) {
+            crtc_index = i;
+            break;
+        }
+    }
+
+    plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
+    if (!plane_resources) {
+        return SDL_SetError("drmModeGetPlaneResources failed.");
+    }
+
+    /* Iterate on all the available planes. */
+    for (i = 0; (i < plane_resources->count_planes) && !found; i++) {
+
+        uint32_t plane_id = plane_resources->planes[i];
+
+        drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
+        if (!plane) {
+            continue;
+        }
+
+        /* See if the current CRTC is available for this plane. */
+        if (plane->possible_crtcs & (1 << crtc_index)) {
+
+            drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(
+                viddata->drm_fd, plane_id, DRM_MODE_OBJECT_PLANE);
+            ret = plane_id;
+
+            /* Iterate on the plane props to find the type of the plane,
+               to see if it's of the type we want. */
+            for (j = 0; j < props->count_props; j++) {
+
+                drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd,
+                    props->props[j]);
+
+                if ((strcmp(p->name, "type") == 0) && (props->prop_values[j] == plane_type)) {
+                    /* found our plane, use that: */
+                    found = 1;
+                }
+
+                KMSDRM_drmModeFreeProperty(p);
+            }
+
+            KMSDRM_drmModeFreeObjectProperties(props);
+        }
+
+        KMSDRM_drmModeFreePlane(plane);
+    }
+
+    KMSDRM_drmModeFreePlaneResources(plane_resources);
+    KMSDRM_drmModeFreeResources(resources);
+
+    return ret;
+}
+
+/* Setup a plane and it's props. */
+bool setup_plane(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, KMSDRM_plane **_plane, uint32_t plane_type)
+{
+    uint32_t plane_id;
+    SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
+    bool ret = true;
+
+    *_plane = SDL_calloc(1, sizeof(**_plane));
+    if (!(*_plane)) {
+        ret = false;
+        goto cleanup;
+    }
+
+    /* Get plane ID for a given CRTC and plane type. */
+    plane_id = get_plane_id(_this, dispdata->crtc.crtc->crtc_id, plane_type);
+
+    if (!plane_id) {
+        ret = SDL_SetError("Invalid Plane ID");
+        goto cleanup;
+    }
+
+    /* Get the DRM plane itself. */
+    (*_plane)->plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
+
+    /* Get the DRM plane properties. */
+    if ((*_plane)->plane) {
+        unsigned int i;
+        (*_plane)->props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
+        (*_plane)->plane->plane_id, DRM_MODE_OBJECT_PLANE);
+        (*_plane)->props_info = SDL_calloc((*_plane)->props->count_props, sizeof(*(*_plane)->props_info));
+
+        if ( !((*_plane)->props_info) ) {
+            ret = false;
+            goto cleanup;
+        }
+
+        for (i = 0; i < (*_plane)->props->count_props; i++) {
+            (*_plane)->props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd, (*_plane)->props->props[i]);
+        }
+    }
+
+cleanup:
+    if (!ret) {
+        if (*_plane) {
+            SDL_free(*_plane);
+            *_plane = NULL;
+        }
+    }
+    return ret;
+}
+
+/* Free a plane and it's props. */
+void free_plane(KMSDRM_plane **_plane)
+{
+    if (*_plane) {
+        if ((*_plane)->plane) {
+            KMSDRM_drmModeFreePlane((*_plane)->plane);
+            (*_plane)->plane = NULL;
+        }
+        if ((*_plane)->props_info) {
+            SDL_free((*_plane)->props_info);
+            (*_plane)->props_info = NULL;
+        }
+        SDL_free(*_plane);
+        *_plane = NULL;
+    }
+}
+
+/**********************************************************************************/
+/* The most important ATOMIC fn of the backend.                                   */
+/* A PLANE reads a BUFFER, and a CRTC reads a PLANE and sends it's contents       */
+/*   over to a CONNECTOR->ENCODER system (several CONNECTORS can be connected     */
+/*   to the same PLANE).                                                          */
+/*   Think of a plane as a "frame" sorrounding a picture, where the "picture"     */
+/*   is the buffer, and we move the "frame" from  a picture to another,           */
+/*   and the one that has the "frame" is the one sent over to the screen          */
+/*   via the CONNECTOR->ENCODER system.                                           */
+/*   Think of a PLANE as being "in the middle", it's the CENTRAL part             */
+/*   bewteen the CRTC and the BUFFER that is shown on screen.                     */
+/*   What we do here is connect a PLANE to a CRTC and a BUFFER.                   */
+/*   -ALWAYS set the CRTC_ID and FB_ID attribs of a plane at the same time,       */
+/*   meaning IN THE SAME atomic request.                                          */
+/*   -And NEVER destroy a GBM surface whose buffers are being read by a plane:    */
+/*   first, move the plane away from those buffers and ONLY THEN destroy the      */
+/*   buffers and/or the GBM surface containig them.                               */
+/**********************************************************************************/
+void
+drm_atomic_set_plane_props(SDL_DisplayData *dispdata, struct KMSDRM_PlaneInfo *info)
+{
+    /* Do we have a set of changes already in the making? If not, allocate a new one. */
+    if (!dispdata->atomic_req) {
+        dispdata->atomic_req = KMSDRM_drmModeAtomicAlloc();
+    }
+
+    add_plane_property(dispdata->atomic_req, info->plane, "FB_ID", info->fb_id);
+    add_plane_property(dispdata->atomic_req, info->plane, "CRTC_ID", info->crtc_id);
+    add_plane_property(dispdata->atomic_req, info->plane, "SRC_W", info->src_w << 16);
+    add_plane_property(dispdata->atomic_req, info->plane, "SRC_H", info->src_h << 16);
+    add_plane_property(dispdata->atomic_req, info->plane, "SRC_X", info->src_x);
+    add_plane_property(dispdata->atomic_req, info->plane, "SRC_Y", info->src_y);
+    add_plane_property(dispdata->atomic_req, info->plane, "CRTC_W", info->crtc_w);
+    add_plane_property(dispdata->atomic_req, info->plane, "CRTC_H", info->crtc_h);
+    add_plane_property(dispdata->atomic_req, info->plane, "CRTC_X", info->crtc_x);
+    add_plane_property(dispdata->atomic_req, info->plane, "CRTC_Y", info->crtc_y);
+}
+
+int drm_atomic_commit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, bool blocking, bool allow_modeset)
+{
+    SDL_VideoData *viddata = ((SDL_VideoData *)_this->internal);
+    uint32_t atomic_flags = 0;
+    int ret;
+
+    if (!blocking) {
+        atomic_flags |= DRM_MODE_ATOMIC_NONBLOCK;
+    }
+
+    if (allow_modeset) {
+        atomic_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+    }
+
+    /* Never issue a new atomic commit if previous has not yet completed,
+       or it will error. */
+    drm_atomic_waitpending(_this, dispdata);
+
+    ret = KMSDRM_drmModeAtomicCommit(viddata->drm_fd, dispdata->atomic_req,
+              atomic_flags, NULL);
+
+    if (ret) {
+        SDL_SetError("Atomic commit failed, returned %d.", ret);
+        /* Uncomment this for fast-debugging */
+#if 0
+        printf("ATOMIC COMMIT FAILED: %s.\n", strerror(errno));
+#endif
+        goto out;
+    }
+
+    if (dispdata->kms_in_fence_fd != -1) {
+        close(dispdata->kms_in_fence_fd);
+        dispdata->kms_in_fence_fd = -1;
+    }
+
+out:
+    KMSDRM_drmModeAtomicFree(dispdata->atomic_req);
+    dispdata->atomic_req = NULL;
+
+    return ret;
+}
+
+void
+drm_atomic_waitpending(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
+{
+    /* Will return immediately if we have already destroyed the fence, because we NULL-ify it just after.
+       Also, will return immediately in double-buffer mode, because kms_fence will alsawys be NULL. */
+    if (dispdata->kms_fence) {
+       EGLint status;
+
+        do {
+            status = _this->egl_data->eglClientWaitSyncKHR(_this->egl_data->egl_display,
+                dispdata->kms_fence, 0, EGL_FOREVER_KHR);
+        } while (status != EGL_CONDITION_SATISFIED_KHR);
+
+        _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, dispdata->kms_fence);
+            dispdata->kms_fence = NULL;
+    }
+}
+
 static bool KMSDRM_Available(void)
 {
 #ifdef SDL_PLATFORM_OPENBSD
@@ -519,14 +912,14 @@ static drmModeModeInfo *KMSDRM_GetClosestDisplayMode(SDL_VideoDisplay *display,
 {
 
     SDL_DisplayData *dispdata = display->internal;
-    drmModeConnector *connector = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
 
     SDL_DisplayMode closest;
     drmModeModeInfo *drm_mode;
 
     if (SDL_GetClosestFullscreenDisplayMode(display->id, width, height, 0.0f, false, &closest)) {
         const SDL_DisplayModeData *modedata = closest.internal;
-        drm_mode = &connector->modes[modedata->mode_index];
+        drm_mode = &conn->modes[modedata->mode_index];
         return drm_mode;
     } else {
         return NULL;
@@ -542,6 +935,11 @@ static bool KMSDRM_DropMaster(SDL_VideoDevice *_this)
 {
     SDL_VideoData *viddata = _this->internal;
 
+    if (viddata->is_atomic) {  // turn off atomic support until we are in control again.
+        KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 0);
+        KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0);
+    }
+
     /* Check if we have DRM master to begin with */
     if (KMSDRM_drmAuthMagic(viddata->drm_fd, 0) == -EACCES) {
         /* Nope, nothing to do then */
@@ -568,15 +966,15 @@ static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this)
             dispdata = SDL_GetDisplayDriverData(displays[i]);
 
             // Free connector
-            if (dispdata && dispdata->connector) {
-                KMSDRM_drmModeFreeConnector(dispdata->connector);
-                dispdata->connector = NULL;
+            if (dispdata && dispdata->connector.connector) {
+                KMSDRM_drmModeFreeConnector(dispdata->connector.connector);
+                dispdata->connector.connector = NULL;
             }
 
             // Free CRTC
-            if (dispdata && dispdata->crtc) {
-                KMSDRM_drmModeFreeCrtc(dispdata->crtc);
-                dispdata->crtc = NULL;
+            if (dispdata && dispdata->crtc.crtc) {
+                KMSDRM_drmModeFreeCrtc(dispdata->crtc.crtc);
+                dispdata->crtc.crtc = NULL;
             }
         }
         SDL_free(displays);
@@ -706,7 +1104,7 @@ static int KMSDRM_CrtcGetOrientation(uint32_t drm_fd, uint32_t crtc_id)
 
 /* Gets a DRM connector, builds an SDL_Display with it, and adds it to the
    list of SDL Displays in _this->displays[]  */
-static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connector, drmModeRes *resources)
+static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *conn, drmModeRes *resources)
 {
     SDL_VideoData *viddata = _this->internal;
     SDL_DisplayData *dispdata = NULL;
@@ -734,6 +1132,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
        to sane values. */
     dispdata->cursor_bo = NULL;
     dispdata->cursor_bo_drm_fd = -1;
+    dispdata->kms_out_fence_fd = -1;
 
     /* Since we create and show the default cursor on KMSDRM_InitMouse(),
        and we call KMSDRM_InitMouse() when we create a window, we have to know
@@ -750,7 +1149,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
             continue;
         }
 
-        if (encoder->encoder_id == connector->encoder_id) {
+        if (encoder->encoder_id == conn->encoder_id) {
             break;
         }
 
@@ -768,13 +1167,13 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
                 continue;
             }
 
-            for (j = 0; j < connector->count_encoders; j++) {
-                if (connector->encoders[j] == encoder->encoder_id) {
+            for (j = 0; j < conn->count_encoders; j++) {
+                if (conn->encoders[j] == encoder->encoder_id) {
                     break;
                 }
             }
 
-            if (j != connector->count_encoders) {
+            if (j != conn->count_encoders) {
                 break;
             }
 
@@ -811,8 +1210,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     // Find the index of the mode attached to this CRTC
     mode_index = -1;
 
-    for (i = 0; i < connector->count_modes; i++) {
-        drmModeModeInfo *mode = &connector->modes[i];
+    for (i = 0; i < conn->count_modes; i++) {
+        drmModeModeInfo *mode = &conn->modes[i];
 
         if (!SDL_memcmp(mode, &crtc->mode, sizeof(crtc->mode))) {
             mode_index = i;
@@ -824,8 +1223,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
         int current_area, largest_area = 0;
 
         // Find the preferred mode or the highest resolution mode
-        for (i = 0; i < connector->count_modes; i++) {
-            drmModeModeInfo *mode = &connector->modes[i];
+        for (i = 0; i < conn->count_modes; i++) {
+            drmModeModeInfo *mode = &conn->modes[i];
 
             if (mode->type & DRM_MODE_TYPE_PREFERRED) {
                 mode_index = i;
@@ -839,7 +1238,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
             }
         }
         if (mode_index != -1) {
-            crtc->mode = connector->modes[mode_index];
+            crtc->mode = conn->modes[mode_index];
         }
     }
 
@@ -869,24 +1268,48 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     }
 
     // Store the connector and crtc for this display.
-    dispdata->connector = connector;
-    dispdata->crtc = crtc;
+    dispdata->connector.connector = conn;
+    dispdata->crtc.crtc = crtc;
 
     // save previous vrr state
     dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
     // try to enable vrr
-    if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, connector->connector_id)) {
+    if (KMSDRM_ConnectorCheckVrrCapable(viddata->drm_fd, conn->connector_id)) {
         SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
         KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
     }
 
     // Set the name by the connector type, if possible
     if (KMSDRM_drmModeGetConnectorTypeName) {
-        connector_type = KMSDRM_drmModeGetConnectorTypeName(connector->connector_type);
+        connector_type = KMSDRM_drmModeGetConnectorTypeName(conn->connector_type);
         if (connector_type == NULL) {
             connector_type = "Unknown";
         }
-        SDL_snprintf(name_fmt, sizeof(name_fmt), "%s-%u", connector_type, connector->connector_type_id);
+        SDL_snprintf(name_fmt, sizeof(name_fmt), "%s-%u", connector_type, conn->connector_type_id);
+    }
+
+    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 = false;
+        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, conn->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 = false;
+        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]);
     }
 
     /*****************************************/
@@ -925,6 +1348,10 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     display_properties = SDL_GetDisplayProperties(display_id);
     SDL_SetNumberProperty(display_properties, SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER, orientation);
 
+#ifdef DEBUG_KMSDRM  // Use this if you ever need to see info on all available planes.
+    get_planes_info(_this, dispdata);
+#endif
+
 cleanup:
     if (encoder) {
         KMSDRM_drmModeFreeEncoder(encoder);
@@ -932,14 +1359,15 @@ cleanup:
     if (ret) {
         // Error (complete) cleanup
         if (dispdata) {
-            if (dispdata->connector) {
-                KMSDRM_drmModeFreeConnector(dispdata->connector);
-                dispdata->connector = NULL;
+            if (dispdata->connector.connector) {
+                KMSDRM_drmModeFreeConnector(dispdata->connector.connector);
             }
-            if (dispdata->crtc) {
-                KMSDRM_drmModeFreeCrtc(dispdata->crtc);
-                dispdata->crtc = NULL;
+            if (dispdata->crtc.crtc) {
+                KMSDRM_drmModeFreeCrtc(dispdata->crtc.crtc);
             }
+            SDL_free(dispdata->connector.props_info);
+            SDL_free(dispdata->crtc.props_info);
+            SDL_free(dispdata->display_plane);
             SDL_free(dispdata);
         }
     }
@@ -988,13 +1416,25 @@ static void KMSDRM_SortDisplays(SDL_VideoDevice *_this)
     }
 }
 
+static bool set_client_atomic_caps(int fd)
+{
+    if (!SDL_GetHintBoolean(SDL_HINT_KMSDRM_ATOMIC, true)) {
+        return false;  // emergency escape hatch.
+    } else if (KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1)) {
+        return false;  // no atomic modesetting support.
+    } else if (KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
+        KMSDRM_drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 0);  // turn this back off again.
+        return false;  // no universal planes support.
+    }
+    return true;
+}
+
 /* Initializes the list of SDL displays: we build a new display for each
    connecter connector we find.
    This is to be called early, in VideoInit(), because it gets us
    the videomode information, which SDL needs immediately after VideoInit(). */
 static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
 {
-
     SDL_VideoData *viddata = _this->internal;
     drmModeRes *resources = NULL;
     uint64_t async_pageflip = 0;
@@ -1015,6 +1455,11 @@ static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
 
     SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Opened DRM FD (%d)", viddata->drm_fd);
 
+    // Set ATOMIC & UNIVERSAL PLANES compatibility
+    viddata->is_atomic = set_client_atomic_caps(viddata->drm_fd);
+
+    SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "DRM FD (%d) %s atomic", viddata->drm_fd, viddata->is_atomic ? "SUPPORTS" : "DOES NOT SUPPORT");
+
     // Get all of the available connectors / devices / crtcs
     resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
     if (!resources) {
@@ -1025,22 +1470,20 @@ static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
     /* Iterate on the available connectors. For every connected connector,
        we create an SDL_Display and add it to the list of SDL Displays. */
     for (i = 0; i < resources->count_connectors; i++) {
-        drmModeConnector *connector = KMSDRM_drmModeGetConnector(viddata->drm_fd,
-                                                                 resources->connectors[i]);
-
-        if (!connector) {
+        drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]);
+        if (!conn) {
             continue;
         }
 
-        if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes) {
+        if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) {
             /* If it's a connected connector with available videomodes, try to add
                an SDL Display representing it. KMSDRM_AddDisplay() is purposely void,
                so if it fails (no encoder for connector, no valid video mode for
                connector etc...) we can keep looking for connected connectors. */
-            KMSDRM_AddDisplay(_this, connector, resources);
+            KMSDRM_AddDisplay(_this, conn, resources);
         } else {
             // If it's not, free it now.
-            KMSDRM_drmModeFreeConnector(connector);
+            KMSDRM_drmModeFreeConnector(conn);
         }
     }
 
@@ -1105,6 +1548,8 @@ static bool KMSDRM_GBMInit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
         }
     }
 
+    viddata->is_atomic = set_client_atomic_caps(viddata->drm_fd);
+
     // Set the FD as current DRM master.
     KMSDRM_drmSetMaster(viddata->drm_fd);
 
@@ -1112,6 +1557,13 @@ static bool KMSDRM_GBMInit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
     viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd);
     if (!viddata->gbm_dev) {
         result = SDL_SetError("Couldn't create gbm device.");
+    } else {
+        // Setup the display plane. ONLY do this after dispdata has the right
+        // crtc and connector, because these are used in this function.
+        result = setup_plane(_this, dispdata, &dispdata->display_plane, DRM_PLANE_TYPE_PRIMARY);
+        if (!result) {
+            SDL_SetError("can't find suitable display plane.");
+        }
     }
 
     viddata->gbm_init = true;
@@ -1124,6 +1576,12 @@ static void KMSDRM_GBMDeinit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
 {
     SDL_VideoData *viddata = _this->internal;
 
+    // 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) {
@@ -1153,19 +1611,86 @@ static void KMSDRM_DestroySurfaces(SDL_VideoDevice *_this, SDL_Window *window)
     /**********************************************/
     // KMSDRM_WaitPageflip(_this, windata);
 
+    if (viddata->is_atomic) {
+
+    /* TODO : Continue investigating why this doesn't work. We should do this instead
+       of making the display plane point to the TTY console, which isn't there
+       after creating and destroying a Vulkan window. */
+
+#if 0  // (note that this code has bitrotted a little, in addition to TODO comment above.)
+        /* 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 can destroy the GBM buffers and surface. */
+        add_connector_property(dispdata->atomic_req, dispdata->connector , "CRTC_ID", 0);
+        add_crtc_property(dispdata->atomic_req, dispdata->crtc , "MODE_ID", 0);
+        add_crtc_property(dispdata->atomic_req, dispdata->crtc , "active", 0);
+        /**********************************************/
+        /* Wait for last issued pageflip to complete. */
+        /**********************************************/
+        KMSDRM_WaitPageFlip(_this, windata, -1);
+
+        plane_info.plane = dispdata->display_plane;
+        plane_info.crtc_id = 0;
+        plane_info.fb_id = 0;
+        /***********************************************************************/
+        /* Restore the original CRTC configuration: configue the crtc with the */
+        /* original video mode and make it point to the original TTY buffer.   */
+        /***********************************************************************/
+
+        drm_atomic_set_plane_props(dispdata, &plane_info);
+        ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
+                dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
+                &dispdata->original_mode);
+
+        /* Issue atomic commit that is blocking and allows modesetting. */
+        if (drm_atomic_commit(_this, dispdata, true, true)) {
+            SDL_SetError("Failed to issue atomic commit on surfaces destruction.");
+        /* If we failed to set the original mode, try to set the connector prefered mode. */
+        if (ret && (dispdata->crtc->mode_valid == 0)) {
+            ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
+                    dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
+                    &dispdata->original_mode);
+        }
+#endif
+
+#if 1
+        /************************************************************/
+        /* Make the display plane point to the original TTY buffer. */
+        /* We have to configure it's input and output scaling       */
+        /* parameters accordingly.                                  */
+        /************************************************************/
+
+        KMSDRM_PlaneInfo plane_info;
+        SDL_zero(plane_info);
+        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 = dispdata->original_mode.hdisplay;
+        plane_info.src_h = dispdata->original_mode.vdisplay;
+        plane_info.crtc_w = dispdata->original_mode.hdisplay;
+        plane_info.crtc_h = dispdata->original_mode.vdisplay;
+
+        drm_atomic_set_plane_props(dispdata, &plane_info);
+
+        if (drm_atomic_commit(_this, dispdata, true, false)) {
+            SDL_SetError("Failed to issue atomic commit on surfaces destruction.");
+        }
+    }
+#endif
+
     /************************************************************************/
     // Restore the original CRTC configuration: configure the crtc with the
     // original video mode and make it point to the original TTY buffer.
     /************************************************************************/
 
-    ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
-                                dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
+    ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc.crtc->crtc_id,
+                                dispdata->crtc.crtc->buffer_id, 0, 0, &dispdata->connector.connector->connector_id, 1,
                                 &dispdata->original_mode);
 
     // If we failed to set the original mode, try to set the connector preferred mode.
-    if (ret && (dispdata->crtc->mode_valid == 0)) {
-        ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc->crtc_id,
-                                    dispdata->crtc->buffer_id, 0, 0, &dispdata->connector->connector_id, 1,
+    if (ret && (dispdata->crtc.crtc->mode_valid == 0)) {
+        ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc.crtc->crtc_id,
+                                    dispdata->crtc.crtc->buffer_id, 0, 0, &dispdata->connector.connector->connector_id, 1,
                                     &dispdata->original_mode);
     }
 
@@ -1412,7 +1937,7 @@ void KMSDRM_VideoQuit(SDL_VideoDevice *_this)
 bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
 {
     SDL_DisplayData *dispdata = display->internal;
-    drmModeConnector *conn = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
     SDL_DisplayMode mode;
     int i;
 
@@ -1445,7 +1970,7 @@ bool KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SD
     SDL_VideoData *viddata = _this->internal;
     SDL_DisplayData *dispdata = display->internal;
     SDL_DisplayModeData *modedata = mode->internal;
-    drmModeConnector *conn = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
     int i;
 
     // Don't do anything if we are in Vulkan mode.
@@ -1481,7 +2006,7 @@ void KMSDRM_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
     }
 
     // restore vrr state
-    KMSDRM_CrtcSetVrr(windata->viddata->drm_fd, dispdata->crtc->crtc_id, dispdata->saved_vrr);
+    KMSDRM_CrtcSetVrr(windata->viddata->drm_fd, dispdata->crtc.crtc->crtc_id, dispdata->saved_vrr);
 
     viddata = windata->viddata;
 

+ 67 - 2
src/video/kmsdrm/SDL_kmsdrmvideo.h

@@ -19,6 +19,8 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
+// Atomic KMSDRM backend originally written by Manuel Alfayate Corchete <[email protected]>
+
 #include "SDL_internal.h"
 
 #ifndef SDL_kmsdrmvideo_h
@@ -32,6 +34,7 @@
 #include <xf86drmMode.h>
 #include <gbm.h>
 #include <EGL/egl.h>
+#include <EGL/eglext.h>
 
 #ifndef DRM_FORMAT_MOD_INVALID
 #define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
@@ -72,6 +75,27 @@
 #define GBM_BO_USE_LINEAR   (1 << 4)
 #endif
 
+typedef struct KMSDRM_plane
+{
+    drmModePlane *plane;
+    drmModeObjectProperties *props;
+    drmModePropertyRes **props_info;
+} KMSDRM_plane;
+ 
+typedef struct KMSDRM_crtc
+{
+    drmModeCrtc *crtc;
+    drmModeObjectProperties *props;
+    drmModePropertyRes **props_info;
+} KMSDRM_crtc;
+
+typedef struct KMSDRM_connector
+{
+    drmModeConnector *connector;
+    drmModeObjectProperties *props;
+    drmModePropertyRes **props_info;
+} KMSDRM_connector;
+
 struct SDL_VideoData
 {
     int devindex;     // device index that was passed on creation
@@ -92,6 +116,7 @@ struct SDL_VideoData
        open 1 FD and create 1 gbm device. */
     bool gbm_init;
 
+    bool is_atomic;  // true if atomic interfaces are supported.
 };
 
 struct SDL_DisplayModeData
@@ -101,8 +126,11 @@ struct SDL_DisplayModeData
 
 struct SDL_DisplayData
 {
-    drmModeConnector *connector;
-    drmModeCrtc *crtc;
+    KMSDRM_plane *display_plane;
+    KMSDRM_plane *cursor_plane;
+    KMSDRM_crtc crtc;
+    KMSDRM_connector connector;
+
     drmModeModeInfo mode;
     drmModeModeInfo original_mode;
     drmModeModeInfo fullscreen_mode;
@@ -118,6 +146,15 @@ struct SDL_DisplayData
     int cursor_bo_drm_fd;
     uint64_t cursor_w, cursor_h;
 
+    /* Central atomic request list, used for the prop
+       changeset related to pageflip in SwapWindow. */
+    drmModeAtomicReq *atomic_req;
+
+    int kms_in_fence_fd;
+    int kms_out_fence_fd;
+    EGLSyncKHR kms_fence;
+    EGLSyncKHR gpu_fence;
+
     bool default_cursor_init;
 };
 
@@ -137,6 +174,9 @@ struct SDL_WindowData
 
     EGLSurface egl_surface;
     bool egl_surface_dirty;
+
+    /* This dictates what approach we'll use for SwapBuffers. */
+    bool (*swap_window)(SDL_VideoDevice *_this, SDL_Window *window);
 };
 
 typedef struct KMSDRM_FBInfo
@@ -145,12 +185,37 @@ typedef struct KMSDRM_FBInfo
     uint32_t fb_id; // DRM framebuffer ID
 } KMSDRM_FBInfo;
 
+typedef struct KMSDRM_PlaneInfo
+{
+    struct KMSDRM_plane *plane;
+    uint32_t fb_id;
+    uint32_t crtc_id;
+    int32_t src_x;
+    int32_t src_y;
+    int32_t src_w;
+    int32_t src_h;
+    int32_t crtc_x;
+    int32_t crtc_y;
+    int32_t crtc_w;
+    int32_t crtc_h;
+} KMSDRM_PlaneInfo;
+
 // Helper functions
 extern bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window);
 extern KMSDRM_FBInfo *KMSDRM_FBFromBO(SDL_VideoDevice *_this, struct gbm_bo *bo);
 extern KMSDRM_FBInfo *KMSDRM_FBFromBO2(SDL_VideoDevice *_this, struct gbm_bo *bo, int w, int h);
 extern bool KMSDRM_WaitPageflip(SDL_VideoDevice *_this, SDL_WindowData *windata);
 
+// Atomic functions that are used from SDL_kmsdrmopengles.c and SDL_kmsdrmmouse.c
+void drm_atomic_set_plane_props(SDL_DisplayData *dispdata, struct KMSDRM_PlaneInfo *info);
+void drm_atomic_waitpending(SDL_VideoDevice *_this, SDL_DisplayData *dispdata);
+int drm_atomic_commit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, bool blocking, bool allow_modeset);
+int add_plane_property(drmModeAtomicReq *req, struct KMSDRM_plane *plane, const char *name, uint64_t value);
+int add_crtc_property(drmModeAtomicReq *req, struct KMSDRM_crtc *crtc, const char *name, uint64_t value);
+int add_connector_property(drmModeAtomicReq *req, struct KMSDRM_connector *connector, const char *name, uint64_t value);
+bool setup_plane(SDL_VideoDevice *_this, SDL_DisplayData *dispdata, struct KMSDRM_plane **plane, uint32_t plane_type);
+void free_plane(struct KMSDRM_plane **plane);
+
 /****************************************************************************/
 // SDL_VideoDevice functions declaration
 /****************************************************************************/

+ 4 - 4
src/video/kmsdrm/SDL_kmsdrmvulkan.c

@@ -177,7 +177,7 @@ bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     uint32_t display_count;
     uint32_t mode_count;
     uint32_t plane_count;
-    uint32_t plane = UINT32_MAX;
+    uint32_t _plane = UINT32_MAX;
 
     VkPhysicalDevice *physical_devices = NULL;
     VkPhysicalDeviceProperties *device_props = NULL;
@@ -456,13 +456,13 @@ bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
         vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
         if (plane_caps.supportedAlpha == alpha_mode) {
             // Yep, this plane is alright.
-            plane = i;
+            _plane = i;
             break;
         }
     }
 
     // If we couldn't find an appropriate plane, error out.
-    if (plane == UINT32_MAX) {
+    if (_plane == UINT32_MAX) {
         SDL_SetError("Vulkan couldn't find an appropriate plane.");
         goto clean;
     }
@@ -477,7 +477,7 @@ bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     SDL_zero(display_plane_surface_create_info);
     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 = plane;
+    display_plane_surface_create_info.planeIndex = _plane;
     display_plane_surface_create_info.imageExtent = image_size;
     display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
     display_plane_surface_create_info.alphaMode = alpha_mode;