[KMS/DRM] Refactor, improve and re-comment async pageflips code.

main
Manuel Alfayate Corchete 2021-01-13 20:11:01 +01:00
parent fbd7c718b6
commit ead3c406a2
2 changed files with 48 additions and 21 deletions

View File

@ -154,13 +154,14 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
/* Issue pageflip on the next front buffer. /* Issue pageflip on the next front buffer.
Remember: drmModePageFlip() never blocks, it just issues the flip, Remember: drmModePageFlip() never blocks, it just issues the flip,
which will be done during the next vblank. which will be done during the next vblank, or immediately if
Since it will return EBUSY if we call it again without having we pass the DRM_MODE_PAGE_FLIP_ASYNC flag.
completed the last issued flip, we must pass the Since calling drmModePageFlip() will return EBUSY if we call it
without having completed the last issued flip, we must pass the
DRM_MODE_PAGE_FLIP_ASYNC if we don't block on EGL (egl_swapinterval = 0). DRM_MODE_PAGE_FLIP_ASYNC if we don't block on EGL (egl_swapinterval = 0).
That makes it flip immediately, without waiting for the next vblank, That makes it flip immediately, without waiting for the next vblank
so even if we don't block on EGL, it will have flipped when we to do so, so even if we don't block on EGL, the flip will have completed
get back here. */ when we get here again. */
if (_this->egl_data->egl_swapinterval == 0) { if (_this->egl_data->egl_swapinterval == 0) {
flip_flags |= DRM_MODE_PAGE_FLIP_ASYNC; flip_flags |= DRM_MODE_PAGE_FLIP_ASYNC;
@ -173,11 +174,15 @@ KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
windata->waiting_for_flip = SDL_TRUE; windata->waiting_for_flip = SDL_TRUE;
} else { } else {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not queue pageflip: %d", ret); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not queue pageflip: %d", ret);
printf("Could not queue pageflip: %s\n", strerror(errno));
} }
/* If we are in double-buffer mode, wait immediately for vsync /* Wait immediately for vsync (as if we only had two buffers).
(as if we only had two buffers), Even if we are already doing a WaitPageFlip at the begining of this
function, this is NOT redundant because here we wait immediately
after submitting the image to the screen, reducing lag, and if
we have waited here, there won't be a pending pageflip so the
WaitPageFlip at the beggining of this function will be a no-op.
Just leave it here and don't worry.
Run your SDL2 program with "SDL_KMSDRM_DOUBLE_BUFFER=1 <program_name>" Run your SDL2 program with "SDL_KMSDRM_DOUBLE_BUFFER=1 <program_name>"
to enable this. */ to enable this. */
if (_this->egl_data->egl_swapinterval == 1 && windata->double_buffer) { if (_this->egl_data->egl_swapinterval == 1 && windata->double_buffer) {

View File

@ -45,8 +45,9 @@
#include "SDL_kmsdrmvulkan.h" #include "SDL_kmsdrmvulkan.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h>
#include <poll.h> #include <poll.h>
#include <time.h>
#include <errno.h>
#ifdef __OpenBSD__ #ifdef __OpenBSD__
#define KMSDRM_DRI_PATH "/dev/" #define KMSDRM_DRI_PATH "/dev/"
@ -340,42 +341,63 @@ KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int us
SDL_bool SDL_bool
KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata) { KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata) {
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata); SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmEventContext ev = {0}; drmEventContext ev = {0};
struct pollfd pfd = {0}; struct pollfd pfd = {0};
/* If the pageflip hasn't completed after 10 seconds, it nevel will. */
uint32_t timeout = 10000;
ev.version = DRM_EVENT_CONTEXT_VERSION; ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = KMSDRM_FlipHandler; ev.page_flip_handler = KMSDRM_FlipHandler;
pfd.fd = viddata->drm_fd; pfd.fd = viddata->drm_fd;
pfd.events = POLLIN; pfd.events = POLLIN;
/* Stay on loop until we get the desired event
We need the while the loop because we could be in a situation where:
-We get events on the FD in time, thus not on exiting on return number 1.
-These events are not errors, thus not exiting on return number 2.
-These events are of POLLIN type, thus not exiting on return number 3,
but if the event is not the pageflip we are waiting for, we arrive at the end
of the loop and do loop re-entry, hoping the next event will be the pageflip.
So we could erroneously exit the function without the pageflip event to arrive
if it wasn't for the while loop!
Vblank events, for example, hit the FD and they are POLLIN events too (POLLIN
means "there's data to read on the FD"), but they are not the pageflip event
we are waiting for, so the drmEventHandle() doesn't run the flip handler, and
since waiting_for_flip is set on the pageflip handle, it's not set and we stay
on the loop.
*/
while (windata->waiting_for_flip) { while (windata->waiting_for_flip) {
pfd.revents = 0;
pfd.revents = 0;
/* poll() waits for events arriving on the FD, and returns < 0 if timeout /* poll() waits for events arriving on the FD, and returns < 0 if timeout
passes with no events. */ passes with no events.
if (poll(&pfd, 1, timeout) < 0) { We wait forever (timeout = -1), but even if we DO get an event,
we have yet to see if it's of the required type. */
if (poll(&pfd, 1, -1) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error"); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error");
return SDL_FALSE; return SDL_FALSE; /* Return number 1. */
} }
if (pfd.revents & (POLLHUP | POLLERR)) { if (pfd.revents & (POLLHUP | POLLERR)) {
/* An event arrived on the FD in time, but it's an error. */ /* An event arrived on the FD in time, but it's an error. */
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error"); SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
return SDL_FALSE; return SDL_FALSE; /* Return number 2. */
} }
if (pfd.revents & POLLIN) { if (pfd.revents & POLLIN) {
/* There is data to read on the FD! /* There is data to read on the FD!
Is the event a pageflip? If so, drmHandleEvent will Is the event a pageflip? We know it is a pageflip if it matches the
unset windata->waiting_for_flip */ event we are passing in &ev. If it is, drmHandleEvent() will unset
windata->waiting_for_flip and we will get out of the "while" loop.
If it's not, we keep iterating on the loop. */
KMSDRM_drmHandleEvent(viddata->drm_fd, &ev); KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
} else { } else {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Dropping frame while waiting_for_flip"); SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Dropping frame while waiting_for_flip");
return SDL_FALSE; return SDL_FALSE; /* Return number 3. */
} }
} }