/* * Copyright (c) 2017 Rob Clark * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sub license, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include "common.h" #include "drm-common.h" #define VOID2U64(x) ((uint64_t)(unsigned long)(x)) static struct drm drm = { .kms_out_fence_fd = -1, }; static int add_connector_property(drmModeAtomicReq *req, uint32_t obj_id, const char *name, uint64_t value) { struct connector *obj = drm.connector; unsigned int i; int prop_id = 0; for (i = 0 ; i < obj->props->count_props ; i++) { if (strcmp(obj->props_info[i]->name, name) == 0) { prop_id = obj->props_info[i]->prop_id; break; } } if (prop_id < 0) { printf("no connector property: %s\n", name); return -EINVAL; } return drmModeAtomicAddProperty(req, obj_id, prop_id, value); } static int add_crtc_property(drmModeAtomicReq *req, uint32_t obj_id, const char *name, uint64_t value) { struct crtc *obj = drm.crtc; unsigned int i; int prop_id = -1; for (i = 0 ; i < obj->props->count_props ; i++) { if (strcmp(obj->props_info[i]->name, name) == 0) { prop_id = obj->props_info[i]->prop_id; break; } } if (prop_id < 0) { printf("no crtc property: %s\n", name); return -EINVAL; } return drmModeAtomicAddProperty(req, obj_id, prop_id, value); } static int add_plane_property(drmModeAtomicReq *req, uint32_t obj_id, const char *name, uint64_t value) { struct plane *obj = drm.plane; unsigned int i; int prop_id = -1; for (i = 0 ; i < obj->props->count_props ; i++) { if (strcmp(obj->props_info[i]->name, name) == 0) { prop_id = obj->props_info[i]->prop_id; break; } } if (prop_id < 0) { printf("no plane property: %s\n", name); return -EINVAL; } return drmModeAtomicAddProperty(req, obj_id, prop_id, value); } static int drm_atomic_commit(uint32_t fb_id, uint32_t flags) { drmModeAtomicReq *req; uint32_t plane_id = drm.plane->plane->plane_id; uint32_t blob_id; int ret; req = drmModeAtomicAlloc(); if (flags & DRM_MODE_ATOMIC_ALLOW_MODESET) { if (add_connector_property(req, drm.connector_id, "CRTC_ID", drm.crtc_id) < 0) return -1; if (drmModeCreatePropertyBlob(drm.fd, drm.mode, sizeof(*drm.mode), &blob_id) != 0) return -1; if (add_crtc_property(req, drm.crtc_id, "MODE_ID", blob_id) < 0) return -1; if (add_crtc_property(req, drm.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", drm.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", drm.mode->hdisplay << 16); add_plane_property(req, plane_id, "SRC_H", drm.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", drm.mode->hdisplay); add_plane_property(req, plane_id, "CRTC_H", drm.mode->vdisplay); if (drm.kms_in_fence_fd != -1) { add_crtc_property(req, drm.crtc_id, "OUT_FENCE_PTR", VOID2U64(&drm.kms_out_fence_fd)); add_plane_property(req, plane_id, "IN_FENCE_FD", drm.kms_in_fence_fd); } ret = drmModeAtomicCommit(drm.fd, req, flags, NULL); if (ret) goto out; if (drm.kms_in_fence_fd != -1) { close(drm.kms_in_fence_fd); drm.kms_in_fence_fd = -1; } out: drmModeAtomicFree(req); return ret; } static EGLSyncKHR create_fence(const struct egl *egl, int fd) { EGLint attrib_list[] = { EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd, EGL_NONE, }; EGLSyncKHR fence = egl->eglCreateSyncKHR(egl->display, EGL_SYNC_NATIVE_FENCE_ANDROID, attrib_list); assert(fence); return fence; } static int atomic_run(const struct gbm *gbm, const struct egl *egl) { struct gbm_bo *bo = NULL; struct drm_fb *fb; uint32_t i = 0; uint32_t flags = DRM_MODE_ATOMIC_NONBLOCK; int64_t start_time, report_time, cur_time; int ret; if (egl_check(egl, eglDupNativeFenceFDANDROID) || egl_check(egl, eglCreateSyncKHR) || egl_check(egl, eglDestroySyncKHR) || egl_check(egl, eglWaitSyncKHR) || egl_check(egl, eglClientWaitSyncKHR)) return -1; /* Allow a modeset change for the first commit only. */ flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; start_time = report_time = get_time_ns(); while (i < drm.count) { unsigned frame = i; struct gbm_bo *next_bo; EGLSyncKHR gpu_fence = NULL; /* out-fence from gpu, in-fence to kms */ EGLSyncKHR kms_fence = NULL; /* in-fence to gpu, out-fence from kms */ if (drm.kms_out_fence_fd != -1) { kms_fence = create_fence(egl, drm.kms_out_fence_fd); assert(kms_fence); /* driver now has ownership of the fence fd: */ drm.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. */ egl->eglWaitSyncKHR(egl->display, kms_fence, 0); } /* Start fps measuring on second frame, to remove the time spent * compiling shader, etc, from the fps: */ if (i == 1) { start_time = report_time = get_time_ns(); } if (!gbm->surface) { glBindFramebuffer(GL_FRAMEBUFFER, egl->fbs[frame % NUM_BUFFERS].fb); } egl->draw(i++); /* insert fence to be singled in cmdstream.. this fence will be * signaled when gpu rendering done */ gpu_fence = create_fence(egl, EGL_NO_NATIVE_FENCE_FD_ANDROID); assert(gpu_fence); if (gbm->surface) { eglSwapBuffers(egl->display, egl->surface); } /* after swapbuffers, gpu_fence should be flushed, so safe * to get fd: */ drm.kms_in_fence_fd = egl->eglDupNativeFenceFDANDROID(egl->display, gpu_fence); egl->eglDestroySyncKHR(egl->display, gpu_fence); assert(drm.kms_in_fence_fd != -1); if (gbm->surface) { next_bo = gbm_surface_lock_front_buffer(gbm->surface); } else { next_bo = gbm->bos[frame % NUM_BUFFERS]; } if (!next_bo) { printf("Failed to lock frontbuffer\n"); return -1; } fb = drm_fb_get_from_bo(next_bo); if (!fb) { printf("Failed to get a new framebuffer BO\n"); return -1; } 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 = egl->eglClientWaitSyncKHR(egl->display, kms_fence, 0, EGL_FOREVER_KHR); } while (status != EGL_CONDITION_SATISFIED_KHR); egl->eglDestroySyncKHR(egl->display, kms_fence); } cur_time = get_time_ns(); if (cur_time > (report_time + 2 * NSEC_PER_SEC)) { double elapsed_time = cur_time - start_time; double secs = elapsed_time / (double)NSEC_PER_SEC; unsigned frames = i - 1; /* first frame ignored */ printf("Rendered %u frames in %f sec (%f fps)\n", frames, secs, (double)frames/secs); report_time = cur_time; } /* Check for user input: */ struct pollfd fdset[] = { { .fd = STDIN_FILENO, .events = POLLIN, } }; ret = poll(fdset, ARRAY_SIZE(fdset), 0); if (ret > 0) { printf("user interrupted!\n"); return 0; } /* * Here you could also update drm plane layers if you want * hw composition */ ret = drm_atomic_commit(fb->fb_id, flags); if (ret) { printf("failed to commit: %s\n", strerror(errno)); return -1; } /* release last buffer to render on again: */ if (bo && gbm->surface) gbm_surface_release_buffer(gbm->surface, bo); bo = next_bo; /* Allow a modeset change for the first commit only. */ flags &= ~(DRM_MODE_ATOMIC_ALLOW_MODESET); } finish_perfcntrs(); cur_time = get_time_ns(); double elapsed_time = cur_time - start_time; double secs = elapsed_time / (double)NSEC_PER_SEC; unsigned frames = i - 1; /* first frame ignored */ printf("Rendered %u frames in %f sec (%f fps)\n", frames, secs, (double)frames/secs); dump_perfcntrs(frames, elapsed_time); return ret; } /* Pick a plane.. something that at a minimum can be connected to * the chosen crtc, but prefer primary plane. * * Seems like there is some room for a drmModeObjectGetNamedProperty() * type helper in libdrm.. */ static int get_plane_id(void) { drmModePlaneResPtr plane_resources; uint32_t i, j; int ret = -EINVAL; int found_primary = 0; plane_resources = drmModeGetPlaneResources(drm.fd); if (!plane_resources) { printf("drmModeGetPlaneResources failed: %s\n", strerror(errno)); return -1; } for (i = 0; (i < plane_resources->count_planes) && !found_primary; i++) { uint32_t id = plane_resources->planes[i]; drmModePlanePtr plane = drmModeGetPlane(drm.fd, id); if (!plane) { printf("drmModeGetPlane(%u) failed: %s\n", id, strerror(errno)); continue; } if (plane->possible_crtcs & (1 << drm.crtc_index)) { drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm.fd, id, DRM_MODE_OBJECT_PLANE); /* primary or not, this plane is good enough to use: */ ret = id; for (j = 0; j < props->count_props; j++) { drmModePropertyPtr p = drmModeGetProperty(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; } drmModeFreeProperty(p); } drmModeFreeObjectProperties(props); } drmModeFreePlane(plane); } drmModeFreePlaneResources(plane_resources); return ret; } const struct drm * init_drm_atomic(const char *device, const char *mode_str, unsigned int vrefresh, unsigned int count) { uint32_t plane_id; int ret; ret = init_drm(&drm, device, mode_str, vrefresh, count); if (ret) return NULL; ret = drmSetClientCap(drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); if (ret) { printf("no atomic modesetting support: %s\n", strerror(errno)); return NULL; } ret = get_plane_id(); if (!ret) { printf("could not find a suitable plane\n"); return NULL; } else { plane_id = ret; } /* 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: */ drm.plane = calloc(1, sizeof(*drm.plane)); drm.crtc = calloc(1, sizeof(*drm.crtc)); drm.connector = calloc(1, sizeof(*drm.connector)); #define get_resource(type, Type, id) do { \ drm.type->type = drmModeGet##Type(drm.fd, id); \ if (!drm.type->type) { \ printf("could not get %s %i: %s\n", \ #type, id, strerror(errno)); \ return NULL; \ } \ } while (0) get_resource(plane, Plane, plane_id); get_resource(crtc, Crtc, drm.crtc_id); get_resource(connector, Connector, drm.connector_id); #define get_properties(type, TYPE, id) do { \ uint32_t i; \ drm.type->props = drmModeObjectGetProperties(drm.fd, \ id, DRM_MODE_OBJECT_##TYPE); \ if (!drm.type->props) { \ printf("could not get %s %u properties: %s\n", \ #type, id, strerror(errno)); \ return NULL; \ } \ drm.type->props_info = calloc(drm.type->props->count_props, \ sizeof(*drm.type->props_info)); \ for (i = 0; i < drm.type->props->count_props; i++) { \ drm.type->props_info[i] = drmModeGetProperty(drm.fd, \ drm.type->props->props[i]); \ } \ } while (0) get_properties(plane, PLANE, plane_id); get_properties(crtc, CRTC, drm.crtc_id); get_properties(connector, CONNECTOR, drm.connector_id); drm.run = atomic_run; return &drm; }