瀏覽代碼

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"
 #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.
  * 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.
     // Destroy the curso GBM BO.
     if (dispdata->cursor_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);
         KMSDRM_gbm_bo_destroy(dispdata->cursor_bo);
         dispdata->cursor_bo = NULL;
         dispdata->cursor_bo = NULL;
         dispdata->cursor_bo_drm_fd = -1;
         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. */
    build a window and assign a display to it. */
 bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
 bool KMSDRM_CreateCursorBO(SDL_VideoDisplay *display)
 {
 {
-
     SDL_VideoDevice *dev = SDL_GetVideoDevice();
     SDL_VideoDevice *dev = SDL_GetVideoDevice();
     SDL_VideoData *viddata = dev->internal;
     SDL_VideoData *viddata = dev->internal;
     SDL_DisplayData *dispdata = display->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,
     if (!KMSDRM_gbm_device_is_format_supported(viddata->gbm_dev,
                                                GBM_FORMAT_ARGB8888,
                                                GBM_FORMAT_ARGB8888,
                                                GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) {
                                                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_VideoDevice *video_device = SDL_GetVideoDevice();
     SDL_VideoData *viddata = video_device->internal;
     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;
     return result;
 }
 }
 
 
 // Dump a cursor buffer to a display's DRM cursor BO.
 // 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_DisplayData *dispdata = display->internal;
     SDL_CursorData *curdata = cursor->internal;
     SDL_CursorData *curdata = cursor->internal;
@@ -173,22 +207,42 @@ static bool KMSDRM_DumpCursorToBO(SDL_VideoDisplay *display, SDL_Cursor *cursor)
         goto cleanup;
         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 {
     } 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:
 cleanup:
-
     if (ready_buffer) {
     if (ready_buffer) {
         SDL_free(ready_buffer);
         SDL_free(ready_buffer);
     }
     }
@@ -316,7 +370,7 @@ static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
             if (cursor) {
             if (cursor) {
                 /* Dump the cursor to the display DRM cursor BO so it becomes visible
                 /* Dump the cursor to the display DRM cursor BO so it becomes visible
                    on that display. */
                    on that display. */
-                result = KMSDRM_DumpCursorToBO(display, cursor);
+                result = KMSDRM_DumpCursorToBO(display, mouse, cursor);
             } else {
             } else {
                 // Hide the cursor on that display.
                 // Hide the cursor on that display.
                 result = KMSDRM_RemoveCursorFromBO(display);
                 result = KMSDRM_RemoveCursorFromBO(display);
@@ -327,6 +381,18 @@ static bool KMSDRM_ShowCursor(SDL_Cursor *cursor)
     return result;
     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)
 static bool KMSDRM_WarpMouseGlobal(float x, float y)
 {
 {
     SDL_Mouse *mouse = SDL_GetMouse();
     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.
         // And now update the cursor graphic position on screen.
         if (dispdata->cursor_bo) {
         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 {
         } else {
             return SDL_SetError("Cursor not initialized properly.");
             return SDL_SetError("Cursor not initialized properly.");
         }
         }
     } else {
     } else {
         return SDL_SetError("No mouse or current cursor.");
         return SDL_SetError("No mouse or current cursor.");
     }
     }
+
+    return true;
 }
 }
 
 
 static bool KMSDRM_WarpMouse(SDL_Window *window, float x, float y)
 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) {
     if (mouse && mouse->cur_cursor && mouse->focus) {
         SDL_Window *window = mouse->focus;
         SDL_Window *window = mouse->focus;
         SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
         SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
+        SDL_VideoDevice *dev = SDL_GetVideoDevice();
+        SDL_VideoData *viddata = dev->internal;
 
 
         if (!dispdata->cursor_bo) {
         if (!dispdata->cursor_bo) {
             return SDL_SetError("Cursor not initialized properly.");
             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;
     return true;

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

@@ -28,10 +28,25 @@
 #include "SDL_kmsdrmdyn.h"
 #include "SDL_kmsdrmdyn.h"
 #include <errno.h>
 #include <errno.h>
 
 
+#define VOID2U64(x) ((uint64_t)(size_t)(x))
+
 #ifndef EGL_PLATFORM_GBM_MESA
 #ifndef EGL_PLATFORM_GBM_MESA
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #endif
 #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
 // EGL implementation of SDL OpenGL support
 
 
 void KMSDRM_GLES_DefaultProfileConfig(SDL_VideoDevice *_this, int *mask, int *major, int *minor)
 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;
     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_WindowData *windata = window->internal;
     SDL_DisplayData *dispdata = SDL_GetDisplayDriverDataForWindow(window);
     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
     // Release the previous front buffer
     if (windata->bo) {
     if (windata->bo) {
         KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
         KMSDRM_gbm_surface_release_buffer(windata->gs, windata->bo);
-        windata->bo = NULL;
     }
     }
 
 
     windata->bo = windata->next_bo;
     windata->bo = windata->next_bo;
 
 
     /* Mark a buffer to become the next front buffer.
     /* 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,
     if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display,
                                           windata->egl_surface))) {
                                           windata->egl_surface))) {
         return SDL_SetError("eglSwapBuffers failed");
         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
            drmModePageFlip can be used the CRTC has to be configured to use
            the current connector and mode with drmModeSetCrtc */
            the current connector and mode with drmModeSetCrtc */
         ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd,
         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) {
         if (ret) {
             return SDL_SetError("Could not set videomode on CRTC.");
             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;
             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);
                                      fb_info->fb_id, flip_flags, &windata->waiting_for_flip);
 
 
         if (ret == 0) {
         if (ret == 0) {
@@ -198,6 +496,26 @@ bool KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
     return true;
     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)
 SDL_EGL_MakeCurrent_impl(KMSDRM)
 
 
 #endif // SDL_VIDEO_DRIVER_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(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_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_MODULE
 #undef SDL_KMSDRM_SYM
 #undef SDL_KMSDRM_SYM
 #undef SDL_KMSDRM_SYM_CONST
 #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 int kmsdrm_dri_devnamesize = 0;
 static char kmsdrm_dri_cardpath[32];
 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
 #ifndef EGL_PLATFORM_GBM_MESA
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #define EGL_PLATFORM_GBM_MESA 0x31D7
 #endif
 #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)
 static bool KMSDRM_Available(void)
 {
 {
 #ifdef SDL_PLATFORM_OPENBSD
 #ifdef SDL_PLATFORM_OPENBSD
@@ -519,14 +912,14 @@ static drmModeModeInfo *KMSDRM_GetClosestDisplayMode(SDL_VideoDisplay *display,
 {
 {
 
 
     SDL_DisplayData *dispdata = display->internal;
     SDL_DisplayData *dispdata = display->internal;
-    drmModeConnector *connector = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
 
 
     SDL_DisplayMode closest;
     SDL_DisplayMode closest;
     drmModeModeInfo *drm_mode;
     drmModeModeInfo *drm_mode;
 
 
     if (SDL_GetClosestFullscreenDisplayMode(display->id, width, height, 0.0f, false, &closest)) {
     if (SDL_GetClosestFullscreenDisplayMode(display->id, width, height, 0.0f, false, &closest)) {
         const SDL_DisplayModeData *modedata = closest.internal;
         const SDL_DisplayModeData *modedata = closest.internal;
-        drm_mode = &connector->modes[modedata->mode_index];
+        drm_mode = &conn->modes[modedata->mode_index];
         return drm_mode;
         return drm_mode;
     } else {
     } else {
         return NULL;
         return NULL;
@@ -542,6 +935,11 @@ static bool KMSDRM_DropMaster(SDL_VideoDevice *_this)
 {
 {
     SDL_VideoData *viddata = _this->internal;
     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 */
     /* Check if we have DRM master to begin with */
     if (KMSDRM_drmAuthMagic(viddata->drm_fd, 0) == -EACCES) {
     if (KMSDRM_drmAuthMagic(viddata->drm_fd, 0) == -EACCES) {
         /* Nope, nothing to do then */
         /* Nope, nothing to do then */
@@ -568,15 +966,15 @@ static void KMSDRM_DeinitDisplays(SDL_VideoDevice *_this)
             dispdata = SDL_GetDisplayDriverData(displays[i]);
             dispdata = SDL_GetDisplayDriverData(displays[i]);
 
 
             // Free connector
             // 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
             // 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);
         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
 /* Gets a DRM connector, builds an SDL_Display with it, and adds it to the
    list of SDL Displays in _this->displays[]  */
    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_VideoData *viddata = _this->internal;
     SDL_DisplayData *dispdata = NULL;
     SDL_DisplayData *dispdata = NULL;
@@ -734,6 +1132,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
        to sane values. */
        to sane values. */
     dispdata->cursor_bo = NULL;
     dispdata->cursor_bo = NULL;
     dispdata->cursor_bo_drm_fd = -1;
     dispdata->cursor_bo_drm_fd = -1;
+    dispdata->kms_out_fence_fd = -1;
 
 
     /* Since we create and show the default cursor on KMSDRM_InitMouse(),
     /* 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
        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;
             continue;
         }
         }
 
 
-        if (encoder->encoder_id == connector->encoder_id) {
+        if (encoder->encoder_id == conn->encoder_id) {
             break;
             break;
         }
         }
 
 
@@ -768,13 +1167,13 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
                 continue;
                 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;
                     break;
                 }
                 }
             }
             }
 
 
-            if (j != connector->count_encoders) {
+            if (j != conn->count_encoders) {
                 break;
                 break;
             }
             }
 
 
@@ -811,8 +1210,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
     // Find the index of the mode attached to this CRTC
     // Find the index of the mode attached to this CRTC
     mode_index = -1;
     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))) {
         if (!SDL_memcmp(mode, &crtc->mode, sizeof(crtc->mode))) {
             mode_index = i;
             mode_index = i;
@@ -824,8 +1223,8 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
         int current_area, largest_area = 0;
         int current_area, largest_area = 0;
 
 
         // Find the preferred mode or the highest resolution mode
         // 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) {
             if (mode->type & DRM_MODE_TYPE_PREFERRED) {
                 mode_index = i;
                 mode_index = i;
@@ -839,7 +1238,7 @@ static void KMSDRM_AddDisplay(SDL_VideoDevice *_this, drmModeConnector *connecto
             }
             }
         }
         }
         if (mode_index != -1) {
         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.
     // 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
     // save previous vrr state
     dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
     dispdata->saved_vrr = KMSDRM_CrtcGetVrr(viddata->drm_fd, crtc->crtc_id);
     // try to enable vrr
     // 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");
         SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Enabling VRR");
         KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
         KMSDRM_CrtcSetVrr(viddata->drm_fd, crtc->crtc_id, true);
     }
     }
 
 
     // Set the name by the connector type, if possible
     // Set the name by the connector type, if possible
     if (KMSDRM_drmModeGetConnectorTypeName) {
     if (KMSDRM_drmModeGetConnectorTypeName) {
-        connector_type = KMSDRM_drmModeGetConnectorTypeName(connector->connector_type);
+        connector_type = KMSDRM_drmModeGetConnectorTypeName(conn->connector_type);
         if (connector_type == NULL) {
         if (connector_type == NULL) {
             connector_type = "Unknown";
             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);
     display_properties = SDL_GetDisplayProperties(display_id);
     SDL_SetNumberProperty(display_properties, SDL_PROP_DISPLAY_KMSDRM_PANEL_ORIENTATION_NUMBER, orientation);
     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:
 cleanup:
     if (encoder) {
     if (encoder) {
         KMSDRM_drmModeFreeEncoder(encoder);
         KMSDRM_drmModeFreeEncoder(encoder);
@@ -932,14 +1359,15 @@ cleanup:
     if (ret) {
     if (ret) {
         // Error (complete) cleanup
         // Error (complete) cleanup
         if (dispdata) {
         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);
             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
 /* Initializes the list of SDL displays: we build a new display for each
    connecter connector we find.
    connecter connector we find.
    This is to be called early, in VideoInit(), because it gets us
    This is to be called early, in VideoInit(), because it gets us
    the videomode information, which SDL needs immediately after VideoInit(). */
    the videomode information, which SDL needs immediately after VideoInit(). */
 static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
 static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
 {
 {
-
     SDL_VideoData *viddata = _this->internal;
     SDL_VideoData *viddata = _this->internal;
     drmModeRes *resources = NULL;
     drmModeRes *resources = NULL;
     uint64_t async_pageflip = 0;
     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);
     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
     // Get all of the available connectors / devices / crtcs
     resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
     resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
     if (!resources) {
     if (!resources) {
@@ -1025,22 +1470,20 @@ static bool KMSDRM_InitDisplays(SDL_VideoDevice *_this)
     /* Iterate on the available connectors. For every connected connector,
     /* Iterate on the available connectors. For every connected connector,
        we create an SDL_Display and add it to the list of SDL Displays. */
        we create an SDL_Display and add it to the list of SDL Displays. */
     for (i = 0; i < resources->count_connectors; i++) {
     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;
             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
             /* If it's a connected connector with available videomodes, try to add
                an SDL Display representing it. KMSDRM_AddDisplay() is purposely void,
                an SDL Display representing it. KMSDRM_AddDisplay() is purposely void,
                so if it fails (no encoder for connector, no valid video mode for
                so if it fails (no encoder for connector, no valid video mode for
                connector etc...) we can keep looking for connected connectors. */
                connector etc...) we can keep looking for connected connectors. */
-            KMSDRM_AddDisplay(_this, connector, resources);
+            KMSDRM_AddDisplay(_this, conn, resources);
         } else {
         } else {
             // If it's not, free it now.
             // 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.
     // Set the FD as current DRM master.
     KMSDRM_drmSetMaster(viddata->drm_fd);
     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);
     viddata->gbm_dev = KMSDRM_gbm_create_device(viddata->drm_fd);
     if (!viddata->gbm_dev) {
     if (!viddata->gbm_dev) {
         result = SDL_SetError("Couldn't create gbm device.");
         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;
     viddata->gbm_init = true;
@@ -1124,6 +1576,12 @@ static void KMSDRM_GBMDeinit(SDL_VideoDevice *_this, SDL_DisplayData *dispdata)
 {
 {
     SDL_VideoData *viddata = _this->internal;
     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(),
     /* Destroy GBM device. GBM surface is destroyed by DestroySurfaces(),
        already called when we get here. */
        already called when we get here. */
     if (viddata->gbm_dev) {
     if (viddata->gbm_dev) {
@@ -1153,19 +1611,86 @@ static void KMSDRM_DestroySurfaces(SDL_VideoDevice *_this, SDL_Window *window)
     /**********************************************/
     /**********************************************/
     // KMSDRM_WaitPageflip(_this, windata);
     // 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
     // Restore the original CRTC configuration: configure the crtc with the
     // original video mode and make it point to the original TTY buffer.
     // 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);
                                 &dispdata->original_mode);
 
 
     // If we failed to set the original mode, try to set the connector preferred 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);
                                     &dispdata->original_mode);
     }
     }
 
 
@@ -1412,7 +1937,7 @@ void KMSDRM_VideoQuit(SDL_VideoDevice *_this)
 bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
 bool KMSDRM_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
 {
 {
     SDL_DisplayData *dispdata = display->internal;
     SDL_DisplayData *dispdata = display->internal;
-    drmModeConnector *conn = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
     SDL_DisplayMode mode;
     SDL_DisplayMode mode;
     int i;
     int i;
 
 
@@ -1445,7 +1970,7 @@ bool KMSDRM_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SD
     SDL_VideoData *viddata = _this->internal;
     SDL_VideoData *viddata = _this->internal;
     SDL_DisplayData *dispdata = display->internal;
     SDL_DisplayData *dispdata = display->internal;
     SDL_DisplayModeData *modedata = mode->internal;
     SDL_DisplayModeData *modedata = mode->internal;
-    drmModeConnector *conn = dispdata->connector;
+    drmModeConnector *conn = dispdata->connector.connector;
     int i;
     int i;
 
 
     // Don't do anything if we are in Vulkan mode.
     // 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
     // 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;
     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.
   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"
 #include "SDL_internal.h"
 
 
 #ifndef SDL_kmsdrmvideo_h
 #ifndef SDL_kmsdrmvideo_h
@@ -32,6 +34,7 @@
 #include <xf86drmMode.h>
 #include <xf86drmMode.h>
 #include <gbm.h>
 #include <gbm.h>
 #include <EGL/egl.h>
 #include <EGL/egl.h>
+#include <EGL/eglext.h>
 
 
 #ifndef DRM_FORMAT_MOD_INVALID
 #ifndef DRM_FORMAT_MOD_INVALID
 #define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
 #define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
@@ -72,6 +75,27 @@
 #define GBM_BO_USE_LINEAR   (1 << 4)
 #define GBM_BO_USE_LINEAR   (1 << 4)
 #endif
 #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
 struct SDL_VideoData
 {
 {
     int devindex;     // device index that was passed on creation
     int devindex;     // device index that was passed on creation
@@ -92,6 +116,7 @@ struct SDL_VideoData
        open 1 FD and create 1 gbm device. */
        open 1 FD and create 1 gbm device. */
     bool gbm_init;
     bool gbm_init;
 
 
+    bool is_atomic;  // true if atomic interfaces are supported.
 };
 };
 
 
 struct SDL_DisplayModeData
 struct SDL_DisplayModeData
@@ -101,8 +126,11 @@ struct SDL_DisplayModeData
 
 
 struct SDL_DisplayData
 struct SDL_DisplayData
 {
 {
-    drmModeConnector *connector;
-    drmModeCrtc *crtc;
+    KMSDRM_plane *display_plane;
+    KMSDRM_plane *cursor_plane;
+    KMSDRM_crtc crtc;
+    KMSDRM_connector connector;
+
     drmModeModeInfo mode;
     drmModeModeInfo mode;
     drmModeModeInfo original_mode;
     drmModeModeInfo original_mode;
     drmModeModeInfo fullscreen_mode;
     drmModeModeInfo fullscreen_mode;
@@ -118,6 +146,15 @@ struct SDL_DisplayData
     int cursor_bo_drm_fd;
     int cursor_bo_drm_fd;
     uint64_t cursor_w, cursor_h;
     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;
     bool default_cursor_init;
 };
 };
 
 
@@ -137,6 +174,9 @@ struct SDL_WindowData
 
 
     EGLSurface egl_surface;
     EGLSurface egl_surface;
     bool egl_surface_dirty;
     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
 typedef struct KMSDRM_FBInfo
@@ -145,12 +185,37 @@ typedef struct KMSDRM_FBInfo
     uint32_t fb_id; // DRM framebuffer ID
     uint32_t fb_id; // DRM framebuffer ID
 } KMSDRM_FBInfo;
 } 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
 // Helper functions
 extern bool KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window);
 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_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 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);
 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
 // 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 display_count;
     uint32_t mode_count;
     uint32_t mode_count;
     uint32_t plane_count;
     uint32_t plane_count;
-    uint32_t plane = UINT32_MAX;
+    uint32_t _plane = UINT32_MAX;
 
 
     VkPhysicalDevice *physical_devices = NULL;
     VkPhysicalDevice *physical_devices = NULL;
     VkPhysicalDeviceProperties *device_props = NULL;
     VkPhysicalDeviceProperties *device_props = NULL;
@@ -456,13 +456,13 @@ bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
         vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
         vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
         if (plane_caps.supportedAlpha == alpha_mode) {
         if (plane_caps.supportedAlpha == alpha_mode) {
             // Yep, this plane is alright.
             // Yep, this plane is alright.
-            plane = i;
+            _plane = i;
             break;
             break;
         }
         }
     }
     }
 
 
     // If we couldn't find an appropriate plane, error out.
     // 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.");
         SDL_SetError("Vulkan couldn't find an appropriate plane.");
         goto clean;
         goto clean;
     }
     }
@@ -477,7 +477,7 @@ bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this,
     SDL_zero(display_plane_surface_create_info);
     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.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
     display_plane_surface_create_info.displayMode = display_mode;
     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.imageExtent = image_size;
     display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
     display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
     display_plane_surface_create_info.alphaMode = alpha_mode;
     display_plane_surface_create_info.alphaMode = alpha_mode;