kmsdrm: Moved to the ATOMIC KMS/DRM interface for buffer swapping, leaving DRM-legacy behind.

This commit is contained in:
Manuel Alfayate Corchete 2020-07-28 21:11:25 +02:00
parent e410b34f92
commit 1a5503ce9f
6 changed files with 619 additions and 253 deletions

View file

@ -437,6 +437,13 @@ SDL_EGL_LoadLibraryOnly(_THIS, const char *egl_path)
LOAD_FUNC(eglGetError);
LOAD_FUNC_EGLEXT(eglQueryDevicesEXT);
LOAD_FUNC_EGLEXT(eglGetPlatformDisplayEXT);
/* Atomic functions */
LOAD_FUNC_EGLEXT(eglCreateSyncKHR);
LOAD_FUNC_EGLEXT(eglDestroySyncKHR);
LOAD_FUNC_EGLEXT(eglDupNativeFenceFDANDROID);
LOAD_FUNC_EGLEXT(eglWaitSyncKHR);
LOAD_FUNC_EGLEXT(eglClientWaitSyncKHR);
/* Atomic functions end */
if (path) {
SDL_strlcpy(_this->gl_config.driver_path, path, sizeof(_this->gl_config.driver_path) - 1);

View file

@ -101,6 +101,21 @@ typedef struct SDL_EGL_VideoData
void **devices,
EGLint *num_devices);
/* Atomic functions */
EGLSyncKHR(EGLAPIENTRY *eglCreateSyncKHR)(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list);
EGLBoolean(EGLAPIENTRY *eglDestroySyncKHR)(EGLDisplay dpy, EGLSyncKHR sync);
EGLint(EGLAPIENTRY *eglDupNativeFenceFDANDROID)(EGLDisplay dpy, EGLSyncKHR sync);
EGLint(EGLAPIENTRY *eglWaitSyncKHR)(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags);
EGLint(EGLAPIENTRY *eglClientWaitSyncKHR)(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout);
/* Atomic functions end */
/* whether EGL display was offscreen */
int is_offscreen;

View file

