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