/* 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 #include #include #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #include "../font_driver.h" #include "../video_context_driver.h" #include "../../configuration.h" #include "../../retroarch.h" #include "../../runloop.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); static 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_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(struct drm_surface *surface, float aspect) { 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 = (struct drm_page*) 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 (string_is_equal(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. */ static void drm_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) strlcpy(format_str, format_info[i].name, sizeof(format_str)); } } /* 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; } static uint64_t drm_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 = drmModeObjectGetProperties(drm.fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); for (j = 0; j < props->count_props; j++) { /* found the type property */ if (string_is_equal(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. */ static void drm_plane_setup(struct drm_surface *surface) { int i,j; char fmt_name[5]; /* 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 (drm_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. */ drm_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; } static void *drm_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data) { struct drm_video *_drmvars = (struct drm_video*) 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"); free(_drmvars); return NULL; } else { RARCH_LOG ("DRM: Init successful.\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) { unsigned int i, j; 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) { 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 * depends on input buffers dimensions. */ drm_plane_setup(_drmvars->menu_surface); } /* 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(_drmvars->main_surface, new_aspect); if (_drmvars->menu_active) { drm_surface_set_aspect(_drmvars->menu_surface, new_aspect); 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 };