@ -55,178 +55,126 @@ int KMSDRM_GLES_SetSwapInterval(_THIS, int interval) {
return 0;
}
/*********************************/
/* Atomic functions block */
/*********************************/
#define VOID2U64(x) ((uint64_t)(unsigned long)(x))
static EGLSyncKHR create_fence(int fd, _THIS)
{
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);
assert(fence);
return fence;
}
int
KMSDRM_GLES_SwapWindow(_THIS, SDL_Window * window) {
SDL_WindowData *windata = ((SDL_WindowData *) window->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *) SDL_GetDisplayForWindow(window)->driverdata;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
KMSDRM_FBInfo *fb_info;
KMSDRM_FBInfo *fb;
int ret;
/* ALWAYS wait for each pageflip to complete before issuing another, vsync or not,
or drmModePageFlip() will start returning EBUSY if there are pending pageflips.
uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK;
To disable vsync in games, it would be needed to issue async pageflips,
and then wait for each pageflip to complete. Since async pageflips complete ASAP
instead of VBLANK, thats how non-vsync screen updates should wok.
EGLSyncKHR gpu_fence = NULL; /* out-fence from gpu, in-fence to kms */
EGLSyncKHR kms_fence = NULL; /* in-fence to gpu, out-fence from kms */
BUT Async pageflips do not work right now because calling drmModePageFlip() with the
DRM_MODE_PAGE_FLIP_ASYNC flag returns error on every driver I have tried.
/* Allow modeset (which is done inside atomic_commit). */
flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
So, for now, only do vsynced updates: _this->egl_data->egl_swapinterval is
ignored for now, it makes no sense to use it until async pageflips work on drm drivers. */
if (dispdata->kms_out_fence_fd != -1) {
kms_fence = create_fence(dispdata->kms_out_fence_fd, _this);
assert(kms_fence);
/* Recreate the GBM / EGL surfaces if the display mode has changed */
if (windata->egl_surface_dirty) {
KMSDRM_CreateSurfaces(_this, window);
/* driver now has ownership of the fence fd: */
dispdata->kms_out_fence_fd = -1;
/* wait "on the gpu" (ie. this won't necessarily block, but
* will block the rendering until fence is signaled), until
* the previous pageflip completes so we don't render into
* the buffer that is still on screen.
*/
_this->egl_data->eglWaitSyncKHR(_this->egl_data->egl_display, kms_fence, 0);
}
if (windata->double_buffer) {
/* Use a double buffering scheme, independently of the number of buffers that the GBM surface has,
(number of buffers on the GBM surface depends on the implementation).
Double buffering (instead of triple) is achieved by waiting for synchronous pageflip to complete
inmediately after the pageflip is issued. That way, in the end of this function only two buffers
are needed: a buffer that is available to be picked again by EGL as a backbuffer to draw on it,
and the new front buffer that has just been set up.
/* insert fence to be singled in cmdstream.. this fence will be
* signaled when gpu rendering done
*/
gpu_fence = create_fence(EGL_NO_NATIVE_FENCE_FD_ANDROID, _this);
assert(gpu_fence);
Since programmer has no control over the number of buffers of the GBM surface, wait for pageflip
is done inmediately after issuing pageflip, and so a double-buffer scheme is achieved. */
_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface);
/* Ask EGL to mark the current back buffer to become the next front buffer.
That will happen when a pageflip is issued, and the next vsync arrives (sync flip)
or ASAP (async flip). */
if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed.");
return 0;
}
/* after swapbuffers, gpu_fence should be flushed, so safe to get the fd: */
dispdata->kms_in_fence_fd = _this->egl_data->eglDupNativeFenceFDANDROID(_this->egl_data->egl_display, gpu_fence);
_this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, gpu_fence);
assert(dispdata->kms_in_fence_fd != -1);
/* Get a handler to the buffer that is marked to become the next front buffer, and lock it
so it can not be chosen by EGL as a back buffer. */
windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
if (!windata->next_bo) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer");
return 0;
/* } else {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)windata->next_bo); */
}
/* Issue synchronous pageflip: drmModePageFlip() NEVER blocks, synchronous here means that it
will be done on next VBLANK, not ASAP. And return to program loop inmediately. */
fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
if (!fb_info) {
return 0;
}
/* When needed, this is done once we have the needed fb_id, not before. */
if (windata->crtc_setup_pending) {
if (KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id, 0,
0, &dispdata->conn->connector_id, 1, &dispdata->mode)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not configure CRTC on video mode setting.");
}
windata->crtc_setup_pending = SDL_FALSE;
}
if (!KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip)) {
windata->waiting_for_flip = SDL_TRUE;
} else {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not issue pageflip");
}
/* Since issued pageflips are always synchronous (ASYNC dont currently work), these pageflips
will happen at next vsync, so in practice waiting for vsync is being done here. */
if (!KMSDRM_WaitPageFlip(_this, windata, -1)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Error waiting for pageflip event");
return 0;
}
/* Return the previous front buffer to the available buffer pool of the GBM surface,
so it can be chosen again by EGL as the back buffer for drawing into it. */
if (windata->curr_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface buffer %p", (void *)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;
} else {
/* Triple buffering requires waiting for last pageflip upon entering instead of waiting at the end,
and issuing the next pageflip at the end, thus allowing the program loop to run
while the issued pageflip arrives (at next VBLANK, since ONLY synchronous pageflips are possible).
In a game context, this means that the player can be doing inputs before seeing the last
completed frame, causing "input lag" that is known to plage other APIs and backends.
Triple buffering requires the use of three different buffers at the end of this function:
1- the front buffer which is on screen,
2- the back buffer wich is ready to be flipped (a pageflip has been issued on it, which has yet to complete)
3- a third buffer that can be used by EGL to draw while the previously issued pageflip arrives
(should not put back the previous front buffer into the free buffers pool of the
GBM surface until that happens).
If the implementation only has two buffers for the GBM surface, this would behave like a double buffer.
*/
/* Wait for previously issued pageflip to complete. */
if (!KMSDRM_WaitPageFlip(_this, windata, -1)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Error waiting for pageflip event");
return 0;
}
/* Free the previous front buffer so EGL can pick it again as back buffer.*/
if (windata->curr_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Released GBM surface buffer %p", (void *)windata->curr_bo); */
windata->curr_bo = NULL;
}
/* Ask EGL to mark the current back buffer to become the next front buffer.
That will happen when a pageflip is issued, and the next vsync arrives (sync flip)
or ASAP (async flip). */
if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, windata->egl_surface))) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed.");
return 0;
}
/* Take note of the current front buffer, so it can be freed next time this function is called. */
windata->curr_bo = windata->next_bo;
/* Get a handler to the buffer that is marked to become the next front buffer, and lock it
so it can not be chosen by EGL as a back buffer. */
windata->next_bo = KMSDRM_gbm_surface_lock_front_buffer(windata->gs);
if (!windata->next_bo) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not lock GBM surface front buffer");
return 0;
/* } else {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Locked GBM surface %p", (void *)windata->next_bo); */
}
/* Issue synchronous pageflip: drmModePageFlip() NEVER blocks, synchronous here means that it
will be done on next VBLANK, not ASAP. And return to program loop inmediately. */
fb_info = KMSDRM_FBFromBO(_this, windata->next_bo);
if (!fb_info) {
return 0;
}
/* When needed, this is done once we have the needed fb_id, not before. */
if (windata->crtc_setup_pending) {
if (KMSDRM_drmModeSetCrtc(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id, 0,
0, &dispdata->conn->connector_id, 1, &dispdata->mode)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not configure CRTC on video mode setting.");
}
windata->crtc_setup_pending = SDL_FALSE;
}
if (!KMSDRM_drmModePageFlip(viddata->drm_fd, dispdata->crtc_id, fb_info->fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &windata->waiting_for_flip)) {
windata->waiting_for_flip = SDL_TRUE;
} else {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "Could not issue pageflip");
}
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;
}
return 0;
if (kms_fence) {
EGLint status;
/* Wait on the CPU side for the _previous_ commit to
* complete before we post the flip through KMS, as
* atomic will reject the commit if we post a new one
* whilst the previous one is still pending.
*/
do {
status = _this->egl_data->eglClientWaitSyncKHR(_this->egl_data->egl_display,
kms_fence,
0,
EGL_FOREVER_KHR);
} while (status != EGL_CONDITION_SATISFIED_KHR);
_this->egl_data->eglDestroySyncKHR(_this->egl_data->egl_display, kms_fence);
}
/*
* Here you could also update drm plane layers if you want
* hw composition
*/
ret = drm_atomic_commit(_this, fb->fb_id, flags);
if (ret) {
printf("failed to do atomic commit\n");
return -1;
}
/* release last buffer to render on 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 */
/***************************************/
SDL_EGL_MakeCurrent_impl(KMSDRM)
#endif /* SDL_VIDEO_DRIVER_KMSDRM && SDL_VIDEO_OPENGL_EGL */

