From 1fe926769c02b934b044e42c4fd43bec0fc6662c Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 20 Nov 2024 20:05:54 -0500
Subject: [PATCH] kmsdrm: Restore atomic support.
---
include/SDL3/SDL_hints.h | 22 +
src/video/kmsdrm/SDL_kmsdrmmouse.c | 137 ++++--
src/video/kmsdrm/SDL_kmsdrmopengles.c | 330 +++++++++++++-
src/video/kmsdrm/SDL_kmsdrmsym.h | 10 +
src/video/kmsdrm/SDL_kmsdrmvideo.c | 615 ++++++++++++++++++++++++--
src/video/kmsdrm/SDL_kmsdrmvideo.h | 69 ++-
src/video/kmsdrm/SDL_kmsdrmvulkan.c | 8 +-
7 files changed, 1109 insertions(+), 82 deletions(-)
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index c4ffae77a4c60..fa477bbc2780e 100644
--- a/include/SDL3/SDL_hints.h
+++ b/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.
*
diff --git a/src/video/kmsdrm/SDL_kmsdrmmouse.c b/src/video/kmsdrm/SDL_kmsdrmmouse.c
index 383d7d43a7fec..f72bd3ae6a2c7 100644
--- a/src/video/kmsdrm/SDL_kmsdrmmouse.c
+++ b/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;
diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c
index cc93cde9c0359..f098cf5b1d559 100644
--- a/src/video/kmsdrm/SDL_kmsdrmopengles.c
+++ b/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)
#en
(Patch may be truncated, please check the link at the top of this post.)