From 8996ee1786644236113b3ea146835b2b5d408f14 Mon Sep 17 00:00:00 2001 From: Manuel Alfayate Corchete Date: Mon, 3 Aug 2020 22:24:49 +0200 Subject: [PATCH] kmsdrm: update SwapWindow fn, moving it to triple-buffer. --- src/video/kmsdrm/SDL_kmsdrmopengles.c | 207 ++++++++++++++++++++++++-- src/video/kmsdrm/SDL_kmsdrmvideo.c | 5 +- src/video/kmsdrm/SDL_kmsdrmvideo.h | 5 + 3 files changed, 205 insertions(+), 12 deletions(-) diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c index 34bcb8ccc..e4274a91a 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 417398a8d..fa2772bd3 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 897451bdb..e69fe176e 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.h +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.h @@ -34,6 +34,7 @@ #include #if SDL_VIDEO_OPENGL_EGL #include +#include #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;