Commit 8996ee1786644236113b3ea146835b2b5d408f14

Manuel Alfayate Corchete 2020-08-03T22:24:49

kmsdrm: update SwapWindow fn, moving it to triple-buffer.

diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c
index 34bcb8c..e4274a9 100644
--- a/src/video/kmsdrm/SDL_kmsdrmopengles.c
+++ b/src/video/kmsdrm/SDL_kmsdrmopengles.c
@@ -73,8 +73,21 @@ static EGLSyncKHR create_fence(int fd, _THIS)
 	return fence;
 }
 
+/*
+-We have to stop the GPU from executing the cmdstream again because the front butter
+ has been marked as back buffer so it can be selected by EGL to draw on it BUT
+ the pageflip has not completed, so it's still on screen, so letting GPU render on it
+ would cause tearing. (kms_fence)
+-We have to stop the DISPLAY from doing the changes we requested in the atomic ioctl,
+ like the pageflip, until the GPU has completed the cmdstream execution,
+ because we don't want the pageflip to be done in the middle of a frame rendering.
+ (gpu_fence).
+-We have to stop the program from doing a new atomic ioctl until the previous one
+has been finished. (kms_fence)
+*/
+
 int
-KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
+KMSDRM_GLES_SwapWindowDOUBLE(_THIS, SDL_Window * window) {
 
     SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
     SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
@@ -85,13 +98,13 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
     
     uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
 
-    EGLSyncKHR display_in_fence  = NULL;
-    EGLSyncKHR display_out_fence = NULL;
+    EGLSyncKHR kms_in_fence  = NULL;
+    EGLSyncKHR kms_out_fence = NULL;
 
     /* Create the display in-fence, and export it as a fence fd to pass into the kernel. */
-    display_in_fence = create_fence(EGL_NO_NATIVE_FENCE_FD_ANDROID, _this);
-    assert(display_in_fence);
-    dispdata->kms_in_fence_fd = _this->egl_data->eglDupNativeFenceFDANDROID(_this->egl_data->egl_display, display_in_fence);
+    kms_in_fence = create_fence(EGL_NO_NATIVE_FENCE_FD_ANDROID, _this);
+    assert(kms_in_fence);
+    dispdata->kms_in_fence_fd = _this->egl_data->eglDupNativeFenceFDANDROID(_this->egl_data->egl_display, kms_in_fence);
     
     /* Mark the back buffer as the next front buffer, and the current front buffer as elegible
        by EGL as back buffer to draw into.
@@ -124,18 +137,18 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
     }
 
     /* Get the display out fence, returned by the atomic ioctl. */
-    display_out_fence = create_fence(dispdata->kms_out_fence_fd, _this);
-    assert(display_out_fence);
+    kms_out_fence = create_fence(dispdata->kms_out_fence_fd, _this);
+    assert(kms_out_fence);
 
     /* Wait on the CPU side for the _previous_ commit to complete before we post the flip through KMS,
      * because atomic will reject the commit if we post a new one while the previous one is still pending. */
     do {
-	status = _this->egl_data->eglClientWaitSyncKHR(_this->egl_data->egl_display, display_out_fence, 0, EGL_FOREVER_KHR);
+	status = _this->egl_data->eglClientWaitSyncKHR(_this->egl_data->egl_display, kms_out_fence, 0, EGL_FOREVER_KHR);
     } while (status != EGL_CONDITION_SATISFIED_KHR);
 
     /* Destroy both in and out display fences. */
-    _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, display_out_fence);
-    _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, display_in_fence);
+    _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, kms_out_fence);
+    _this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, kms_in_fence);
 
     /* Now that the pageflip is complete, release last front buffer so EGL can chose it
      * as back buffer and render on it again: */
@@ -150,6 +163,178 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
     return ret;
 }
 
