diff --git a/src/video/SDL_egl.c b/src/video/SDL_egl.c index 3358f47b8..1a2e838c8 100644 --- a/src/video/SDL_egl.c +++ b/src/video/SDL_egl.c @@ -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); diff --git a/src/video/SDL_egl_c.h b/src/video/SDL_egl_c.h index b01d4bec7..0eddfaa15 100644 --- a/src/video/SDL_egl_c.h +++ b/src/video/SDL_egl_c.h @@ -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; diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c index cd6f6a350..b83ffb706 100644 --- a/src/video/kmsdrm/SDL_kmsdrmopengles.c +++ b/src/video/kmsdrm/SDL_kmsdrmopengles.c @@ -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 */ diff --git a/src/video/kmsdrm/SDL_kmsdrmsym.h b/src/video/kmsdrm/SDL_kmsdrmsym.h index 875bf9358..f5b2e0172 100644 --- a/src/video/kmsdrm/SDL_kmsdrmsym.h +++ b/src/video/kmsdrm/SDL_kmsdrmsym.h @@ -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, diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c index 75bc9646a..417398a8d 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -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; diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.h b/src/video/kmsdrm/SDL_kmsdrmvideo.h index a1c87f0fd..897451bdb 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.h +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.h @@ -31,6 +31,7 @@ #include #include #include +#include #if SDL_VIDEO_OPENGL_EGL #include #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 */ /****************************************************************************/