From d53cbfbdfba131354cd33fc570f67a46a57b9b05 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Sun, 3 Jul 2016 10:37:07 +0200 Subject: [PATCH] Backport DRM video driver --- Makefile.common | 3 +- config.def.h | 1 + configuration.c | 2 + gfx/drivers/drm_gfx.c | 1004 +++++++++++++++++++++++++++++++++++++++++ gfx/video_driver.c | 3 + gfx/video_driver.h | 1 + griffin/griffin.c | 4 + intl/msg_hash_es.c | 10 + intl/msg_hash_us.c | 8 + msg_hash.h | 2 + 10 files changed, 1037 insertions(+), 1 deletion(-) create mode 100644 gfx/drivers/drm_gfx.c diff --git a/Makefile.common b/Makefile.common index aa336ebc3a..c1327fea7b 100644 --- a/Makefile.common +++ b/Makefile.common @@ -677,7 +677,8 @@ OBJ += gfx/video_context_driver.o \ ifeq ($(HAVE_KMS), 1) HAVE_AND_WILL_USE_DRM = 1 - OBJ += gfx/drivers_context/drm_ctx.o + OBJ += gfx/drivers_context/drm_ctx.o \ + gfx/drivers/drm_gfx.o DEFINES += $(GBM_CFLAGS) $(DRM_CFLAGS) $(EGL_CFLAGS) LIBS += $(GBM_LIBS) $(DRM_LIBS) $(EGL_LIBS) endif diff --git a/config.def.h b/config.def.h index a797167a92..c22477185c 100644 --- a/config.def.h +++ b/config.def.h @@ -29,6 +29,7 @@ enum { VIDEO_GL = 0, VIDEO_VULKAN, + VIDEO_DRM, VIDEO_XVIDEO, VIDEO_SDL, VIDEO_SDL2, diff --git a/configuration.c b/configuration.c index 84daadae72..b07fd4d4a3 100644 --- a/configuration.c +++ b/configuration.c @@ -183,6 +183,8 @@ const char *config_get_default_video(void) return "gl"; case VIDEO_VULKAN: return "vulkan"; + case VIDEO_DRM: + return "drm"; case VIDEO_WII: return "gx"; case VIDEO_XENON360: diff --git a/gfx/drivers/drm_gfx.c b/gfx/drivers/drm_gfx.c new file mode 100644 index 0000000000..817d9612f6 --- /dev/null +++ b/gfx/drivers/drm_gfx.c @@ -0,0 +1,1004 @@ +/* RetroArch - A frontend for libretro. + * Plain DRM diver: Copyright (C) 2016 - Manuel Alfayate + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "../../driver.h" +#include "../../general.h" +#include "../../retroarch.h" +#include "../video_context_driver.h" +#include "../font_driver.h" + +#include "drm_pixformats.h" + +struct modeset_buf +{ + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t size; + uint32_t handle; + uint8_t *map; + uint32_t fb_id; + uint32_t pixel_format; +}; + +struct drm_rect +{ + int x; + int y; + int width; + int height; +}; + +/* Pages are abstractions of buffers, encapsulated together with more + data for multiple buffering: to know if it's used, etc. + Hence, each page will have ONE buffer. No more, no less.*/ + +struct drm_page +{ + struct modeset_buf buf; + + bool used; + /* Each page has it's own mutex for + * isolating it's used flag access. */ + slock_t *page_used_mutex; + + /* This field will allow us to access the + * main _dispvars struct from the vsync CB function */ + struct drm_video *drmvars; + + /* This field will allow us to access the + * surface the page belongs to. */ + struct drm_surface *surface; +}; + +/* One surface for main game, another for menu. */ +struct drm_surface +{ + /* main surface has 3 pages, menu surface has 1 */ + unsigned int numpages; + struct drm_page *pages; + /* the page that's currently on screen */ + struct drm_page *current_page; + unsigned int bpp; + uint32_t pixformat; + + /* The internal buffers size. */ + int src_width; + int src_height; + + /* Surfaces with a higher layer will be on top of + * the ones with lower. Default is 0. */ + int layer; + + /* We need to keep this value for the blitting on + * the surface_update function. */ + int pitch; + int total_pitch; + + float aspect; + bool flip_page; +}; + +struct drm_struct { + /* DRM connection, mode and plane management stuff */ + int fd; + drmModeModeInfo *current_mode; + uint32_t crtc_id; + uint32_t connector_id; + + drmModeCrtcPtr orig_crtc; + + uint32_t plane_id; + uint32_t plane_fb_prop_id; + + drmModeEncoder *encoder; + drmModeRes *resources; +} drm; + +struct drm_video +{ + /* Abstract surface management stuff */ + struct drm_surface *main_surface; + struct drm_surface *menu_surface; + + /* Total dispmanx video dimensions. + * Not counting overscan settings. */ + unsigned int kms_width; + unsigned int kms_height; + + /* For threading */ + scond_t *vsync_condition; + slock_t *vsync_cond_mutex; + slock_t *pending_mutex; + + /* Menu */ + bool menu_active; + + bool rgb32; + + /* We use this to keep track of internal resolution changes + * done by cores in the main surface or in the menu. + * We need these outside the surface because we free surfaces + * and then we want to test if these values have changed before + * recreating them. */ + int core_width; + int core_height; + int core_pitch; + /* Both main and menu surfaces are going to have the same aspect, + * so we keep it here for future reference. */ + float current_aspect; + +}; + +/* Some prototypes for later use */ + +static int modeset_create_dumbfb(int fd, + struct modeset_buf *buf, int bpp, uint32_t pixformat); + +void deinit_drm(void); + +static void drm_surface_free(void *data, struct drm_surface **sp) +{ + int i; + struct drm_video *_drmvars = data; + struct drm_surface *surface = *sp; + + for (i = 0; i < surface->numpages; i++) + surface->pages[i].used = false; + + free(surface->pages); + + free(surface); + *sp = NULL; +} + +/* Changes surface ratio only without recreating the buffers etc. */ +static void drm_surface_set_aspect (float aspect, struct drm_surface *surface) +{ + surface->aspect = aspect; +} + +static void drm_surface_setup(void *data, int src_width, int src_height, + int pitch, int bpp, uint32_t pixformat, + int alpha, float aspect, int numpages, int layer, + struct drm_surface **sp) +{ + struct drm_video *_drmvars = data; + int i; + struct drm_surface *surface = NULL; + + *sp = calloc (1, sizeof(struct drm_surface)); + + surface = *sp; + + /* Setup surface parameters */ + surface->numpages = numpages; + /* We receive the total pitch, including things that are between scanlines + * and we calculate the visible pitch from the visible width. + * These will be used to increase the offsets for blitting. */ + surface->total_pitch = pitch; + surface->pitch = src_width * bpp; + surface->bpp = bpp; + surface->pixformat = pixformat; + surface->src_width = src_width; + surface->src_height = src_height; + surface->aspect = aspect; + + /* Allocate memory for all the pages in each surface + * and initialize variables inside each page's struct. */ + surface->pages = calloc(surface->numpages, sizeof(struct drm_page)); + + for (i = 0; i < surface->numpages; i++) + { + surface->pages[i].used = false; + surface->pages[i].surface = surface; + surface->pages[i].drmvars = _drmvars; + surface->pages[i].page_used_mutex = slock_new(); + } + + /* Create the framebuffer for each one of the pages of the surface. */ + for (i = 0; i < surface->numpages; i++) + { + surface->pages[i].buf.width = src_width; + surface->pages[i].buf.height = src_height; + int ret = modeset_create_dumbfb(drm.fd, &surface->pages[i].buf, bpp, pixformat); + if (ret) + { + RARCH_ERR ("DRM: can't create fb\n"); + } + } + + surface->flip_page = 0; +} + +static void drm_page_flip(struct drm_surface *surface) +{ + /* We alredy have the id of the FB_ID property of + * the plane on which we are going to do a pageflip: + * we got it back in drm_plane_setup() */ + int ret; + static drmModeAtomicReqPtr req = NULL; + + req = drmModeAtomicAlloc(); + + /* We add the buffer to the plane properties we want to + * set on an atomically, in a single step. + * We pass the plane id, the property id and the new fb id. */ + ret = drmModeAtomicAddProperty(req, + drm.plane_id, + drm.plane_fb_prop_id, + surface->pages[surface->flip_page].buf.fb_id); + + if (ret < 0) + { + RARCH_ERR ("DRM: failed to add atomic property for pageflip\n"); + } + /*... now we just need to do the commit */ + + /* REMEMBER!!! The DRM_MODE_PAGE_FLIP_EVENT flag asks the kernel + * to send you an event to the drm.fd once the + * pageflip is complete. If you don't want -12 errors + * (ENOMEM), namely "Cannot allocate memory", then + * you must drain the event queue of that fd. */ + ret = drmModeAtomicCommit(drm.fd, req, 0, NULL); + + if (ret < 0) + { + RARCH_ERR ("DRM: failed to commit for pageflip: %s\n", strerror(errno)); + } + + surface->flip_page = !(surface->flip_page); + + drmModeAtomicFree(req); +} + +static void drm_surface_update(void *data, const void *frame, + struct drm_surface *surface) +{ + struct drm_video *_drmvars = data; + struct drm_page *page = NULL; + + /* Frame blitting */ + int line = 0; + int src_offset = 0; + int dst_offset = 0; + for (line = 0; line < surface->src_height; line++) + { + memcpy ( + surface->pages[surface->flip_page].buf.map + dst_offset, + (uint8_t*)frame + src_offset, + surface->pitch); + src_offset += surface->total_pitch; + dst_offset += surface->pitch; + } + + /* Page flipping */ + drm_page_flip(surface); +} + + + +static uint32_t get_plane_prop_id(uint32_t obj_id, const char *name) +{ + int i,j; + drmModePlaneRes *plane_resources; + drmModePlane *plane; + drmModeObjectProperties *props; + drmModePropertyRes **props_info; + + char format_str[5]; + + plane_resources = drmModeGetPlaneResources(drm.fd); + for (i = 0; i < plane_resources->count_planes; i++) + { + plane = drmModeGetPlane(drm.fd, plane_resources->planes[i]); + if (plane->plane_id != obj_id) + continue; + + /* TODO: Improvement. We get all the properties of the plane and info about the properties. + * We should have done this already... This implementation must be improved. */ + props = drmModeObjectGetProperties(drm.fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); + props_info = malloc(props->count_props * sizeof *props_info); + for (j = 0; j < props->count_props; ++j) + props_info[j] = drmModeGetProperty(drm.fd, props->props[j]); + + /* We look for the prop_id we need */ + for (j = 0; j < props->count_props; j++) + { + if (!strcmp(props_info[j]->name, name)) + return props_info[j]->prop_id; + } + RARCH_ERR ("DRM: plane %d fb property ID with name %s not found\n", plane->plane_id, name); + } + return (0); +} + +/* gets fourcc, returns name string. */ +void format_name(const unsigned int fourcc, char *format_str) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(format_info); i++) + { + if (format_info[i].format == fourcc) + strcpy(format_str, format_info[i].name); + } +} + +/* Will tell us if the supplied plane supports the supplied pix format. */ +static bool format_support(const drmModePlanePtr ovr, uint32_t fmt) +{ + unsigned int i; + + for (i = 0; i < ovr->count_formats; ++i) + { + if (ovr->formats[i] == fmt) + return true; + } + + return false; +} + +uint64_t plane_type (drmModePlane *plane) +{ + int i,j; + /* The property values and their names are stored in different arrays, so we + * access them simultaneously here. + * We are interested in OVERLAY planes only, that's type 0 or DRM_PLANE_TYPE_OVERLAY + * (see /usr/xf86drmMode.h for definition). */ + drmModeObjectPropertiesPtr props; + props = drmModeObjectGetProperties(drm.fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); + + for (j=0; j < props->count_props; j++) + { + /* found the type property */ + if ( !strcmp(drmModeGetProperty(drm.fd, props->props[j])->name, "type")) + return (props->prop_values[j]); + } + return (0); +} + +/* This configures our only overlay plane to render the given surface. */ +void drm_plane_setup(struct drm_surface *surface) +{ + int i,j; + + /* Get plane resources */ + drmModePlane *plane; + drmModePlaneRes *plane_resources; + plane_resources = drmModeGetPlaneResources(drm.fd); + if (!plane_resources) + { + RARCH_ERR ("DRM: No scaling planes available!\n"); + } + + RARCH_LOG ("DRM: Number of planes on FD %d is %d\n", drm.fd, plane_resources->count_planes); + /* dump_planes(drm.fd); */ + + /* Look for a plane/overlay we can use with the configured CRTC + * Find a plane which can be connected to our CRTC. Find the + * CRTC index first, then iterate over available planes. + * Yes, strangely we need the in-use CRTC index to mask possible_crtc + * during the planes iteration... */ + unsigned int crtc_index = 0; + for (i = 0; i < (unsigned int)drm.resources->count_crtcs; i++) + { + if (drm.crtc_id == drm.resources->crtcs[i]) + { + crtc_index = i; + RARCH_LOG ("DRM: CRTC index found %d with ID %d\n", crtc_index, drm.crtc_id); + break; + } + } + + /* Programmer!! Save your sanity!! Primary planes have to cover the entire CRTC, and if you + * don't do that, you will get dmesg error "Plane must cover entire CRTC". + * Look at linux/source/drivers/gpu/drm/drm_plane_helper.c comments for more info. + * Also, primary planes can't be scaled: we need overlays for that. */ + for (i = 0; i < plane_resources->count_planes; i++) + { + plane = drmModeGetPlane(drm.fd, plane_resources->planes[i]); + + if (!(plane->possible_crtcs & (1 << crtc_index))){ + RARCH_LOG ("DRM: plane with ID %d can't be used with current CRTC\n",plane->plane_id); + continue; + } + + /* We are only interested in overlay planes. No overlay, no fun. + * (no scaling, must cover crtc..etc) so we skip primary planes */ + if (plane_type(plane)!= DRM_PLANE_TYPE_OVERLAY) + { + RARCH_LOG ("DRM: plane with ID %d is not an overlay. May be primary or cursor. Not usable.\n", plane->plane_id); + continue; + } + + if (!format_support(plane, surface->pixformat)) + { + RARCH_LOG ("DRM: plane with ID %d does not support framebuffer format\n", plane->plane_id); + continue; + } + + drm.plane_id = plane->plane_id; + drmModeFreePlane(plane); + } + + if (!drm.plane_id) + { + RARCH_LOG ("DRM: couldn't find an usable overlay plane for current CRTC and framebuffer pixel formal.\n"); + deinit_drm(); + exit (0); + } + else + { + RARCH_LOG ("DRM: using plane/overlay ID %d\n", drm.plane_id); + } + + /* We are going to be changing the framebuffer ID property of the chosen overlay every time + * we do a pageflip, so we get the property ID here to have it handy on the PageFlip function. */ + drm.plane_fb_prop_id = get_plane_prop_id(drm.plane_id, "FB_ID"); + if (!drm.plane_fb_prop_id) + { + RARCH_LOG("DRM: Can't get the FB property ID for plane(%u)\n", drm.plane_id); + } + + /* Note src coords (last 4 args) are in Q16 format + * crtc_w and crtc_h are the final size with applied scale/ratio. + * crtc_x and crtc_y are the position of the plane + * pw and ph are the input size: the size of the area we read from the fb. */ + uint32_t plane_flags = 0; + uint32_t plane_w = drm.current_mode->vdisplay * surface->aspect;; + uint32_t plane_h = drm.current_mode->vdisplay; + /* If we obtain a scaled image width that is bigger than the physical screen width, + * then we keep the physical screen width as our maximun width. */ + if (plane_w > drm.current_mode->hdisplay) + plane_w = drm.current_mode->hdisplay; + + uint32_t plane_x = (drm.current_mode->hdisplay - plane_w) / 2; + uint32_t plane_y = (drm.current_mode->vdisplay - plane_h) / 2; + + uint32_t src_w = surface->src_width; + uint32_t src_h = surface->src_height; + uint32_t src_x = 0; + uint32_t src_y = 0; + + /* We have to set a buffer for the plane, whatever buffer we want, + * but we must set a buffer so the plane starts reading from it now. */ + if (drmModeSetPlane(drm.fd, drm.plane_id, drm.crtc_id, + surface->pages[surface->flip_page].buf.fb_id, + plane_flags, plane_x, plane_y, plane_w, plane_h, + src_x<<16, src_y<<16, src_w<<16, src_h<<16)) + { + RARCH_ERR("DRM: failed to enable plane: %s\n", strerror(errno)); + } + + RARCH_LOG("DRM: src_w %d, src_h %d, plane_w %d, plane_h %d\n", src_w, src_h, plane_w, plane_h); + + /* Report what plane (of overlay type) we're using. */ + char fmt_name[5]; + format_name(surface->pixformat, fmt_name); + RARCH_LOG("DRM: Using plane with ID %d on CRTC ID %d format %s\n", drm.plane_id, drm.crtc_id, fmt_name); + +} + +static int modeset_create_dumbfb(int fd, struct modeset_buf *buf, int bpp, uint32_t pixformat) +{ + struct drm_mode_create_dumb create_dumb={0}; + struct drm_mode_map_dumb map_dumb={0}; + struct drm_mode_fb_cmd cmd_dumb={0}; + + create_dumb.width = buf->width; + create_dumb.height = buf->height; + create_dumb.bpp = bpp * 8; + create_dumb.flags = 0; + create_dumb.pitch = 0; + create_dumb.size = 0; + create_dumb.handle = 0; + drmIoctl(drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); + + /* Create the buffer. We just copy values here... */ + cmd_dumb.width=create_dumb.width; + cmd_dumb.height=create_dumb.height; + cmd_dumb.bpp=create_dumb.bpp; + cmd_dumb.pitch=create_dumb.pitch; + cmd_dumb.handle=create_dumb.handle; + cmd_dumb.depth=24; + + /* Map the buffer */ + drmIoctl(drm.fd,DRM_IOCTL_MODE_ADDFB,&cmd_dumb); + map_dumb.handle=create_dumb.handle; + drmIoctl(drm.fd,DRM_IOCTL_MODE_MAP_DUMB,&map_dumb); + + buf->pixel_format = pixformat; + buf->fb_id = cmd_dumb.fb_id; + buf->stride = create_dumb.pitch; + buf->size = create_dumb.size; + buf->handle = create_dumb.handle; + + /* Get address */ + buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, map_dumb.offset); + if (buf->map == MAP_FAILED) + { + RARCH_ERR ("DRM: cannot mmap dumb buffer\n"); + return 0; + } + + return 0; +} + +static bool init_drm(void) +{ + int ret; + drmModeConnector *connector; + uint i; + + drm.fd = open("/dev/dri/card0", O_RDWR); + + if (drm.fd < 0) + { + RARCH_LOG ("DRM: could not open drm device\n"); + return false; + } + + /* Programmer!! Save your sanity!! + * VERY important or we won't get all the available planes on drmGetPlaneResources()! + * We also need to enable the ATOMIC cap to see the atomic properties in objects!! */ + ret = drmSetClientCap(drm.fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + if (ret) + RARCH_ERR ("DRM: can't set UNIVERSAL PLANES cap.\n"); + else + RARCH_LOG ("DRM: UNIVERSAL PLANES cap set\n"); + + + ret = drmSetClientCap(drm.fd, DRM_CLIENT_CAP_ATOMIC, 1); + if (ret) + { + /*If this happens, check kernel support and kernel parameters + * (add i915.nuclear_pageflip=y to the kernel boot line for example) */ + RARCH_ERR ("DRM: can't set ATOMIC caps: %s\n", strerror(errno)); + } + else + RARCH_LOG ("DRM: ATOMIC caps set\n"); + + drm.resources = drmModeGetResources(drm.fd); + if (!drm.resources) + { + RARCH_ERR ("DRM: drmModeGetResources failed\n"); + return false; + } + + /* Find a connected connector. */ + for (i = 0; i < (uint)drm.resources->count_connectors; i++) + { + connector = drmModeGetConnector(drm.fd, drm.resources->connectors[i]); + /* It's connected, let's use it. */ + if (connector->connection == DRM_MODE_CONNECTED) + break; + drmModeFreeConnector(connector); + connector = NULL; + } + + if (!connector) + { + RARCH_ERR ("DRM: no connected connector found\n"); + return false; + } + + /* Find encoder */ + for (i = 0; i < (uint)drm.resources->count_encoders; i++) + { + drm.encoder = drmModeGetEncoder(drm.fd, drm.resources->encoders[i]); + if (drm.encoder->encoder_id == connector->encoder_id) + break; + drmModeFreeEncoder(drm.encoder); + drm.encoder = NULL; + } + + if (!drm.encoder) + { + RARCH_ERR ("DRM: no encoder found\n"); + return false; + } + + drm.crtc_id = drm.encoder->crtc_id; + drm.connector_id = connector->connector_id; + + /* Backup original crtc and it's mode, so we can restore the original video mode + * on exit in case we change it. */ + drm.orig_crtc = drmModeGetCrtc(drm.fd, drm.encoder->crtc_id); + drm.current_mode = &(drm.orig_crtc->mode); + + /* Set mode physical video mode. Not really needed, but clears TTY console. */ + struct modeset_buf buf; + buf.width = drm.current_mode->hdisplay; + buf.height = drm.current_mode->vdisplay; + ret = modeset_create_dumbfb(drm.fd, &buf, 4, DRM_FORMAT_XRGB8888); + if (ret) + { + RARCH_ERR ("DRM: can't create dumb fb\n"); + } + + if (drmModeSetCrtc(drm.fd, drm.crtc_id, buf.fb_id, 0, 0, + &drm.connector_id, 1, drm.current_mode)) + { + RARCH_ERR ("DRM: failed to set mode\n"); + return false; + } + + return true; +} + +void deinit_drm(void) +{ + /* Restore the original videomode/connector/scanoutbuffer(fb) + * combination (the original CRTC, that is). */ + drmModeSetCrtc(drm.fd, drm.orig_crtc->crtc_id, + drm.orig_crtc->buffer_id, + drm.orig_crtc->x, drm.orig_crtc->y, + &drm.connector_id, 1, &drm.orig_crtc->mode); + +#if 0 + /* TODO: Free surfaces here along + * with their pages (framebuffers)! */ + + if (bufs[0].fb_id) + { + drmModeRmFB(drm.fd, bufs[0].fb_id); + drmModeRmFB(drm.fd, bufs[1].fb_id); + } +#endif +} + +static void *drm_gfx_init(const video_info_t *video, + const input_driver_t **input, void **input_data) +{ + struct drm_video *_drmvars = calloc(1, sizeof(struct drm_video)); + if (!_drmvars) + return NULL; + + /* Setup surface parameters */ + _drmvars->menu_active = false; + _drmvars->rgb32 = video->rgb32; + + /* It's very important that we set aspect here because the + * call seq when a core is loaded is gfx_init()->set_aspect()->gfx_frame() + * and we don't want the main surface to be setup in set_aspect() + * before we get to gfx_frame(). */ + _drmvars->current_aspect = video_driver_get_aspect_ratio(); + + /* Initialize the rest of the mutexes and conditions. */ + _drmvars->vsync_condition = scond_new(); + _drmvars->vsync_cond_mutex = slock_new(); + _drmvars->pending_mutex = slock_new(); + _drmvars->core_width = 0; + _drmvars->core_height = 0; + + _drmvars->main_surface = NULL; + _drmvars->menu_surface = NULL; + + if (input && input_data) + *input = NULL; + + /* DRM Init */ + if (!init_drm()) + { + RARCH_ERR ("DRM: Failed to initialize DRM\n"); + return NULL; + } + else + RARCH_LOG ("DRM: Init succesful.\n"); + + _drmvars->kms_width = drm.current_mode->hdisplay; + _drmvars->kms_height = drm.current_mode->vdisplay; + + return _drmvars; +} + +static bool drm_gfx_frame(void *data, const void *frame, unsigned width, + unsigned height, uint64_t frame_count, unsigned pitch, const char *msg) +{ + struct drm_video *_drmvars = data; + + if (width != _drmvars->core_width || height != _drmvars->core_height) + { + /* Sanity check. */ + if (width == 0 || height == 0) + return true; + + _drmvars->core_width = width; + _drmvars->core_height = height; + _drmvars->core_pitch = pitch; + + if (_drmvars->main_surface != NULL) + drm_surface_free(_drmvars, &_drmvars->main_surface); + + /* We need to recreate the main surface and it's pages (buffers). */ + drm_surface_setup(_drmvars, + width, + height, + pitch, + _drmvars->rgb32 ? 4 : 2, + _drmvars->rgb32 ? DRM_FORMAT_XRGB8888 : DRM_FORMAT_RGB565, + 255, + _drmvars->current_aspect, + 3, + 0, + &_drmvars->main_surface); + + /* We need to change the plane to read from the main surface */ + drm_plane_setup(_drmvars->main_surface); + } + + if (_drmvars->menu_active) + { + char buf[128]; + video_monitor_get_fps(buf, sizeof(buf), NULL, 0); + } + + /* Update main surface: locate free page, blit and flip. */ + drm_surface_update(_drmvars, frame, _drmvars->main_surface); + return true; +} + +static void drm_set_texture_enable(void *data, bool state, bool full_screen) +{ + struct drm_video *_drmvars = data; + + /* If menu was active but it's not anymore... */ + if (!state && _drmvars->menu_active) + { + /* We tell ony the plane we have to read from the main surface again */ + drm_plane_setup(_drmvars->main_surface); + /* We free the menu surface buffers */ + drm_surface_free(_drmvars, &_drmvars->menu_surface); + } + + _drmvars->menu_active = state; +} + +static void drm_set_texture_frame(void *data, const void *frame, bool rgb32, + unsigned width, unsigned height, float alpha) +{ + struct drm_video *_drmvars = data; + + if (!_drmvars->menu_active) + return; + + /* If menu is active in this frame but the + * menu surface is NULL, we allocate a new one.*/ + if (_drmvars->menu_surface == NULL) + { + drm_surface_setup(_drmvars, + width, + height, + width * 4, + 4, + DRM_FORMAT_XRGB8888, + 210, + _drmvars->current_aspect, + 2, + 0, + &_drmvars->menu_surface); + + /* We need to re-setup the ONLY plane as the setup depens on input buffers dimensions */ + drm_plane_setup(_drmvars->menu_surface); + } + + unsigned int i, j; + + /* We have to go on a pixel format conversion adventure for now, until we can + * convince RGUI to output in an 8888 format. */ + unsigned int src_pitch = width * 2; + unsigned int dst_pitch = width * 4; + unsigned int dst_width = width; + uint32_t line[dst_width]; + + /* The output pixel array with the converted pixels. */ + char *frame_output = (char *) malloc (dst_pitch * height); + + /* Remember, memcpy() works with 8bits pointers for increments. */ + char *dst_base_addr = frame_output; + + for (i = 0; i < height; i++) + { + for (j = 0; j < src_pitch / 2; j++) + { + uint16_t src_pix = *((uint16_t*)frame + (src_pitch / 2 * i) + j); + /* The hex AND is for keeping only the part we need for each component. */ + uint32_t R = (src_pix << 8) & 0x00FF0000; + uint32_t G = (src_pix << 4) & 0x0000FF00; + uint32_t B = (src_pix << 0) & 0x000000FF; + line[j] = (0 | R | G | B); + } + memcpy(dst_base_addr + (dst_pitch * i), (char*)line, dst_pitch); + } + + /* We update the menu surface if menu is active. */ + drm_surface_update(_drmvars, frame_output, _drmvars->menu_surface); +} + +static void drm_gfx_set_nonblock_state(void *data, bool state) +{ + struct drm_video *vid = data; + + (void)data; + (void)vid; + + /* TODO */ +} + +static bool drm_gfx_alive(void *data) +{ + (void)data; + return true; /* always alive */ +} + +static bool drm_gfx_focus(void *data) +{ + (void)data; + return true; /* fb device always has focus */ +} + +static void drm_gfx_viewport_info(void *data, struct video_viewport *vp) +{ + struct drm_video *vid = data; + + if (!vid) + return; + + vp->x = vp->y = 0; + + vp->width = vp->full_width = vid->core_width; + vp->height = vp->full_height = vid->core_height; +} + +static bool drm_gfx_suppress_screensaver(void *data, bool enable) +{ + (void)data; + (void)enable; + + return false; +} + +static bool drm_gfx_has_windowed(void *data) +{ + (void)data; + + return false; +} + +static bool drm_gfx_set_shader(void *data, + enum rarch_shader_type type, const char *path) +{ + (void)data; + (void)type; + (void)path; + + return false; +} + +static void drm_gfx_set_rotation(void *data, unsigned rotation) +{ + (void)data; + (void)rotation; +} + +static bool drm_gfx_read_viewport(void *data, uint8_t *buffer) +{ + (void)data; + (void)buffer; + + return true; +} + +static void drm_set_aspect_ratio (void *data, unsigned aspect_ratio_idx) +{ + struct drm_video *_drmvars = data; + /* Here we obtain the new aspect ratio. */ + float new_aspect = aspectratio_lut[aspect_ratio_idx].value; + + if (_drmvars->current_aspect != new_aspect) + { + _drmvars->current_aspect = new_aspect; + drm_surface_set_aspect(new_aspect, _drmvars->main_surface); + if (_drmvars->menu_active) + { + drm_surface_set_aspect(new_aspect, _drmvars->menu_surface); + drm_plane_setup(_drmvars->menu_surface); + } + } +} + +static const video_poke_interface_t drm_poke_interface = { + NULL, + NULL, + NULL, /* set_video_mode */ + NULL, /* set_filtering */ + NULL, /* get_video_output_size */ + NULL, /* get_video_output_prev */ + NULL, /* get_video_output_next */ + NULL, /* get_current_framebuffer */ + NULL, /* get_proc_address */ + drm_set_aspect_ratio, + NULL, /* drm_apply_state_changes */ +#ifdef HAVE_MENU + drm_set_texture_frame, + drm_set_texture_enable, +#endif + NULL, /* drm_set_osd_msg */ + NULL /* drm_show_mouse */ +}; + +static void drm_gfx_get_poke_interface(void *data, + const video_poke_interface_t **iface) +{ + (void)data; + *iface = &drm_poke_interface; +} + +static void drm_gfx_free(void *data) +{ + struct drm_video *_drmvars = data; + + if (!_drmvars) + return; + + drm_surface_free(_drmvars, &_drmvars->main_surface); + + if (_drmvars->menu_surface) + drm_surface_free(_drmvars, &_drmvars->menu_surface); + + /* Destroy mutexes and conditions. */ + slock_free(_drmvars->pending_mutex); + slock_free(_drmvars->vsync_cond_mutex); + scond_free(_drmvars->vsync_condition); + + free(_drmvars); +} + +video_driver_t video_drm = { + drm_gfx_init, + drm_gfx_frame, + drm_gfx_set_nonblock_state, + drm_gfx_alive, + drm_gfx_focus, + drm_gfx_suppress_screensaver, + drm_gfx_has_windowed, + drm_gfx_set_shader, + drm_gfx_free, + "drm", + NULL, /* set_viewport */ + drm_gfx_set_rotation, + drm_gfx_viewport_info, + drm_gfx_read_viewport, + NULL, /* read_frame_raw */ + +#ifdef HAVE_OVERLAY + NULL, /* overlay_interface */ +#endif + drm_gfx_get_poke_interface +}; diff --git a/gfx/video_driver.c b/gfx/video_driver.c index dc0b4622cf..e0654af150 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -199,6 +199,9 @@ static const video_driver_t *video_drivers[] = { #endif #ifdef HAVE_SUNXI &video_sunxi, +#endif +#ifdef HAVE_KMS + &video_drm, #endif &video_null, NULL, diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 07eaeb6b33..1a7be266a2 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -551,6 +551,7 @@ extern video_driver_t video_omap; extern video_driver_t video_exynos; extern video_driver_t video_dispmanx; extern video_driver_t video_sunxi; +extern video_driver_t video_drm; extern video_driver_t video_xshm; extern video_driver_t video_null; diff --git a/griffin/griffin.c b/griffin/griffin.c index a47625cd1b..9a44f4cc99 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -278,6 +278,10 @@ VIDEO DRIVER #include "../gfx/drivers/vulkan.c" #endif +#if defined(HAVE_KMS) +#include "../gfx/drivers/drm_gfx.c" +#endif + #ifdef HAVE_OPENGL #include "../gfx/common/gl_common.c" #include "../gfx/drivers/gl.c" diff --git a/intl/msg_hash_es.c b/intl/msg_hash_es.c index 4c6a5f0d0f..15728cd63d 100644 --- a/intl/msg_hash_es.c +++ b/intl/msg_hash_es.c @@ -305,6 +305,16 @@ int menu_hash_get_help_es(uint32_t hash, char *s, size_t len) "renderizados por software debería \n" "ser óptimo."); break; + case MENU_LABEL_VIDEO_DRIVER_DRM: + snprintf(s, len, + "Controlador de vídeo de DRM simple. \n" + " \n" + "Este es un controlador de vídeo que \n" + "usa libdrm para escalado por hardware \n" + "mediante los overlays de la GPU. \n" + " \n" + "El blitting se hace por software."); + break; case MENU_LABEL_VIDEO_DRIVER_SUNXI: snprintf(s, len, "Controlador de vídeo Sunxi-G2D. \n" diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 4ba42b0678..396a209042 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -261,6 +261,14 @@ int menu_hash_get_help_us(uint32_t hash, char *s, size_t len) "Performance for software rendered cores \n" "should be optimal."); break; + case MENU_LABEL_VIDEO_DRIVER_DRM: + snprintf(s, len, + "Plain DRM Video Driver. \n" + " \n" + "This is a low-level video driver using. \n" + "libdrm for hardware scaling using \n" + "GPU overlays."); + break; case MENU_LABEL_VIDEO_DRIVER_SUNXI: snprintf(s, len, "Sunxi-G2D Video Driver. \n" diff --git a/msg_hash.h b/msg_hash.h index 6cbbaadb8d..07315c3a28 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1032,6 +1032,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VIDEO_DRIVER_D3D, MENU_ENUM_LABEL_VIDEO_DRIVER_EXYNOS, MENU_ENUM_LABEL_VIDEO_DRIVER_SUNXI, + MENU_ENUM_LABEL_VIDEO_DRIVER_DRM, MENU_ENUM_LABEL_MENU_ENUM_DRIVER, MENU_ENUM_LABEL_VALUE_MENU_ENUM_DRIVER, @@ -1968,6 +1969,7 @@ enum msg_hash_enums #define MENU_LABEL_VIDEO_DRIVER_D3D 0x0b886340U #define MENU_LABEL_VIDEO_DRIVER_EXYNOS 0xfc37c54bU #define MENU_LABEL_VIDEO_DRIVER_SUNXI 0x10620e3cU +#define MENU_LABEL_VIDEO_DRIVER_DRM 0x61a89bfaU #define MENU_LABEL_VIDEO_ROTATION 0x4ce6882bU #define MENU_LABEL_VIDEO_SCALE 0x09835d63U #define MENU_LABEL_VIDEO_CROP_OVERSCAN 0x861f7a2fU