+
+int
+KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
+
+    SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
+    SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
+    KMSDRM_FBInfo *fb;
+    int ret;
+
+    uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
+
+    /* Create the fence that will be inserted in the 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(EGL_NO_NATIVE_FENCE_FD_ANDROID, _this);
+    assert(dispdata->gpu_fence);
+
+    _this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface);
+
+    /* It's safe to get the gpu_fence FD now, because eglSwapBuffers flushes it
+       down the cmdstream, so it's now in place in the cmdstream.
+       Atomic ioctl will pass the in-fence fd into the kernel. */
+    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);
+    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. */
+    windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
+    if (!windata->next_bo) {
+	printf("Failed to lock frontbuffer\n");
+	return -1;
+    }
+    fb = KMSDRM_FBFromBO(_this, windata->next_bo);
+    if (!fb) {
+	 printf("Failed to get a new framebuffer BO\n");
+	 return -1;
+    }
+
+
+    /* Don't issue another atomic ioctl until previous one has completed. */
+    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);
+    }
+
+
+    /* Issue atomic commit. */
+    ret = drm_atomic_commit(_this, fb->fb_id, flags);
+    if (ret) {
+	printf("failed to do atomic commit\n");
+	return -1;
+    }
+
+
+
+    /* release last front buffer so EGL can chose it as back buffer and render on it again: */
+    if (windata->curr_bo) {
+	KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
+	windata->curr_bo = NULL;
+    }
+
+    /* Take note of the current front buffer, so it can be freed next time this function is called. */
+    windata->curr_bo = windata->next_bo;
+
+    /* Import out fence from the out fence fd and tell the GPU to wait on it
+       until the requested pageflip has completed. */
+    dispdata->kms_fence = create_fence(dispdata->kms_out_fence_fd, _this);
+    assert(dispdata->kms_fence);
+
+    dispdata->kms_out_fence_fd = -1;
+
+    _this->egl_data->eglWaitSyncKHR(_this->egl_data->egl_display, dispdata->kms_fence, 0);
+
+    return ret;
+
+}
+
+int
+KMSDRM_GLES_SwapWindowOLD(_THIS, SDL_Window * window) {
+
+    SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
+    SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
+    KMSDRM_FBInfo *fb;
+    int ret;
+
+    uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
+
+    dispdata->kms_fence = NULL;   /* in-fence to gpu, out-fence from kms */
+    dispdata->gpu_fence = NULL;   /* in-fence to kms, out-fence from gpu, */
+
+    /* Allow modeset (which is done inside atomic_commit). */
+    flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+
+
+    /* Import out fence from the out fence fd and tell the GPU to wait on it
+       until the requested pageflip has completed. */
+    dispdata->kms_fence = create_fence(dispdata->kms_out_fence_fd, _this);
+    assert(dispdata->kms_fence);
+
+    dispdata->kms_out_fence_fd = -1;
+
+
+    _this->egl_data->eglWaitSyncKHR(_this->egl_data->egl_display, dispdata->kms_fence, 0);
+
+
+    /* GL DRAW */
+
+    /* Create the gpu fence here so it's inserted in the cmdstream exactly
+       at the end of the gl commands that form a frame. */
+    dispdata->gpu_fence = create_fence(EGL_NO_NATIVE_FENCE_FD_ANDROID, _this);
+    assert(dispdata->gpu_fence);
+
+    _this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface);
+
+    /* It's safe to get the gpu_fence fd now, because eglSwapBuffers() flushes the fd. */
+    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);
+    assert(dispdata->kms_in_fence_fd != -1);
+
+    windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
+    if (!windata->next_bo) {
+	printf("Failed to lock frontbuffer\n");
+	return -1;
+    }
+    fb = KMSDRM_FBFromBO(_this, windata->next_bo);
+    if (!fb) {
+	 printf("Failed to get a new framebuffer BO\n");
+	 return -1;
+    }
+
+    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);
+    }
+
+
+    /* Issue atomic commit. */
+    ret = drm_atomic_commit(_this, fb->fb_id, flags);
+    if (ret) {
+	printf("failed to do atomic commit\n");
+	return -1;
+    }
+
+
+
+    /* release last front buffer so EGL can chose it as back buffer and render on it again: */
+    if (windata->curr_bo) {
+	KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
+	windata->curr_bo = NULL;
+    }
+
+    /* Take note of the current front buffer, so it can be freed next time this function is called. */
+    windata->curr_bo = windata->next_bo;
+
+    /* Allow a modeset change for the first commit only. */
+    flags &= ~(DRM_MODE_ATOMIC_ALLOW_MODESET);
+
+
+    return ret;
+}
+
 /***************************************/
 /* End of Atomic functions block       */
 /***************************************/
diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c
index 417398a..fa2772b 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvideo.c
+++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c
@@ -874,8 +874,11 @@ KMSDRM_VideoInit(_THIS)
 	}
     }
 
-    /* Initialize the fence fd: */
+    /* Initialize the fences and their fds: */
+    dispdata->kms_fence = NULL;
+    dispdata->gpu_fence = NULL;
     dispdata->kms_out_fence_fd = -1,
+    dispdata->kms_in_fence_fd  = -1,
 
     /*********************/
     /* Atomic block ends */
diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.h b/src/video/kmsdrm/SDL_kmsdrmvideo.h
index 897451b..e69fe17 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvideo.h
+++ b/src/video/kmsdrm/SDL_kmsdrmvideo.h
@@ -34,6 +34,7 @@
 #include <assert.h>
 #if SDL_VIDEO_OPENGL_EGL
 #include <EGL/egl.h>
+#include <EGL/eglext.h>
 #endif
 
 typedef struct SDL_VideoData
@@ -75,9 +76,13 @@ typedef struct SDL_DisplayData
     drmModePropertyRes **connector_props_info;
 
     int crtc_index;
+
     int kms_in_fence_fd;
     int kms_out_fence_fd;
 
+    EGLSyncKHR kms_fence; /* Signaled when kms completes changes requested in atomic iotcl (pageflip, etc). */
+    EGLSyncKHR gpu_fence; /* Signaled when GPU rendering is done. */
+
 } SDL_DisplayData;