View file

@ -64,6 +64,27 @@ SDL_KMSDRM_SYM(int,drmModePageFlip,(int fd, uint32_t crtc_id, uint32_t fb_id,
uint32_t flags, void *user_data))
/* Atomic functions */
SDL_KMSDRM_SYM(int,drmSetClientCap,(int fd, uint64_t capability, uint64_t value))
SDL_KMSDRM_SYM(drmModePlaneResPtr,drmModeGetPlaneResources,(int fd))
SDL_KMSDRM_SYM(drmModePlanePtr,drmModeGetPlane,(int fd, uint32_t plane_id))
SDL_KMSDRM_SYM(drmModeObjectPropertiesPtr,drmModeObjectGetProperties,(int fd,uint32_t object_id,uint32_t object_type))
SDL_KMSDRM_SYM(drmModePropertyPtr,drmModeGetProperty,(int fd, uint32_t propertyId))
SDL_KMSDRM_SYM(void,drmModeFreeProperty,(drmModePropertyPtr ptr))
SDL_KMSDRM_SYM(void,drmModeFreeObjectProperties,(drmModeObjectPropertiesPtr ptr))
SDL_KMSDRM_SYM(void,drmModeFreePlane,(drmModePlanePtr ptr))
SDL_KMSDRM_SYM(void,drmModeFreePlaneResources,(drmModePlaneResPtr ptr))
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))
/* End of atomic fns */
SDL_KMSDRM_MODULE(GBM)
SDL_KMSDRM_SYM(int,gbm_device_get_fd,(struct gbm_device *gbm))
SDL_KMSDRM_SYM(int,gbm_device_is_format_supported,(struct gbm_device *gbm,

View file

@ -144,6 +144,320 @@ get_driindex(void)
return -ENOENT;
}
/*********************************/
/* Atomic helper functions block */
/*********************************/
#define VOID2U64(x) ((uint64_t)(unsigned long)(x))
static int add_connector_property(drmModeAtomicReq *req, uint32_t obj_id,
const char *name, uint64_t value)
{
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
unsigned int i;
int prop_id = 0;
for (i = 0 ; i < dispdata->connector_props->count_props ; i++) {
if (strcmp(dispdata->connector_props_info[i]->name, name) == 0) {
prop_id = dispdata->connector_props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
printf("no connector property: %s\n", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, obj_id, prop_id, value);
}
static int add_crtc_property(drmModeAtomicReq *req, uint32_t obj_id,
const char *name, uint64_t value)
{
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
unsigned int i;
int prop_id = -1;
for (i = 0 ; i < dispdata->crtc_props->count_props ; i++) {
if (strcmp(dispdata->crtc_props_info[i]->name, name) == 0) {
prop_id = dispdata->crtc_props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
printf("no crtc property: %s\n", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, obj_id, prop_id, value);
}
static int add_plane_property(drmModeAtomicReq *req, uint32_t obj_id,
const char *name, uint64_t value)
{
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
unsigned int i;
int prop_id = -1;
for (i = 0 ; i < dispdata->plane_props->count_props ; i++) {
if (strcmp(dispdata->plane_props_info[i]->name, name) == 0) {
prop_id = dispdata->plane_props_info[i]->prop_id;
break;
}
}
if (prop_id < 0) {
printf("no plane property: %s\n", name);
return -EINVAL;
}
return KMSDRM_drmModeAtomicAddProperty(req, obj_id, prop_id, value);
}
/*static void get_plane_properties() {
uint32_t i;
dispdata->plane_ = drmModeObjectGetProperties(viddata->drm_fd,
plane->plane_id, DRM_MODE_OBJECT_PLANE);
if (!dispdata->type.props) {
printf("could not get %s %u properties: %s\n",
#type, id, strerror(errno));
return NULL;
}
dispdata->type->props_info = calloc(dispdata->type.props->count_props,
sizeof(*dispdata->type->props_info));
for (i = 0; i < dispdata->type->props->count_props; i++) {
dispdata->type.props_info[i] = drmModeGetProperty(viddata->drm_fd,
dispdata->type->props->props[i]);
}
return props;
}*/
void print_plane_info(_THIS, drmModePlanePtr plane)
{
char *plane_type;
drmModeRes *resources;
uint32_t type = 0;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
plane->plane_id, DRM_MODE_OBJECT_PLANE);
/* Search the plane props for the plane type. */
for (int j = 0; j < props->count_props; j++) {
drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[j]);
if ((strcmp(p->name, "type") == 0)) {
type = props->prop_values[j];
}
KMSDRM_drmModeFreeProperty(p);
}
switch (type) {
case DRM_PLANE_TYPE_OVERLAY:
plane_type = "overlay";
break;
case DRM_PLANE_TYPE_PRIMARY:
plane_type = "primary";
break;
case DRM_PLANE_TYPE_CURSOR:
plane_type = "cursor";
break;
}
/* Remember that, to present a plane on screen, it has to be connected to a CRTC so the CRTC scans it,
scales it, etc... and presents it on screen. */
/* Now we look for the CRTCs supported by the plane. */
resources = KMSDRM_drmModeGetResources(viddata->drm_fd);
if (!resources)
return;
printf("--PLANE ID: %d\nPLANE TYPE: %s\nCRTC READING THIS PLANE: %d\nCRTCS SUPPORTED BY THIS PLANE: ", plane->plane_id, plane_type, plane->crtc_id);
for (int i = 0; i < resources->count_crtcs; i++) {
if (plane->possible_crtcs & (1 << i)) {
uint32_t crtc_id = resources->crtcs[i];
printf ("%d", crtc_id);
break;
}
}
printf ("\n\n");
}
void get_planes_info(_THIS)
{
drmModePlaneResPtr plane_resources;
uint32_t i;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
if (!plane_resources) {
printf("drmModeGetPlaneResources failed: %s\n", strerror(errno));
return;
}
printf("--Number of planes found: %d-- \n", plane_resources->count_planes);
printf("--Usable CRTC that we have chosen: %d-- \n", dispdata->crtc->crtc_id);
/* Iterate on all the available planes. */
for (i = 0; (i < plane_resources->count_planes); i++) {
uint32_t plane_id = plane_resources->planes[i];
drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
if (!plane) {
printf("drmModeGetPlane(%u) failed: %s\n", plane_id, strerror(errno));
continue;
}
/* Print plane info. */
print_plane_info(_this, plane);
KMSDRM_drmModeFreePlane(plane);
}
KMSDRM_drmModeFreePlaneResources(plane_resources);
}
/* Get a plane that is PRIMARY (there's no guarantee that we have overlays in all hardware!)
and can use the CRTC we have chosen. That's all. */
int get_plane_id(_THIS)
{
drmModePlaneResPtr plane_resources;
uint32_t i, j;
int ret = -EINVAL;
int found_primary = 0;
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
plane_resources = KMSDRM_drmModeGetPlaneResources(viddata->drm_fd);
if (!plane_resources) {
printf("drmModeGetPlaneResources failed: %s\n", strerror(errno));
return -1;
}
/* Iterate on all the available planes. */
for (i = 0; (i < plane_resources->count_planes) && !found_primary; i++) {
uint32_t plane_id = plane_resources->planes[i];
drmModePlanePtr plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, plane_id);
if (!plane) {
printf("drmModeGetPlane(%u) failed: %s\n", plane_id, strerror(errno));
continue;
}
/* See if the current CRTC is available for this plane. */
if (plane->possible_crtcs & (1 << dispdata->crtc_index)) {
drmModeObjectPropertiesPtr props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd, plane_id, DRM_MODE_OBJECT_PLANE);
ret = plane_id;
/* Search the plane props, to see if it's a primary plane. */
for (j = 0; j < props->count_props; j++) {
drmModePropertyPtr p = KMSDRM_drmModeGetProperty(viddata->drm_fd, props->props[j]);
if ((strcmp(p->name, "type") == 0) &&
(props->prop_values[j] == DRM_PLANE_TYPE_PRIMARY)) {
/* found our primary plane, lets use that: */
found_primary = 1;
}
KMSDRM_drmModeFreeProperty(p);
}
KMSDRM_drmModeFreeObjectProperties(props);
}
KMSDRM_drmModeFreePlane(plane);
}
KMSDRM_drmModeFreePlaneResources(plane_resources);
return ret;
}
int drm_atomic_commit(_THIS, uint32_t fb_id, uint32_t flags)
{
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
uint32_t plane_id = dispdata->plane->plane_id;
uint32_t blob_id;
drmModeAtomicReq *req;
int ret;
req = KMSDRM_drmModeAtomicAlloc();
if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) {
if (add_connector_property(req, dispdata->connector->connector_id, "CRTC_ID",
dispdata->crtc_id) < 0)
return -1;
if (KMSDRM_drmModeCreatePropertyBlob(viddata->drm_fd, &dispdata->mode, sizeof(dispdata->mode),
&blob_id) != 0)
return -1;
if (add_crtc_property(req, dispdata->crtc_id, "MODE_ID", blob_id) < 0)
return -1;
if (add_crtc_property(req, dispdata->crtc_id, "ACTIVE", 1) < 0)
return -1;
}
add_plane_property(req, plane_id, "FB_ID", fb_id);
add_plane_property(req, plane_id, "CRTC_ID", dispdata->crtc_id);
add_plane_property(req, plane_id, "SRC_X", 0);
add_plane_property(req, plane_id, "SRC_Y", 0);
add_plane_property(req, plane_id, "SRC_W", dispdata->mode.hdisplay << 16);
add_plane_property(req, plane_id, "SRC_H", dispdata->mode.vdisplay << 16);
add_plane_property(req, plane_id, "CRTC_X", 0);
add_plane_property(req, plane_id, "CRTC_Y", 0);
add_plane_property(req, plane_id, "CRTC_W", dispdata->mode.hdisplay);
add_plane_property(req, plane_id, "CRTC_H", dispdata->mode.vdisplay);
if (dispdata->kms_in_fence_fd != -1) {
add_crtc_property(req, dispdata->crtc_id, "OUT_FENCE_PTR",
VOID2U64(&dispdata->kms_out_fence_fd));
add_plane_property(req, plane_id, "IN_FENCE_FD", dispdata->kms_in_fence_fd);
}
ret = KMSDRM_drmModeAtomicCommit(viddata->drm_fd, req, flags, NULL);
if (ret)
goto out;
if (dispdata->kms_in_fence_fd != -1) {
close(dispdata->kms_in_fence_fd);
dispdata->kms_in_fence_fd = -1;
}
out:
KMSDRM_drmModeAtomicFree(req);
return ret;
}
/***************************************/
/* End of Atomic helper functions block*/
/***************************************/
static int
KMSDRM_Available(void)
{
@ -319,60 +633,6 @@ KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo)
return fb_info;
}
static void
KMSDRM_FlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
{
/* If the data pointer received here is the same passed as the user_data in drmModePageFlip()
then this is the event handler for the pageflip that was issued on drmPageFlip(): got here
because of that precise page flip, the while loop gets broken here because of the right event.
This knowledge will allow handling different issued pageflips if sometime in the future
managing different CRTCs in SDL2 is needed, for example (synchronous pageflips happen on vblank
and vblank is a CRTC thing). */
*((SDL_bool *) data) = SDL_FALSE;
}
SDL_bool
KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata, int timeout) {
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
drmEventContext ev = {0};
struct pollfd pfd = {0};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = KMSDRM_FlipHandler;
pfd.fd = viddata->drm_fd;
pfd.events = POLLIN;
while (windata->waiting_for_flip) {
pfd.revents = 0;
if (poll(&pfd, 1, timeout) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll error");
return SDL_FALSE;
}
if (pfd.revents & (POLLHUP | POLLERR)) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "DRM poll hup or error");
return SDL_FALSE;
}
/* Is the fd readable? Thats enough to call drmHandleEvent() on it. */
if (pfd.revents & POLLIN) {
/* Page flip? ONLY if the event that made the fd readable (=POLLIN state)
is a page flip, will drmHandleEvent call page_flip_handler, which will break the loop.
The drmHandleEvent() and subsequent page_flip_handler calls are both synchronous (blocking),
nothing runs on a different thread, so no need to protect waiting_for_flip access with mutexes. */
KMSDRM_drmHandleEvent(viddata->drm_fd, &ev);
} else {
/* Timed out and page flip didn't happen */
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Dropping frame while waiting_for_flip");
return SDL_FALSE;
}
}
return SDL_TRUE;
}
/*****************************************************************************/
/* SDL Video and Display initialization/handling functions */
/* _this is a SDL_VideoDevice * */
@ -382,8 +642,6 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window * window)
{
SDL_WindowData *windata = (SDL_WindowData *)window->driverdata;
KMSDRM_WaitPageFlip(_this, windata, -1);
if (windata->curr_bo) {
KMSDRM_gbm_surface_release_buffer(windata->gs, windata->curr_bo);
windata->curr_bo = NULL;
@ -504,6 +762,7 @@ KMSDRM_VideoInit(_THIS)
goto cleanup;
}
/* Iterate on the available connectors to find a connected connector. */
for (int i = 0; i < resources->count_connectors; i++) {
drmModeConnector *conn = KMSDRM_drmModeGetConnector(viddata->drm_fd, resources->connectors[i]);
@ -514,14 +773,15 @@ KMSDRM_VideoInit(_THIS)
if (conn->connection == DRM_MODE_CONNECTED && conn->count_modes) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found connector %d with %d modes.",
conn->connector_id, conn->count_modes);
dispdata->conn = conn;
dispdata->connector = conn;
dispdata->connector_id = conn->connector_id;
break;
}
KMSDRM_drmModeFreeConnector(conn);
}
if (!dispdata->conn) {
if (!dispdata->connector) {
ret = SDL_SetError("No currently active connector found.");
goto cleanup;
}
@ -534,7 +794,7 @@ KMSDRM_VideoInit(_THIS)
continue;
}
if (encoder->encoder_id == dispdata->conn->encoder_id) {
if (encoder->encoder_id == dispdata->connector->encoder_id) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
break;
}
@ -552,13 +812,13 @@ KMSDRM_VideoInit(_THIS)
continue;
}
for (j = 0; j < dispdata->conn->count_encoders; j++) {
if (dispdata->conn->encoders[j] == encoder->encoder_id) {
for (j = 0; j < dispdata->connector->count_encoders; j++) {
if (dispdata->connector->encoders[j] == encoder->encoder_id) {
break;
}
}
if (j != dispdata->conn->count_encoders) {
if (j != dispdata->connector->count_encoders) {
break;
}
@ -575,40 +835,62 @@ KMSDRM_VideoInit(_THIS)
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Found encoder %d.", encoder->encoder_id);
/* Try to find a CRTC connected to this encoder */
dispdata->saved_crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
dispdata->crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
if (!dispdata->saved_crtc) {
/* No CRTC was connected, find the first CRTC that can be connected */
/* If no CRTC was connected to the encoder, find the first CRTC that is supported by the encoder, and use that. */
if (!dispdata->crtc) {
for (int i = 0; i < resources->count_crtcs; i++) {
if (encoder->possible_crtcs & (1 << i)) {
encoder->crtc_id = resources->crtcs[i];
dispdata->saved_crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
dispdata->crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, encoder->crtc_id);
break;
}
}
}
if (!dispdata->saved_crtc) {
if (!dispdata->crtc) {
ret = SDL_SetError("No CRTC found.");
goto cleanup;
}
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Saved crtc_id %u, fb_id %u, (%u,%u), %ux%u",
dispdata->saved_crtc->crtc_id, dispdata->saved_crtc->buffer_id, dispdata->saved_crtc->x,
dispdata->saved_crtc->y, dispdata->saved_crtc->width, dispdata->saved_crtc->height);
/* SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "Saved crtc_id %u, fb_id %u, (%u,%u), %ux%u",
dispdata->crtc->crtc_id, dispdata->crtc->buffer_id, dispdata->crtc->x,
dispdata->crtc->y, dispdata->crtc->width, dispdata->crtc->height);
*/
dispdata->crtc_id = encoder->crtc_id;
dispdata->crtc_id = dispdata->crtc->crtc_id;
dispdata->crtc = KMSDRM_drmModeGetCrtc(viddata->drm_fd, dispdata->crtc_id);
/****************/
/* Atomic block */
/****************/
/* Find crtc_index. It's used to find out if a plane supports a CRTC. */
/* TODO: include this in the get_plane_id() function somehow. */
for (int i = 0; i < resources->count_crtcs; i++) {
if (resources->crtcs[i] == dispdata->crtc_id) {
dispdata->crtc_index = i;
break;
}
}
/* Initialize the fence fd: */
dispdata->kms_out_fence_fd = -1,
/*********************/
/* Atomic block ends */
/*********************/
/* Figure out the default mode to be set. If the current CRTC's mode isn't
valid, select the first mode supported by the connector
FIXME find first mode that specifies DRM_MODE_TYPE_PREFERRED */
dispdata->mode = dispdata->saved_crtc->mode;
dispdata->mode = dispdata->crtc->mode;
if (dispdata->saved_crtc->mode_valid == 0) {
if (dispdata->crtc->mode_valid == 0) {
SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO,
"Current mode is invalid, selecting connector's mode #0.");
dispdata->mode = dispdata->conn->modes[0];
dispdata->mode = dispdata->connector->modes[0];
}
/* Setup the single display that's available */
@ -620,15 +902,15 @@ KMSDRM_VideoInit(_THIS)
display.desktop_mode.format = SDL_PIXELFORMAT_ARGB8888;
#else
/* FIXME */
drmModeFB *fb = drmModeGetFB(viddata->drm_fd, dispdata->saved_crtc->buffer_id);
drmModeFB *fb = drmModeGetFB(viddata->drm_fd, dispdata->crtc->buffer_id);
display.desktop_mode.format = drmToSDLPixelFormat(fb->bpp, fb->depth);
drmModeFreeFB(fb);
#endif
/* DRM mode index for the desktop mode is needed to complete desktop mode init NOW,
so look for it in the DRM modes array. */
for (int i = 0; i < dispdata->conn->count_modes; i++) {
if (!SDL_memcmp(dispdata->conn->modes + i, &dispdata->saved_crtc->mode, sizeof(drmModeModeInfo))) {
for (int i = 0; i < dispdata->connector->count_modes; i++) {
if (!SDL_memcmp(dispdata->connector->modes + i, &dispdata->crtc->mode, sizeof(drmModeModeInfo))) {
SDL_DisplayModeData *modedata = SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (modedata) {
modedata->mode_index = i;
@ -641,6 +923,79 @@ KMSDRM_VideoInit(_THIS)
display.driverdata = dispdata;
SDL_AddVideoDisplay(&display);
/****************/
/* Atomic block */
/****************/
ret = KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_ATOMIC, 1);
if (ret) {
ret = SDL_SetError("no atomic modesetting support.");
goto cleanup;
}
ret = KMSDRM_drmSetClientCap(viddata->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
if (ret) {
ret = SDL_SetError("no universal planes support.");
goto cleanup;
}
dispdata->plane_id = get_plane_id(_this);
if (!dispdata->plane_id) {
ret = SDL_SetError("could not find a suitable plane.");
goto cleanup;
}
dispdata->plane = KMSDRM_drmModeGetPlane(viddata->drm_fd, dispdata->plane_id);
//get_planes_info(_this);
/* We only do single plane to single crtc to single connector, no
* fancy multi-monitor or multi-plane stuff. So just grab the
* plane/crtc/connector property info for one of each:
*/
/* Get PLANE properties */
dispdata->plane_props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
dispdata->plane_id, DRM_MODE_OBJECT_PLANE);
dispdata->plane_props_info = calloc(dispdata->plane_props->count_props,
sizeof(dispdata->plane_props_info));
for (int i = 0; i < dispdata->plane_props->count_props; i++) {
dispdata->plane_props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd,
dispdata->plane_props->props[i]);
}
/* Get CRTC properties */
dispdata->crtc_props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
dispdata->crtc_id, DRM_MODE_OBJECT_CRTC);
dispdata->crtc_props_info = calloc(dispdata->crtc_props->count_props,
sizeof(dispdata->crtc_props_info));
for (int i = 0; i < dispdata->crtc_props->count_props; i++) {
dispdata->crtc_props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd,
dispdata->crtc_props->props[i]);
}
/* Get connector properties */
dispdata->connector_props = KMSDRM_drmModeObjectGetProperties(viddata->drm_fd,
dispdata->connector_id, DRM_MODE_OBJECT_CONNECTOR);
dispdata->connector_props_info = calloc(dispdata->connector_props->count_props,
sizeof(dispdata->connector_props_info));
for (int i = 0; i < dispdata->connector_props->count_props; i++) {
dispdata->connector_props_info[i] = KMSDRM_drmModeGetProperty(viddata->drm_fd,
dispdata->connector_props->props[i]);
}
/*********************/
/* Atomic block ends */
/*********************/
#ifdef SDL_INPUT_LINUXEV
SDL_EVDEV_Init();
#endif
@ -657,13 +1012,13 @@ cleanup:
if (ret != 0) {
/* Error (complete) cleanup */
if (dispdata->conn) {
KMSDRM_drmModeFreeConnector(dispdata->conn);
dispdata->conn = NULL;
if (dispdata->connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector);
dispdata->connector = NULL;
}
if (dispdata->saved_crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
dispdata->saved_crtc = NULL;
if (dispdata->crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc);
dispdata->crtc = NULL;
}
if (viddata->gbm_dev) {
KMSDRM_gbm_device_destroy(viddata->gbm_dev);
@ -697,9 +1052,9 @@ KMSDRM_VideoQuit(_THIS)
viddata->num_windows = 0;
/* Restore saved CRTC settings */
if (viddata->drm_fd >= 0 && dispdata && dispdata->conn && dispdata->saved_crtc) {
drmModeConnector *conn = dispdata->conn;
drmModeCrtc *crtc = dispdata->saved_crtc;
if (viddata->drm_fd >= 0 && dispdata && dispdata->connector && dispdata->crtc) {
drmModeConnector *conn = dispdata->connector;
drmModeCrtc *crtc = dispdata->crtc;
int ret = KMSDRM_drmModeSetCrtc(viddata->drm_fd, crtc->crtc_id, crtc->buffer_id,
crtc->x, crtc->y, &conn->connector_id, 1, &crtc->mode);
@ -708,13 +1063,13 @@ KMSDRM_VideoQuit(_THIS)
SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "Could not restore original CRTC mode");
}
}
if (dispdata && dispdata->conn) {
KMSDRM_drmModeFreeConnector(dispdata->conn);
dispdata->conn = NULL;
if (dispdata && dispdata->connector) {
KMSDRM_drmModeFreeConnector(dispdata->connector);
dispdata->connector = NULL;
}
if (dispdata && dispdata->saved_crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->saved_crtc);
dispdata->saved_crtc = NULL;
if (dispdata && dispdata->crtc) {
KMSDRM_drmModeFreeCrtc(dispdata->crtc);
dispdata->crtc = NULL;
}
if (viddata->gbm_dev) {
KMSDRM_gbm_device_destroy(viddata->gbm_dev);
@ -734,7 +1089,7 @@ void
KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{
SDL_DisplayData *dispdata = display->driverdata;
drmModeConnector *conn = dispdata->conn;
drmModeConnector *conn = dispdata->connector;
SDL_DisplayMode mode;
for (int i = 0; i < conn->count_modes; i++) {
@ -762,7 +1117,7 @@ KMSDRM_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
SDL_VideoData *viddata = (SDL_VideoData *)_this->driverdata;
SDL_DisplayData *dispdata = (SDL_DisplayData *)display->driverdata;
SDL_DisplayModeData *modedata = (SDL_DisplayModeData *)mode->driverdata;
drmModeConnector *conn = dispdata->conn;
drmModeConnector *conn = dispdata->connector;
if (!modedata) {
return SDL_SetError("Mode doesn't have an associated index");
@ -808,7 +1163,6 @@ KMSDRM_CreateWindow(_THIS, SDL_Window * window)
}
/* Init windata fields. */
windata->waiting_for_flip = SDL_FALSE;
windata->double_buffer = SDL_FALSE;
windata->crtc_setup_pending = SDL_FALSE;
windata->egl_surface_dirty = SDL_FALSE;

View file

@ -31,6 +31,7 @@
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <gbm.h>
#include <assert.h>
#if SDL_VIDEO_OPENGL_EGL
#include <EGL/egl.h>
#endif
@ -55,10 +56,28 @@ typedef struct SDL_DisplayModeData
typedef struct SDL_DisplayData
{
uint32_t crtc_id;
drmModeConnector *conn;
drmModeModeInfo mode;
drmModeCrtc *saved_crtc; /* CRTC to restore on quit */
uint32_t plane_id;
uint32_t crtc_id;
uint32_t connector_id;
drmModePlane *plane;
drmModeObjectProperties *plane_props;
drmModePropertyRes **plane_props_info;
drmModeCrtc *crtc;
drmModeObjectProperties *crtc_props;
drmModePropertyRes **crtc_props_info;
drmModeConnector *connector;
drmModeObjectProperties *connector_props;
drmModePropertyRes **connector_props_info;
int crtc_index;
int kms_in_fence_fd;
int kms_out_fence_fd;
} SDL_DisplayData;
@ -69,7 +88,6 @@ typedef struct SDL_WindowData
struct gbm_bo *curr_bo;
struct gbm_bo *next_bo;
struct gbm_bo *crtc_bo;
SDL_bool waiting_for_flip;
SDL_bool double_buffer;
SDL_bool crtc_setup_pending;
#if SDL_VIDEO_OPENGL_EGL
@ -89,6 +107,9 @@ int KMSDRM_CreateSurfaces(_THIS, SDL_Window * window);
KMSDRM_FBInfo *KMSDRM_FBFromBO(_THIS, struct gbm_bo *bo);
SDL_bool KMSDRM_WaitPageFlip(_THIS, SDL_WindowData *windata, int timeout);
/* Atomic functions that are used from SDL_kmsdrmopengles.c */
int drm_atomic_commit(_THIS, uint32_t fb_id, uint32_t flags);
/****************************************************************************/
/* SDL_VideoDevice functions declaration */
/****************************************************************************/