kmsdrm: Restore atomic support.

This commit is contained in:
Ryan C. Gordon
2024-11-20 20:05:54 -05:00
parent e4c60c05bf
commit 1fe926769c
7 changed files with 1109 additions and 82 deletions
+22
View File
@@ -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
View File
@@ -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 (viddata->is_atomic) {
if (rc < 0) { if (dispdata->cursor_plane) {
result = SDL_SetError("drmModeSetCursor() failed: %s", strerror(-rc)); 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. if (viddata->is_atomic) {
bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32; // Get the fb_id for the GBM BO so we can show it on the cursor plane.
if (curdata->hot_x == 0 && curdata->hot_y == 0) { KMSDRM_FBInfo *fb = KMSDRM_FBFromBO(video_device, dispdata->cursor_bo);
rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc->crtc_id, KMSDRM_PlaneInfo info;
bo_handle, dispdata->cursor_w, dispdata->cursor_h);
// 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, // Put the GBM BO buffer on screen using the DRM interface.
bo_handle, dispdata->cursor_w, dispdata->cursor_h, curdata->hot_x, curdata->hot_y); bo_handle = KMSDRM_gbm_bo_get_handle(dispdata->cursor_bo).u32;
} if (curdata->hot_x == 0 && curdata->hot_y == 0) {
if (rc < 0) { rc = KMSDRM_drmModeSetCursor(viddata->drm_fd, dispdata->crtc.crtc->crtc_id, bo_handle, dispdata->cursor_w, dispdata->cursor_h);
result = SDL_SetError("Failed to set DRM cursor: %s", strerror(-rc)); } else {
goto cleanup; 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); SDL_VideoDevice *dev = SDL_GetVideoDevice();
if (rc < 0) { SDL_VideoData *viddata = dev->internal;
return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc)); 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 (viddata->is_atomic) {
if (rc < 0) { /* !!! FIXME: Some programs expect cursor movement even while they don't do SwapWindow() calls,
return SDL_SetError("drmModeMoveCursor() failed: %s", strerror(-rc)); 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
View File
@@ -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->crtc.crtc->crtc_id, fb_info->fb_id, 0, 0,
&dispdata->connector->connector_id, 1, &dispdata->mode); &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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+67 -2
View File
@@ -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 <redwindwanderer@gmail.com>
#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; KMSDRM_plane *display_plane;
drmModeCrtc *crtc; 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
View File
@@ -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;