From 7efa9def0751b9b5bce777c0a8dcfc9eb211c168 Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Sun, 24 Nov 2013 17:28:21 +0100 Subject: [PATCH 1/7] Add exynos video driver Documentation is provided in README-exynos. --- Makefile | 6 + README-exynos.md | 60 ++ config.def.h | 1 + driver.c | 3 + driver.h | 1 + gfx/exynos_gfx.c | 1489 +++++++++++++++++++++++++++++++++++++++++++ memcpy-neon.S | 139 ++++ qb/config.libs.sh | 8 +- qb/config.params.sh | 1 + settings.c | 2 + 10 files changed, 1709 insertions(+), 1 deletion(-) create mode 100644 README-exynos.md create mode 100644 gfx/exynos_gfx.c create mode 100644 memcpy-neon.S diff --git a/Makefile b/Makefile index f31ff77c7b..caa0dcde4f 100644 --- a/Makefile +++ b/Makefile @@ -220,6 +220,12 @@ ifeq ($(HAVE_OMAP), 1) OBJ += gfx/omap_gfx.o endif +ifeq ($(HAVE_EXYNOS), 1) + OBJ += gfx/exynos_gfx.o memcpy-neon.o + LIBS += $(DRM_LIBS) $(EXYNOS_LIBS) + DEFINES += $(DRM_CFLAGS) $(EXYNOS_CFLAGS) +endif + ifeq ($(HAVE_OPENGL), 1) OBJ += gfx/gl.o \ gfx/gfx_context.o \ diff --git a/README-exynos.md b/README-exynos.md new file mode 100644 index 0000000000..eeffe0df12 --- /dev/null +++ b/README-exynos.md @@ -0,0 +1,60 @@ +# RetroArch Exynos-G2D video driver + +The Exynos-G2D video driver for RetroArch uses the Exynos DRM layer for presentation and the Exynos G2D block to scale and blit the emulator framebuffer to the screen. The G2D subsystem is a separate functional block on modern Samsung Exynos SoCs (in particular Exynos4412 and Exynos5250) that accelerates various kind of 2D blit operations. It can fill, copy, scale and blend pixel buffers and therefore provides adequate functionality for RetroArch purposes. + +## Reasons to use the driver + +Hardware accelerated rendering on devices based on an Exynos SoC is usually restricted to the use of the GPU block, which is either a Mali or PowerVR IP. Both GPU types have the problem that interfacing with them requires a proprietary driver stack, comprised of kernel and userspace code. While the kernel code is open source, the userspace code is only available as a binary blob to the enduser. + +If you want to use such a device with an upstream kernel, the GPU block will most likely not work for you. Also the chances of Mali or PowerVR kernel code being accepted upstream is very slim. Still, one might want to ask the question if using the GPU block for such trivial operations (basically scale and blend) is the right approach in the first place. + +Since the G2D block is present on all modern Exynos SoCs, the natural way of proceeding would be to use it instead of the GPU block. The G2D is still a dedicated piece of hardware, so all operations are offloaded from the CPU. It should be noted though, that using the G2D instead of the GPU removes the possibility to use GPU shaders to enhance the image quality of your emulator core of choice. If the user relies on these enhancements, then he's advised to continue using the GPU, most likely by using the EGL/GLES video driver. + +The author uses a Hardkernel ODROID-X2, which is an developer board powered by an Exynos4412 SoC. The vendor supplied kernel, a Linux tree based on the 3.8.y branch, currently offers no way to use the G2D because of issues related to clock setup. However upstreaming work is in progress and a tree based on 3.15.y, with some slight modifications, is available from here: + +[odroid-3.15.y repository](https://github.com/tobiasjakobi/linux-odroid) + +Please refer to the minimalistic documentation in README-ODROID for setup. + +## Performance analysis + +Some simple benchmarking was done to evaluate the performance of the G2D block. The test run was done with the snes9x-next emulation core and a game title that uses a native resolution of 256x224 pixels. The output screen was configured to a 1280x720 mode. Scaling to the output screen was done by keeping the native aspect ratio. In this case this would result in an output rectangle of size 822x720. + + total memcpy calls: 18795 + total g2d calls: 18795 + total memcpy time: 8.978532 seconds + total g2d time: 29.703944 seconds + average time per memcpy call: 477.708540 microseconds + average time per g2d call: 1580.417345 microseconds + +The average time to display the emulator framebuffer on screen is roughly 2058 microseconds, or around 486 frames per second. Assuming that the time consumption increases linearly with the amount of pixels processed, which is usually a safe assumption, scaling to an output rectangle of size 1920x1080 would yield a average duration of 7207 microseconds, which is still 138 frames per second. + +## Configuration + +The video driver uses the libdrm API to interface with the DRM. Some patches are still missing in the upstream tree, therefore the user is advised to use the 'exynos' branch of the repository mentioned below. + +[libdrm repository](https://github.com/tobiasjakobi/libdrm) + +Make sure that the Exynos API support is enabled. If you're building libdrm from source, then use + + ./configure --enable-exynos-experimental-api + +to enable it. + +The video driver name is 'exynos'. It honors the following video settings: + + - video\_monitor\_index + - video\_fullscreen\_x and video\_fullscreen\_y + +The monitor index maps to the DRM connector index. If it is zero, then it just selects the first 'sane' connector, which means that it is connected to a display device and it provides at least one useable mode. If the value is non-zero, it forces the selection of this connector. For example, on the author's ODROID-X2, with an odroid-3.15.y kernel, the HDMI connector has index 1. + +The two fullscreen parameters select the mode the DRM should select. If zero, the native connector mode is selected. If non-zero, the DRM tries to select the wanted mode. This might fail if the mode is not available from the connector. + +## Issues and TODOs + +The driver still suffers from some issues. + + - The aspect ratio computation can be improved. In particular the user supplied aspect ratio is currently unused. + - Font rendering and blitting is very inefficient since the backing buffer is cleared every frame. Introduce a invalidation rectangle which covers the region where font glyphs are drawn, and then only clear this region. + - Temporary GEM buffers are used as source for blitting operations. Support for the IOMMU has to be enabled, so that one can use the 'userptr' functionality. + - More TODOs are pointed out in the code itself. diff --git a/config.def.h b/config.def.h index a917d13124..8e7e795dbb 100644 --- a/config.def.h +++ b/config.def.h @@ -45,6 +45,7 @@ enum VIDEO_VG, VIDEO_NULL, VIDEO_OMAP, + VIDEO_EXYNOS, AUDIO_RSOUND, AUDIO_OSS, diff --git a/driver.c b/driver.c index 3bee37e194..94c80821d1 100644 --- a/driver.c +++ b/driver.c @@ -139,6 +139,9 @@ static const video_driver_t *video_drivers[] = { #endif #ifdef HAVE_OMAP &video_omap, +#endif +#ifdef HAVE_EXYNOS + &video_exynos, #endif NULL, }; diff --git a/driver.h b/driver.h index 43bcc7cedf..e4c268b7e5 100644 --- a/driver.h +++ b/driver.h @@ -626,6 +626,7 @@ extern const video_driver_t video_vg; extern const video_driver_t video_null; extern const video_driver_t video_lima; extern const video_driver_t video_omap; +extern const video_driver_t video_exynos; extern const input_driver_t input_android; extern const input_driver_t input_sdl; extern const input_driver_t input_dinput; diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c new file mode 100644 index 0000000000..866dd07695 --- /dev/null +++ b/gfx/exynos_gfx.c @@ -0,0 +1,1489 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2013-2014 - Tobias Jakobi + * + * 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 + +#include +#include +#include + +#include + +#include "../general.h" +#include "gfx_common.h" +#include "fonts/fonts.h" + +/* TODO: Honor these properties: vsync, RGUI rotation, RGUI alpha, aspect ratio change */ + +/* Set to '1' to enable debug logging code. */ +#define EXYNOS_GFX_DEBUG_LOG 0 + +/* Set to '1' to enable debug perf code. */ +#define EXYNOS_GFX_DEBUG_PERF 0 + +extern void *memcpy_neon(void *dst, const void *src, size_t n); + + +/* When scaling the G2D seems to need a small 'safety zone' towards the borders of * + * the destination buffer, otherwise leading to execution errors. This needs further * + * investigation, but for now we just stay 4 (= 8 / 2) pixels away from the border. */ +static const unsigned g2d_safety_zone = 8; + +/* We use two GEM buffers (main and aux) to handle 'data' from the frontend. */ +enum exynos_buffer_type { + exynos_buffer_main = 0, + exynos_buffer_aux, + exynos_buffer_count +}; + +/* We have to handle three types of 'data' from the frontend, each abstracted by a * + * G2D image object. The image objects are then backed by some storage buffer. * + * (1) the emulator framebuffer (backed by main buffer) * + * (2) the RGUI buffer (backed by aux buffer) * + * (3) the font rendering buffer (backed by aux buffer) */ +enum exynos_image_type { + exynos_image_frame = 0, + exynos_image_font, + exynos_image_rgui, + exynos_image_count +}; + +static const struct exynos_config_default { + unsigned width, height; + enum exynos_buffer_type buf_type; + unsigned g2d_color_mode; + unsigned bpp; /* bytes per pixel */ +} defaults[exynos_image_count] = { + {1024, 640, exynos_buffer_main, G2D_COLOR_FMT_RGB565 | G2D_ORDER_AXRGB, 2}, /* frame */ + {720, 368, exynos_buffer_aux, G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_AXRGB, 2}, /* font */ + {400, 240, exynos_buffer_aux, G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_RGBAX, 2} /* RGUI */ +}; + + +struct exynos_data; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) +struct exynos_perf { + unsigned memcpy_calls; + unsigned g2d_calls; + + unsigned long long memcpy_time; + unsigned long long g2d_time; + + struct timespec tspec; +}; +#endif + +struct exynos_page { + struct exynos_bo *bo; + uint32_t buf_id; + + struct exynos_data *base; + + bool used; /* Set if page is currently used. */ + bool clear; /* Set if page has to be cleared. */ +}; + +struct exynos_fliphandler { + struct pollfd fds; + drmEventContext evctx; +}; + +struct exynos_drm { + drmModeRes *resources; + drmModeConnector *connector; + drmModeEncoder *encoder; + drmModeModeInfo *mode; + drmModeCrtc *orig_crtc; + + uint32_t crtc_id; + uint32_t connector_id; +}; + +struct exynos_data { + char drmname[32]; + int fd; + struct exynos_device *device; + + struct exynos_drm *drm; + struct exynos_fliphandler *fliphandler; + + /* G2D is used for scaling to framebuffer dimensions. */ + struct g2d_context *g2d; + struct g2d_image *dst; + struct g2d_image *src[exynos_image_count]; + + struct exynos_bo *buf[exynos_buffer_count]; + + struct exynos_page *pages; + unsigned num_pages; + + /* currently displayed page */ + struct exynos_page *cur_page; + + unsigned pageflip_pending; + + /* framebuffer dimensions */ + unsigned width, height; + + /* framebuffer aspect ratio */ + float aspect; + + /* parameters for blitting emulator fb to screen */ + unsigned blit_params[6]; + + /* bytes per pixel */ + unsigned bpp; + + /* framebuffer parameters */ + unsigned pitch, size; + + bool sync; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + struct exynos_perf perf; +#endif +}; + +static inline void put_pixel_argb4444(uint16_t *p, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + *p = (b >> 4) | ((g >> 4) << 4) | ((r >> 4) << 8) | ((a >> 4) << 12); +} + +static inline unsigned align_common(unsigned i, unsigned j) { + return (i + j - 1) & ~(j - 1); +} + +/* Find the index of a compatible DRM device. */ +static int get_device_index(void) { + char buf[32]; + drmVersionPtr ver; + + int index = 0; + int fd; + bool found = false; + + while (!found) { + snprintf(buf, sizeof(buf), "/dev/dri/card%d", index); + + fd = open(buf, O_RDWR); + if (fd == -1) break; + + ver = drmGetVersion(fd); + + if (strcmp("exynos", ver->name) == 0) + found = true; + else + ++index; + + drmFreeVersion(ver); + close(fd); + } + + return (found ? index : -1); +} + +/* Restore the original CRTC. */ +static void restore_crtc(struct exynos_drm *d, int fd) { + if (d->orig_crtc == NULL) return; + + drmModeSetCrtc(fd, d->orig_crtc->crtc_id, + d->orig_crtc->buffer_id, + d->orig_crtc->x, + d->orig_crtc->y, + &d->connector_id, 1, &d->orig_crtc->mode); + + drmModeFreeCrtc(d->orig_crtc); + d->orig_crtc = NULL; +} + +static void clean_up_drm(struct exynos_drm *d, int fd) { + if (d->encoder) drmModeFreeEncoder(d->encoder); + if (d->connector) drmModeFreeConnector(d->connector); + if (d->resources) drmModeFreeResources(d->resources); + + free(d); + close(fd); +} + +/* The main pageflip handler, which the DRM executes when it flips to the page. * + * Decreases the pending pageflip count and updates the current page. */ +static void page_flip_handler(int fd, unsigned frame, unsigned sec, + unsigned usec, void *data) { + struct exynos_page *page = data; + +#if (EXYNOS_GFX_DEBUG_LOG == 1) + RARCH_LOG("video_exynos: in page_flip_handler, page = %p\n", page); +#endif + + if (page->base->cur_page != NULL) { + page->base->cur_page->used = false; + } + + page->base->pageflip_pending--; + page->base->cur_page = page; +} + +static void wait_flip(struct exynos_fliphandler *fh) { + const int timeout = -1; + + fh->fds.revents = 0; + + if (poll(&fh->fds, 1, timeout) < 0) + return; + + if (fh->fds.revents & (POLLHUP | POLLERR)) + return; + + if (fh->fds.revents & POLLIN) + drmHandleEvent(fh->fds.fd, &fh->evctx); +} + +static struct exynos_page *get_free_page(struct exynos_page *p, unsigned cnt) { + unsigned i; + + for (i = 0; i < cnt; ++i) { + if (!p[i].used) return &p[i]; + } + + return NULL; +} + +/* Count the number of used pages. */ +static unsigned pages_used(struct exynos_page *p, unsigned cnt) { + unsigned i; + unsigned count = 0; + + for (i = 0; i < cnt; ++i) { + if (p[i].used) ++count; + } + + return count; +} + +static void clean_up_pages(struct exynos_page *p, unsigned cnt) { + unsigned i; + + for (i = 0; i < cnt; ++i) { + if (p[i].bo != NULL) { + if (p[i].buf_id != 0) + drmModeRmFB(p[i].buf_id, p[i].bo->handle); + + exynos_bo_destroy(p[i].bo); + } + } +} + +#if (EXYNOS_GFX_DEBUG_LOG == 1) +static const char *buffer_name(enum exynos_buffer_type type) { + switch (type) { + case exynos_buffer_main: + return "main"; + case exynos_buffer_aux: + return "aux"; + default: + assert(false); + return NULL; + } +} +#endif + +/* Create a GEM buffer with userspace mapping. Buffer is cleared after creation. */ +static struct exynos_bo *create_mapped_buffer(struct exynos_device *dev, unsigned size) { + struct exynos_bo *buf; + const unsigned flags = 0; + + buf = exynos_bo_create(dev, size, flags); + if (buf == NULL) { + RARCH_ERR("video_exynos: failed to create temp buffer object\n"); + return NULL; + } + + if (exynos_bo_map(buf) == NULL) { + RARCH_ERR("video_exynos: failed to map temp buffer object\n"); + exynos_bo_destroy(buf); + return NULL; + } + + memset(buf->vaddr, 0, size); + + return buf; +} + +static int realloc_buffer(struct exynos_data *pdata, + enum exynos_buffer_type type, unsigned size) { + struct exynos_bo *buf = pdata->buf[type]; + unsigned i; + + if (size > buf->size) { +#if (EXYNOS_GFX_DEBUG_LOG == 1) + RARCH_LOG("video_exynos: reallocating %s buffer (%u -> %u bytes)\n", + buffer_name(type), buf->size, size); +#endif + + exynos_bo_destroy(buf); + buf = create_mapped_buffer(pdata->device, size); + + if (buf == NULL) { + RARCH_ERR("video_exynos: reallocation failed\n"); + return -1; + } + + pdata->buf[type] = buf; + + /* Map new GEM buffer to the G2D images backed by it. */ + for (i = 0; i < exynos_image_count; ++i) { + if (defaults[i].buf_type == type) + pdata->src[i]->bo[0] = buf->handle; + } + } + + return 0; +} + +/* Clear a buffer associated to a G2D image by doing a (fast) solid fill. */ +static int clear_buffer(struct g2d_context *g2d, struct g2d_image *img) { + int ret; + + ret = g2d_solid_fill(g2d, img, 0, 0, img->width, img->height); + + if (ret == 0) + ret = g2d_exec(g2d); + + if (ret != 0) + RARCH_ERR("video_exynos: failed to clear buffer using G2D\n"); + + return ret; +} + +/* Put a font glyph at a position in the buffer that is backing the G2D font image object. */ +static void put_glyph_rgba4444(struct exynos_data *pdata, const uint8_t *src, uint8_t *f_rgb, + unsigned g_width, unsigned g_height, unsigned g_pitch, + unsigned dst_x, unsigned dst_y) { + const enum exynos_image_type buf_type = defaults[exynos_image_font].buf_type; + const unsigned buf_width = pdata->src[exynos_image_font]->width; + + unsigned x, y; + uint16_t *dst; + + dst = (uint16_t*)pdata->buf[buf_type]->vaddr + dst_y * buf_width + dst_x; + + for (y = 0; y < g_height; ++y, src += g_pitch, dst += buf_width) { + for (x = 0; x < g_width; ++x) { + const uint8_t blend = src[x]; + + if (blend != 0) put_pixel_argb4444(&dst[x], f_rgb[0], f_rgb[1], f_rgb[2], blend); + } + } +} + +#if (EXYNOS_GFX_DEBUG_PERF == 1) +void perf_init(struct exynos_perf *p) { + p->memcpy_calls = 0; + p->g2d_calls = 0; + + p->memcpy_time = 0; + p->g2d_time = 0; + + memset(&p->tspec, 0, sizeof(struct timespec)); +} + +void perf_finish(struct exynos_perf *p) { + RARCH_LOG("video_exynos: debug: total memcpy calls: %u\n", p->memcpy_calls); + RARCH_LOG("video_exynos: debug: total g2d calls: %u\n", p->g2d_calls); + + RARCH_LOG("video_exynos: debug: total memcpy time: %f seconds\n", + (double)p->memcpy_time / 1000000.0); + RARCH_LOG("video_exynos: debug: total g2d time: %f seconds\n", + (double)p->g2d_time / 1000000.0); + + RARCH_LOG("video_exynos: debug: average time per memcpy call: %f microseconds\n", + (double)p->memcpy_time / (double)p->memcpy_calls); + RARCH_LOG("video_exynos: debug: average time per g2d call: %f microseconds\n", + (double)p->g2d_time / (double)p->g2d_calls); +} + +void perf_memcpy(struct exynos_perf *p, bool start) { + if (start) { + clock_gettime(CLOCK_MONOTONIC, &p->tspec); + } else { + struct timespec new = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &new); + + p->memcpy_time += (new.tv_sec - p->tspec.tv_sec) * 1000000; + p->memcpy_time += (new.tv_nsec - p->tspec.tv_nsec) / 1000; + ++p->memcpy_calls; + } +} + +void perf_g2d(struct exynos_perf *p, bool start) { + if (start) { + clock_gettime(CLOCK_MONOTONIC, &p->tspec); + } else { + struct timespec new = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &new); + + p->g2d_time += (new.tv_sec - p->tspec.tv_sec) * 1000000; + p->g2d_time += (new.tv_nsec - p->tspec.tv_nsec) / 1000; + ++p->g2d_calls; + } +} +#endif + + +static int exynos_g2d_init(struct exynos_data *pdata) { + struct g2d_image *dst; + struct g2d_context *g2d; + unsigned i; + + g2d = g2d_init(pdata->fd); + if (g2d == NULL) return -1; + + dst = calloc(1, sizeof(struct g2d_image)); + if (dst == NULL) goto fail; + + dst->buf_type = G2D_IMGBUF_GEM; + dst->color_mode = (pdata->bpp == 2) ? G2D_COLOR_FMT_RGB565 | G2D_ORDER_AXRGB : + G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB; + dst->width = pdata->width; + dst->height = pdata->height; + dst->stride = pdata->pitch; + dst->color = 0xff000000; /* Clear color for solid fill operation. */ + + for (i = 0; i < exynos_image_count; ++i) { + const enum exynos_buffer_type buf_type = defaults[i].buf_type; + const unsigned buf_size = defaults[i].width * defaults[i].height * defaults[i].bpp; + + struct g2d_image *src; + + src = calloc(1, sizeof(struct g2d_image)); + if (src == NULL) break; + + src->width = defaults[i].width; + src->height = defaults[i].height; + src->stride = defaults[i].width * defaults[i].bpp; + + src->color_mode = defaults[i].g2d_color_mode; + src->color = (i == exynos_image_font) ? 0x00 : 0xff000000; + + /* Associate GEM buffer storage with G2D image. */ + src->buf_type = G2D_IMGBUF_GEM; + src->bo[0] = pdata->buf[buf_type]->handle; + + src->repeat_mode = G2D_REPEAT_MODE_PAD; /* Pad creates no border artifacts. */ + + /* Make sure that the storage buffer is large enough. If the code is working * + * properly, then this is just a NOP. Still put it here as an insurance. */ + realloc_buffer(pdata, buf_type, buf_size); + + pdata->src[i] = src; + } + + if (i != exynos_image_count) { + while (i-- > 0) { + free(pdata->src[i]); + pdata->src[i] = NULL; + } + goto fail_src; + } + + pdata->dst = dst; + pdata->g2d = g2d; + + return 0; + +fail_src: + free(dst); + +fail: + g2d_fini(g2d); + + return -1; +} + +static void exynos_g2d_free(struct exynos_data *pdata) { + unsigned i; + + free(pdata->dst); + + for (i = 0; i < exynos_image_count; ++i) { + free(pdata->src[i]); + pdata->src[i] = NULL; + } + + g2d_fini(pdata->g2d); +} + +static int exynos_open(struct exynos_data *pdata) { + char buf[32]; + int devidx; + + int fd = -1; + struct exynos_drm *drm = NULL; + struct exynos_fliphandler *fliphandler = NULL; + unsigned i; + + pdata->fd = -1; + + devidx = get_device_index(); + if (devidx != -1) { + snprintf(buf, sizeof(buf), "/dev/dri/card%d", devidx); + } else { + RARCH_ERR("video_exynos: no compatible drm device found\n"); + return -1; + } + + fd = open(buf, O_RDWR); + if (fd == -1) { + RARCH_ERR("video_exynos: can't open drm device\n"); + return -1; + } + + drm = calloc(1, sizeof(struct exynos_drm)); + if (drm == NULL) { + RARCH_ERR("video_exynos: failed to allocate drm\n"); + close(fd); + return -1; + } + + drm->resources = drmModeGetResources(fd); + if (drm->resources == NULL) { + RARCH_ERR("video_exynos: failed to get drm resources\n"); + goto fail; + } + + for (i = 0; i < drm->resources->count_connectors; ++i) { + if (g_settings.video.monitor_index != 0 && + g_settings.video.monitor_index - 1 != i) + continue; + + drm->connector = drmModeGetConnector(fd, drm->resources->connectors[i]); + if (drm->connector == NULL) + continue; + + if (drm->connector->connection == DRM_MODE_CONNECTED && + drm->connector->count_modes > 0) + break; + + drmModeFreeConnector(drm->connector); + drm->connector = NULL; + } + + if (i == drm->resources->count_connectors) { + RARCH_ERR("video_exynos: no currently active connector found\n"); + goto fail; + } + + for (i = 0; i < drm->resources->count_encoders; i++) { + drm->encoder = drmModeGetEncoder(fd, drm->resources->encoders[i]); + + if (drm->encoder == NULL) continue; + + if (drm->encoder->encoder_id == drm->connector->encoder_id) + break; + + drmModeFreeEncoder(drm->encoder); + drm->encoder = NULL; + } + + fliphandler = calloc(1, sizeof(struct exynos_fliphandler)); + if (fliphandler == NULL) { + RARCH_ERR("video_exynos: failed to allocate fliphandler\n"); + goto fail; + } + + /* Setup the flip handler. */ + fliphandler->fds.fd = fd; + fliphandler->fds.events = POLLIN; + fliphandler->evctx.version = DRM_EVENT_CONTEXT_VERSION; + fliphandler->evctx.page_flip_handler = page_flip_handler; + + strncpy(pdata->drmname, buf, sizeof(buf)); + pdata->fd = fd; + + pdata->drm = drm; + pdata->fliphandler = fliphandler; + + RARCH_LOG("video_exynos: using DRM device \"%s\" with connector id %u\n", + pdata->drmname, pdata->drm->connector->connector_id); + + return 0; + +fail: + free(fliphandler); + clean_up_drm(drm, fd); + + return -1; +} + +/* Counterpart to exynos_open. */ +static void exynos_close(struct exynos_data *pdata) { + free(pdata->fliphandler); + pdata->fliphandler = NULL; + + memset(pdata->drmname, 0, sizeof(char) * 32); + + clean_up_drm(pdata->drm, pdata->fd); + pdata->fd = -1; + pdata->drm = NULL; +} + +static int exynos_init(struct exynos_data *pdata, unsigned bpp) { + struct exynos_drm *drm = pdata->drm; + int fd = pdata->fd; + + unsigned i; + + if (g_settings.video.fullscreen_x != 0 && + g_settings.video.fullscreen_y != 0) { + for (i = 0; i < drm->connector->count_modes; i++) { + if (drm->connector->modes[i].hdisplay == g_settings.video.fullscreen_x && + drm->connector->modes[i].vdisplay == g_settings.video.fullscreen_y) { + drm->mode = &drm->connector->modes[i]; + break; + } + } + + if (drm->mode == NULL) { + RARCH_ERR("video_exynos: requested resolution (%dx%d) not available\n", + g_settings.video.fullscreen_x, g_settings.video.fullscreen_y); + goto fail; + } + + } else { + /* Select first mode, which is the native one. */ + drm->mode = &drm->connector->modes[0]; + } + + if (drm->mode->hdisplay == 0 || drm->mode->vdisplay == 0) { + RARCH_ERR("video_exynos: failed to select sane resolution\n"); + goto fail; + } + + drm->crtc_id = drm->encoder->crtc_id; + drm->orig_crtc = drmModeGetCrtc(fd, drm->crtc_id); + if (!drm->orig_crtc) + RARCH_WARN("video_exynos: cannot find original crtc\n"); + + pdata->width = drm->mode->hdisplay; + pdata->height = drm->mode->vdisplay; + + pdata->aspect = (float)drm->mode->hdisplay / (float)drm->mode->vdisplay; + + /* Always use triple buffering to reduce chance of tearing. */ + pdata->num_pages = 3; + + pdata->bpp = bpp; + pdata->pitch = bpp * pdata->width; + pdata->size = pdata->pitch * pdata->height; + + RARCH_LOG("video_exynos: selected %ux%u resolution with %u bpp\n", + pdata->width, pdata->height, pdata->bpp); + + return 0; + +fail: + restore_crtc(drm, fd); + + drm->mode = NULL; + + return -1; +} + +/* Counterpart to exynos_init. */ +static void exynos_deinit(struct exynos_data *pdata) { + struct exynos_drm *drm = pdata->drm; + + restore_crtc(drm, pdata->fd); + + drm = NULL; + + pdata->width = 0; + pdata->height = 0; + + pdata->num_pages = 0; + + pdata->bpp = 0; + pdata->pitch = 0; + pdata->size = 0; +} + +static int exynos_alloc(struct exynos_data *pdata) { + struct exynos_device *device; + struct exynos_bo *bo; + struct exynos_page *pages; + unsigned i; + uint32_t pixel_format; + uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; + + const unsigned flags = 0; + + device = exynos_device_create(pdata->fd); + if (device == NULL) { + RARCH_ERR("video_exynos: failed to create device from fd\n"); + return -1; + } + + pages = calloc(pdata->num_pages, sizeof(struct exynos_page)); + if (pages == NULL) { + RARCH_ERR("video_exynos: failed to allocate pages\n"); + goto fail_alloc; + } + + for (i = 0; i < exynos_buffer_count; ++i) { + const unsigned buffer_size = defaults[i].width * defaults[i].height * defaults[i].bpp; + + bo = create_mapped_buffer(device, buffer_size); + if (bo == NULL) break; + + pdata->buf[i] = bo; + } + + if (i != exynos_buffer_count) { + while (i-- > 0) { + exynos_bo_destroy(pdata->buf[i]); + pdata->buf[i] = NULL; + } + + goto fail; + } + + for (i = 0; i < pdata->num_pages; ++i) { + bo = exynos_bo_create(device, pdata->size, flags); + if (bo == NULL) { + RARCH_ERR("video_exynos: failed to create buffer object\n"); + goto fail; + } + + /* Don't map the BO, since we don't access it through userspace. */ + + pages[i].bo = bo; + pages[i].base = pdata; + + pages[i].used = false; + pages[i].clear = true; + } + + pixel_format = (pdata->bpp == 2) ? DRM_FORMAT_RGB565 : DRM_FORMAT_XRGB8888; + pitches[0] = pdata->pitch; + offsets[0] = 0; + + for (i = 0; i < pdata->num_pages; ++i) { + handles[0] = pages[i].bo->handle; + + if (drmModeAddFB2(pdata->fd, pdata->width, pdata->height, + pixel_format, handles, pitches, offsets, + &pages[i].buf_id, flags)) { + RARCH_ERR("video_exynos: failed to add bo %u to fb\n", i); + goto fail; + } + } + + pdata->pages = pages; + pdata->device = device; + + /* Setup CRTC: display the last allocated page. */ + drmModeSetCrtc(pdata->fd, pdata->drm->crtc_id, pages[pdata->num_pages - 1].buf_id, + 0, 0, &pdata->drm->connector_id, 1, pdata->drm->mode); + + return 0; + +fail: + clean_up_pages(pages, pdata->num_pages); + +fail_alloc: + exynos_device_destroy(device); + + return -1; +} + +/* Counterpart to exynos_alloc. */ +static void exynos_free(struct exynos_data *pdata) { + unsigned i; + + clean_up_pages(pdata->pages, pdata->num_pages); + + free(pdata->pages); + pdata->pages = NULL; + + for (i = 0; i < exynos_buffer_count; ++i) { + exynos_bo_destroy(pdata->buf[i]); + pdata->buf[i] = NULL; + } +} + +#if (EXYNOS_GFX_DEBUG_LOG == 1) +static void exynos_alloc_status(struct exynos_data *pdata) { + unsigned i; + struct exynos_page *pages = pdata->pages; + + RARCH_LOG("video_exynos: allocated %u pages with %u bytes each (pitch = %u bytes)\n", + pdata->num_pages, pdata->size, pdata->pitch); + + for (i = 0; i < pdata->num_pages; ++i) { + RARCH_LOG("video_exynos: page %u: BO at %p, buffer id = %u\n", + i, pages[i].bo, pages[i].buf_id); + } +} +#endif + +/* Find a free page, clear it if necessary, and return the page. If * + * no free page is available when called, wait for a page flip. */ +static struct exynos_page *exynos_free_page(struct exynos_data *pdata) { + struct exynos_page *page = NULL; + struct g2d_image *dst = pdata->dst; + + /* Wait until a free page is available. */ + while (page == NULL) { + page = get_free_page(pdata->pages, pdata->num_pages); + + if (page == NULL) wait_flip(pdata->fliphandler); + } + + dst->bo[0] = page->bo->handle; + + if (page->clear) { + if (clear_buffer(pdata->g2d, dst) == 0) + page->clear = false; + } + + page->used = true; + return page; +} + +static void exynos_setup_scale(struct exynos_data *pdata, unsigned width, + unsigned height, unsigned src_bpp) { + struct g2d_image *src = pdata->src[exynos_image_frame]; + unsigned i; + unsigned w, h; + + const float aspect = (float)width / (float)height; + + src->width = width; + src->height = height; + + src->color_mode = (src_bpp == 2) ? + G2D_COLOR_FMT_RGB565 | G2D_ORDER_AXRGB: + G2D_COLOR_FMT_XRGB8888 | G2D_ORDER_AXRGB; + + if (fabsf(pdata->aspect - aspect) < 0.0001f) { + w = pdata->width; + h = pdata->height; + } else { + if (pdata->aspect > aspect) { + w = (float)pdata->width * aspect / pdata->aspect; + h = pdata->height; + } else { + w = pdata->width; + h = (float)pdata->height * pdata->aspect / aspect; + } + } + + w -= g2d_safety_zone; + h -= g2d_safety_zone; + + pdata->blit_params[0] = (pdata->width - w) / 2; + pdata->blit_params[1] = (pdata->height - h) / 2; + pdata->blit_params[2] = w; + pdata->blit_params[3] = h; + pdata->blit_params[4] = width; + pdata->blit_params[5] = height; + + for (i = 0; i < pdata->num_pages; ++i) + pdata->pages[i].clear = true; +} + +static void exynos_set_fake_blit(struct exynos_data *pdata) { + const unsigned offset = g2d_safety_zone / 2; + unsigned i; + + pdata->blit_params[0] = offset; + pdata->blit_params[1] = offset; + pdata->blit_params[2] = pdata->width - offset; + pdata->blit_params[3] = pdata->height - offset; + + for (i = 0; i < pdata->num_pages; ++i) + pdata->pages[i].clear = true; +} + +static int exynos_blit_frame(struct exynos_data *pdata, const void *frame, + unsigned src_pitch) { + const enum exynos_buffer_type buf_type = defaults[exynos_image_frame].buf_type; + const unsigned size = src_pitch * pdata->blit_params[5]; + + struct g2d_image *src = pdata->src[exynos_image_frame]; + + if (realloc_buffer(pdata, buf_type, size) != 0) + return -1; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_memcpy(&pdata->perf, true); +#endif + + /* HACK: Without IOMMU the G2D only works properly between GEM buffers. */ + memcpy_neon(pdata->buf[buf_type]->vaddr, frame, size); + src->stride = src_pitch; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_memcpy(&pdata->perf, false); +#endif + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, true); +#endif + + if (g2d_copy_with_scale(pdata->g2d, src, pdata->dst, 0, 0, + pdata->blit_params[4], pdata->blit_params[5], + pdata->blit_params[0], pdata->blit_params[1], + pdata->blit_params[2], pdata->blit_params[3], 0) || + g2d_exec(pdata->g2d)) { + RARCH_ERR("video_exynos: failed to blit frame\n"); + return -1; + } + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, false); +#endif + + return 0; +} + +static int exynos_blend_rgui(struct exynos_data *pdata, + unsigned rotation) { + struct g2d_image *src = pdata->src[exynos_image_rgui]; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, true); +#endif + + if (g2d_scale_and_blend(pdata->g2d, src, pdata->dst, 0, 0, + src->width, src->height, pdata->blit_params[0], + pdata->blit_params[1], pdata->blit_params[2], + pdata->blit_params[3], G2D_OP_OVER) || + g2d_exec(pdata->g2d)) { + RARCH_ERR("video_exynos: failed to blend RGUI\n"); + return -1; + } + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, false); +#endif + + return 0; +} + +static int exynos_blend_font(struct exynos_data *pdata) { + struct g2d_image *src = pdata->src[exynos_image_font]; + const unsigned offset = g2d_safety_zone / 2; + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, true); +#endif + + if (g2d_scale_and_blend(pdata->g2d, src, pdata->dst, 0, 0, + src->width, src->height, offset, + offset, pdata->width - offset, + pdata->height - offset, G2D_OP_OVER) || + g2d_exec(pdata->g2d)) { + RARCH_ERR("video_exynos: failed to blend font\n"); + return -1; + } + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_g2d(&pdata->perf, false); +#endif + + return 0; +} + +static int exynos_flip(struct exynos_data *pdata, struct exynos_page *page) { + /* We don't queue multiple page flips. */ + if (pdata->pageflip_pending > 0) { + wait_flip(pdata->fliphandler); + } + + /* Issue a page flip at the next vblank interval. */ + if (drmModePageFlip(pdata->fd, pdata->drm->crtc_id, page->buf_id, + DRM_MODE_PAGE_FLIP_EVENT, page) != 0) { + RARCH_ERR("video_exynos: failed to issue page flip\n"); + return -1; + } else { + pdata->pageflip_pending++; + } + + /* On startup no frame is displayed. We therefore wait for the initial flip to finish. */ + if (pdata->cur_page == NULL) wait_flip(pdata->fliphandler); + + return 0; +} + + +struct exynos_video { + struct exynos_data *data; + + void *font; + const font_renderer_driver_t *font_driver; + + uint8_t font_rgb[4]; + + unsigned bytes_per_pixel; + + /* current dimensions of the emulator fb */ + unsigned width; + unsigned height; + + /* RGUI data */ + unsigned rgui_rotation; + bool rgui_active; + + bool aspect_changed; +}; + + +static int exynos_init_font(struct exynos_video *vid) { + struct exynos_data *pdata = vid->data; + struct g2d_image *src = pdata->src[exynos_image_font]; + + const unsigned buf_height = defaults[exynos_image_font].height; + const unsigned buf_width = align_common(pdata->aspect * (float)buf_height, 16); + + if (!g_settings.video.font_enable) return 0; + + if (font_renderer_create_default(&vid->font_driver, &vid->font)) { + const int r = g_settings.video.msg_color_r * 255; + const int g = g_settings.video.msg_color_g * 255; + const int b = g_settings.video.msg_color_b * 255; + + vid->font_rgb[0] = r < 0 ? 0 : (r > 255 ? 255 : r); + vid->font_rgb[1] = g < 0 ? 0 : (g > 255 ? 255 : g); + vid->font_rgb[2] = b < 0 ? 0 : (b > 255 ? 255 : b); + } else { + RARCH_ERR("video_exynos: creating font renderer failed\n"); + return -1; + } + + /* The font buffer color type is ARGB4444. */ + if (realloc_buffer(pdata, defaults[exynos_image_font].buf_type, + buf_width * buf_height * 2) != 0) { + vid->font_driver->free(vid->font); + return -1; + } + + src->width = buf_width; + src->height = buf_height; + src->stride = buf_width * 2; + +#if (EXYNOS_GFX_DEBUG_LOG == 1) + RARCH_LOG("video_exynos: using font rendering image with size %ux%u\n", + buf_width, buf_height); +#endif + + return 0; +} + +static int exynos_render_msg(struct exynos_video *vid, + const char *msg) { + struct exynos_data *pdata = vid->data; + struct g2d_image *dst = pdata->src[exynos_image_font]; + + struct font_output_list out; + struct font_output *head; + int ret; + + const int msg_base_x = g_settings.video.msg_pos_x * dst->width; + const int msg_base_y = (1.0f - g_settings.video.msg_pos_y) * dst->height; + + if (vid->font == NULL || vid->font_driver == NULL) + return -1; + + if (clear_buffer(pdata->g2d, dst) != 0) + return -1; + + vid->font_driver->render_msg(vid->font, msg, &out); + + for (head = out.head; head; head = head->next) { + int base_x = msg_base_x + head->off_x; + int base_y = msg_base_y - head->off_y - head->height; + + const int max_width = dst->width - base_x; + const int max_height = dst->height - base_y; + + int glyph_width = head->width; + int glyph_height = head->height; + + const uint8_t *src = head->output; + + if (base_x < 0) { + src -= base_x; + glyph_width += base_x; + base_x = 0; + } + + if (base_y < 0) { + src -= base_y * (int)head->pitch; + glyph_height += base_y; + base_y = 0; + } + + if (max_width <= 0 || max_height <= 0) continue; + + if (glyph_width > max_width) glyph_width = max_width; + if (glyph_height > max_height) glyph_height = max_height; + + put_glyph_rgba4444(pdata, src, vid->font_rgb, + glyph_width, glyph_height, + head->pitch, base_x, base_y); + } + + ret = exynos_blend_font(pdata); + + vid->font_driver->free_output(vid->font, &out); + + return ret; +} + + +static void *exynos_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data) { + struct exynos_video *vid; + + const unsigned fb_bpp = 4; /* Use XRGB8888 framebuffer. */ + + vid = calloc(1, sizeof(struct exynos_video)); + if (!vid) return NULL; + + vid->data = calloc(1, sizeof(struct exynos_data)); + if (!vid->data) goto fail_data; + + vid->bytes_per_pixel = video->rgb32 ? 4 : 2; + + if (exynos_open(vid->data) != 0) { + RARCH_ERR("video_exynos: opening device failed\n"); + goto fail; + } + + if (exynos_init(vid->data, fb_bpp) != 0) { + RARCH_ERR("video_exynos: initialization failed\n"); + goto fail_init; + } + + if (exynos_alloc(vid->data) != 0) { + RARCH_ERR("video_exynos: allocation failed\n"); + goto fail_alloc; + } + + if (exynos_g2d_init(vid->data) != 0) { + RARCH_ERR("video_exynos: G2D initialization failed\n"); + goto fail_g2d; + } + +#if (EXYNOS_GFX_DEBUG_LOG == 1) + exynos_alloc_status(vid->data); +#endif + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_init(&vid->data->perf); +#endif + + if (input && input_data) { + *input = NULL; + } + + if (exynos_init_font(vid) != 0) { + RARCH_ERR("video_exynos: font initialization failed\n"); + goto fail_font; + } + + return vid; + +fail_font: + exynos_g2d_free(vid->data); + +fail_g2d: + exynos_free(vid->data); + +fail_alloc: + exynos_deinit(vid->data); + +fail_init: + exynos_close(vid->data); + +fail: + free(vid->data); + +fail_data: + free(vid); + return NULL; +} + +static void exynos_gfx_free(void *data) { + struct exynos_video *vid = data; + struct exynos_data *pdata; + + if (!vid) return; + + pdata = vid->data; + + exynos_g2d_free(pdata); + + /* Flush pages: One page remains, the one being displayed at this moment. */ + while (pages_used(pdata->pages, pdata->num_pages) > 1) { + wait_flip(pdata->fliphandler); + } + + exynos_free(pdata); + exynos_deinit(pdata); + exynos_close(pdata); + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_finish(&pdata->perf); +#endif + + free(pdata); + + if (vid->font != NULL && vid->font_driver != NULL) + vid->font_driver->free(vid->font); + + free(vid); +} + +static bool exynos_gfx_frame(void *data, const void *frame, unsigned width, + unsigned height, unsigned pitch, const char *msg) { + struct exynos_video *vid = data; + struct exynos_page *page = NULL; + + /* Check if neither RGUI nor emulator framebuffer is to be displayed. */ + if (!vid->rgui_active && frame == NULL) return true; + + if (frame != NULL) { + if (width != vid->width || height != vid->height) { + /* Sanity check on new dimension parameters. */ + if (width == 0 || height == 0) return true; + + RARCH_LOG("video_exynos: resolution changed by core: %ux%u -> %ux%u\n", + vid->width, vid->height, width, height); + exynos_setup_scale(vid->data, width, height, vid->bytes_per_pixel); + + vid->width = width; + vid->height = height; + } + + page = exynos_free_page(vid->data); + + if (exynos_blit_frame(vid->data, frame, pitch) != 0) + goto fail; + } + + if (g_settings.fps_show) { + char buffer[128], buffer_fps[128]; + + gfx_get_fps(buffer, sizeof(buffer), g_settings.fps_show ? buffer_fps : NULL, sizeof(buffer_fps)); + msg_queue_push(g_extern.msg_queue, buffer_fps, 1, 1); + } + + if (vid->width == 0 || vid->height == 0) { + /* If at this point the dimension parameters are still zero, setup some * + * fake blit parameters so that RGUI and font rendering work properly. */ + exynos_set_fake_blit(vid->data); + } + + if (page == NULL) + page = exynos_free_page(vid->data); + + if (vid->rgui_active) { + if (exynos_blend_rgui(vid->data, vid->rgui_rotation) != 0) + goto fail; + } + + if (msg) { + if (exynos_render_msg(vid, msg) != 0) goto fail; + + /* Font is blitted to the entire screen, so issue clear afterwards. */ + page->clear = true; + } + + if (exynos_flip(vid->data, page) != 0) goto fail; + + g_extern.frame_count++; + + return true; + +fail: + /* Since we didn't managed to issue a pageflip to this page, set * + * it to 'unused' again, and hope that it works next time. */ + page->used = false; + + return false; +} + +static void exynos_gfx_set_nonblock_state(void *data, bool state) { + struct exynos_video *vid = data; + + vid->data->sync = !state; +} + +static bool exynos_gfx_alive(void *data) { + (void)data; + return true; /* always alive */ +} + +static bool exynos_gfx_focus(void *data) { + (void)data; + return true; /* drm device always has focus */ +} + +static void exynos_gfx_set_rotation(void *data, unsigned rotation) { + struct exynos_video *vid = data; + + vid->rgui_rotation = rotation; +} + +static void exynos_gfx_viewport_info(void *data, struct rarch_viewport *vp) { + struct exynos_video *vid = data; + + vp->x = vp->y = 0; + + vp->width = vp->full_width = vid->width; + vp->height = vp->full_height = vid->height; +} + +static void exynos_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) { + struct exynos_video *vid = data; + + switch (aspect_ratio_idx) { + case ASPECT_RATIO_SQUARE: + gfx_set_square_pixel_viewport(g_extern.system.av_info.geometry.base_width, g_extern.system.av_info.geometry.base_height); + break; + + case ASPECT_RATIO_CORE: + gfx_set_core_viewport(); + break; + + case ASPECT_RATIO_CONFIG: + gfx_set_config_viewport(); + break; + + default: + break; + } + + g_extern.system.aspect_ratio = aspectratio_lut[aspect_ratio_idx].value; + vid->aspect_changed = true; +} + +static void exynos_apply_state_changes(void *data) { + (void)data; +} + +static void exynos_set_texture_frame(void *data, const void *frame, bool rgb32, + unsigned width, unsigned height, float alpha) { + const enum exynos_buffer_type buf_type = defaults[exynos_image_rgui].buf_type; + + struct exynos_video *vid = data; + struct exynos_data *pdata = vid->data; + struct g2d_image *src = pdata->src[exynos_image_rgui]; + + const unsigned size = width * height * (rgb32 ? 4 : 2); + + if (realloc_buffer(pdata, buf_type, size) != 0) + return; + + src->width = width; + src->height = height; + src->stride = width * (rgb32 ? 4 : 2); + src->color_mode = rgb32 ? G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_RGBAX : + G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_RGBAX; + + src->component_alpha = (unsigned char)(255.0f * alpha); + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_memcpy(&pdata->perf, true); +#endif + + memcpy_neon(pdata->buf[buf_type]->vaddr, frame, size); + +#if (EXYNOS_GFX_DEBUG_PERF == 1) + perf_memcpy(&pdata->perf, false); +#endif +} + +static void exynos_set_texture_enable(void *data, bool state, bool full_screen) { + struct exynos_video *vid = data; + vid->rgui_active = state; +} + +static void exynos_set_osd_msg(void *data, const char *msg, void *userdata) { + struct exynos_video *vid = data; + + /* TODO: what does this do? */ + (void)msg; + (void)userdata; +} + +static void exynos_show_mouse(void *data, bool state) { + (void)data; +} + +static const video_poke_interface_t exynos_poke_interface = { + NULL, /* set_filtering */ +#ifdef HAVE_FBO + NULL, /* get_current_framebuffer */ + NULL, /* get_proc_address */ +#endif + exynos_set_aspect_ratio, + exynos_apply_state_changes, +#if defined(HAVE_RGUI) || defined(HAVE_RMENU) /* TODO: only HAVE_MENU i think? */ + exynos_set_texture_frame, + exynos_set_texture_enable, +#endif + exynos_set_osd_msg, + exynos_show_mouse +}; + +static void exynos_gfx_get_poke_interface(void *data, const video_poke_interface_t **iface) { + (void)data; + *iface = &exynos_poke_interface; +} + +const video_driver_t video_exynos = { + exynos_gfx_init, + exynos_gfx_frame, + exynos_gfx_set_nonblock_state, + exynos_gfx_alive, + exynos_gfx_focus, + NULL, /* set_shader */ + exynos_gfx_free, + "exynos", + +#ifdef HAVE_MENU + NULL, /* restart */ +#endif + + exynos_gfx_set_rotation, + exynos_gfx_viewport_info, + NULL, /* read_viewport */ + +#ifdef HAVE_OVERLAY + NULL, /* overlay_interface */ +#endif + exynos_gfx_get_poke_interface +}; diff --git a/memcpy-neon.S b/memcpy-neon.S new file mode 100644 index 0000000000..a26ace3a75 --- /dev/null +++ b/memcpy-neon.S @@ -0,0 +1,139 @@ +/* + * NEON code contributed by Siarhei Siamashka . + * Origin: http://sourceware.org/ml/libc-ports/2009-07/msg00003.html + * + * The GNU C Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License. + * + * Tweaked for Android by Jim Huang + */ + +.arm +.fpu neon + +.global memcpy_neon + +/* + * ENABLE_UNALIGNED_MEM_ACCESSES macro can be defined to permit the use + * of unaligned load/store memory accesses supported since ARMv6. This + * will further improve performance, but can purely theoretically cause + * problems if somebody decides to set SCTLR.A bit in the OS kernel + * (to trap each unaligned memory access) or somehow mess with strongly + * ordered/device memory. + */ +#define ENABLE_UNALIGNED_MEM_ACCESSES 1 + +#define NEON_MAX_PREFETCH_DISTANCE 320 + +.align 4 +memcpy_neon: + .fnstart + mov ip, r0 + cmp r2, #16 + blt 4f @ Have less than 16 bytes to copy + + @ First ensure 16 byte alignment for the destination buffer + tst r0, #0xF + beq 2f + tst r0, #1 + ldrneb r3, [r1], #1 + strneb r3, [ip], #1 + subne r2, r2, #1 + tst ip, #2 +#ifdef ENABLE_UNALIGNED_MEM_ACCESSES + ldrneh r3, [r1], #2 + strneh r3, [ip], #2 +#else + ldrneb r3, [r1], #1 + strneb r3, [ip], #1 + ldrneb r3, [r1], #1 + strneb r3, [ip], #1 +#endif + subne r2, r2, #2 + + tst ip, #4 + beq 1f + vld4.8 {d0[0], d1[0], d2[0], d3[0]}, [r1]! + vst4.8 {d0[0], d1[0], d2[0], d3[0]}, [ip, :32]! + sub r2, r2, #4 +1: + tst ip, #8 + beq 2f + vld1.8 {d0}, [r1]! + vst1.8 {d0}, [ip, :64]! + sub r2, r2, #8 +2: + subs r2, r2, #32 + blt 3f + mov r3, #32 + + @ Main copy loop, 32 bytes are processed per iteration. + @ ARM instructions are used for doing fine-grained prefetch, + @ increasing prefetch distance progressively up to + @ NEON_MAX_PREFETCH_DISTANCE at runtime +1: + vld1.8 {d0-d3}, [r1]! + cmp r3, #(NEON_MAX_PREFETCH_DISTANCE - 32) + pld [r1, r3] + addle r3, r3, #32 + vst1.8 {d0-d3}, [ip, :128]! + sub r2, r2, #32 + cmp r2, r3 + bge 1b + cmp r2, #0 + blt 3f +1: @ Copy the remaining part of the buffer (already prefetched) + vld1.8 {d0-d3}, [r1]! + subs r2, r2, #32 + vst1.8 {d0-d3}, [ip, :128]! + bge 1b +3: @ Copy up to 31 remaining bytes + tst r2, #16 + beq 4f + vld1.8 {d0, d1}, [r1]! + vst1.8 {d0, d1}, [ip, :128]! +4: + @ Use ARM instructions exclusively for the final trailing part + @ not fully fitting into full 16 byte aligned block in order + @ to avoid "ARM store after NEON store" hazard. Also NEON + @ pipeline will be (mostly) flushed by the time when the + @ control returns to the caller, making the use of NEON mostly + @ transparent (and avoiding hazards in the caller code) + +#ifdef ENABLE_UNALIGNED_MEM_ACCESSES + movs r3, r2, lsl #29 + ldrcs r3, [r1], #4 + strcs r3, [ip], #4 + ldrcs r3, [r1], #4 + strcs r3, [ip], #4 + ldrmi r3, [r1], #4 + strmi r3, [ip], #4 + movs r2, r2, lsl #31 + ldrcsh r3, [r1], #2 + strcsh r3, [ip], #2 + ldrmib r3, [r1], #1 + strmib r3, [ip], #1 +#else + movs r3, r2, lsl #29 + bcc 1f + .rept 8 + ldrcsb r3, [r1], #1 + strcsb r3, [ip], #1 + .endr +1: + bpl 1f + .rept 4 + ldrmib r3, [r1], #1 + strmib r3, [ip], #1 + .endr +1: + movs r2, r2, lsl #31 + ldrcsb r3, [r1], #1 + strcsb r3, [ip], #1 + ldrcsb r3, [r1], #1 + strcsb r3, [ip], #1 + ldrmib r3, [r1], #1 + strmib r3, [ip], #1 +#endif + bx lr + .fnend diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 1719b6daac..728fbaf321 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -85,6 +85,11 @@ if [ "$HAVE_EGL" != "no" ]; then fi fi +if [ "$HAVE_EXYNOS" != "no" ]; then + check_pkgconf EXYNOS libdrm_exynos + check_pkgconf DRM libdrm +fi + if [ "$LIBRETRO" ]; then echo "Explicit libretro used, disabling dynamic libretro loading ..." HAVE_DYNAMIC='no' @@ -276,6 +281,7 @@ add_define_make OS "$OS" # Creates config.mk and config.h. add_define_make GLOBAL_CONFIG_DIR "$GLOBAL_CONFIG_DIR" -VARS="RGUI LAKKA ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL LIMA OMAP GLES GLES3 VG EGL KMS GBM DRM DYLIB GETOPT_LONG THREADS CG LIBXML2 ZLIB DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE FREETYPE XKBCOMMON XVIDEO X11 XEXT XF86VM XINERAMA MALI_FBDEV NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL STRCASESTR MMAP PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 BSV_MOVIE VIDEOCORE NEON FLOATHARD FLOATSOFTFP UDEV V4L2 AV_CHANNEL_LAYOUT" +VARS="RGUI LAKKA ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL LIMA OMAP GLES GLES3 VG EGL KMS EXYNOS GBM DRM DYLIB GETOPT_LONG THREADS CG LIBXML2 ZLIB DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE FREETYPE XKBCOMMON XVIDEO X11 XEXT XF86VM XINERAMA MALI_FBDEV NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL STRCASESTR MMAP PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 BSV_MOVIE VIDEOCORE NEON FLOATHARD FLOATSOFTFP UDEV V4L2 AV_CHANNEL_LAYOUT" +>>>>>>> Add exynos video driver create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 88d50a3cb7..6cd98f20c0 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -20,6 +20,7 @@ HAVE_LIMA=no # Enable Lima video support HAVE_OMAP=no # Enable OMAP video support HAVE_XINERAMA=auto # Disable Xinerama support. HAVE_KMS=auto # Enable KMS context support +HAVE_EXYNOS=no # Enable Exynos video support HAVE_EGL=auto # Enable EGL context support HAVE_VG=auto # Enable OpenVG support HAVE_CG=auto # Enable Cg shader support diff --git a/settings.c b/settings.c index 4dc5c68d59..09aff4d999 100644 --- a/settings.c +++ b/settings.c @@ -109,6 +109,8 @@ const char *config_get_default_video(void) return "null"; case VIDEO_OMAP: return "omap"; + case VIDEO_EXYNOS: + return "exynos"; default: return NULL; } From 9a38d776559beac870d4eed356beb60fe7bf906e Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Thu, 29 May 2014 01:22:16 +0200 Subject: [PATCH 2/7] Remove the lima video driver The driver is now superseded by the exynos video driver. Also limare only works up to mali kernelspace driver version r3p2, which makes the driver nonfunctional with the latest r4p0. --- Makefile | 5 - README-lima.md | 42 -- driver.c | 3 - driver.h | 1 - gfx/lima_gfx.c | 958 -------------------------------------------- qb/config.libs.sh | 8 +- qb/config.params.sh | 1 - 7 files changed, 1 insertion(+), 1017 deletions(-) delete mode 100644 README-lima.md delete mode 100644 gfx/lima_gfx.c diff --git a/Makefile b/Makefile index caa0dcde4f..cabf3d93d1 100644 --- a/Makefile +++ b/Makefile @@ -211,11 +211,6 @@ ifeq ($(HAVE_SDL), 1) LIBS += $(SDL_LIBS) endif -ifeq ($(HAVE_LIMA), 1) - OBJ += gfx/lima_gfx.o - LIBS += -llimare -endif - ifeq ($(HAVE_OMAP), 1) OBJ += gfx/omap_gfx.o endif diff --git a/README-lima.md b/README-lima.md deleted file mode 100644 index 493efa03b4..0000000000 --- a/README-lima.md +++ /dev/null @@ -1,42 +0,0 @@ -# RetroArch Lima video driver - -The Lima video driver for RetroArch uses the open-source Lima driver, which implements the userspace code to enable the Mali GPU contained in a lot of ARM SoC. At the time of writing (24/01/2014) the Lima driver supports GPUs of the type Mali-200 and Mali-400. The full driver stack to enable the Mali GPU is comprised of a part in kernelspace, which is available as open-source from ARM itself, and the aforementioned userspace part, which ARM only supplies as a binary blob. - -## Reasons to use the driver - -The original binary blob provides hardware-accelerated GLES 2.0 rendering through EGL. Depending on which blob one uses, rendering is either done to a framebuffer provided by a fbdev device or a framebuffer provided by a X11 window. None of these choices are particular good and are also not very performant. - -The author uses a Hardkernel ODROID-X2, which is an developer board powered by an Exynos4412 SoC. This SoC incorporates a Mali-400 GPU and dedicated blocks for 2D acceleration and HDMI interfacing. The non-Mali graphics blocks functionality of the SoC is exported through a DRM driver (Exynos DRM). - -The DRM exposes a fbdev device through an emulation layer. The layer introduces overhead and also doesn't provide any decent support for proper vertical synchronisation, ruining the experience with lots of tearing artifacts. Switching back and forth between fbdev and X11 solved neither the vsync nor the performance issue. - -Users with similar experiences on a Exynos4-based hardware (coupled with a Mali GPU supported by the Lima driver) are invited to try this driver. - -## Configuration - -The original Lima driver suffers from a similar problem as the blob, since it can only render into a framebuffer provided by a fbdev device. In the repository mentioned below you can find a modified Lima version, which can utilize the Exynos DRM directly. - -[lima-drm repository](https://github.com/tobiasjakobi/lima-drm) - -The Lima video driver for RetroArch only works with this version. Proceed with the usual steps to install limare from the repository onto your system. Make sure that you have a recent version of [libdrm](http://cgit.freedesktop.org/mesa/drm/) installed on your system, and that Exynos API support is enabled in libdrm. If you're compiling libdrm from source, then use - - ./configure --enable-exynos-experimental-api - -to enable the Exynos API. After finishing the limare build, compile RetroArch against the resulting limare library (*liblimare.so*). I usually just skip the make install step and manually place the library and header (which is *limare.h*) in *$HOME/local/lib/* and *$HOME/local/include/* respectively (this requires adjustements for *LD_LIBRARY_PATH* and *CFLAGS*). - -The video driver name is 'lima'. It honors the following video settings: - - - video\_monitor\_index - - video\_fullscreen\_x and video\_fullscreen\_y - -The monitor index maps to the DRM connector index. If it's zero, then it just selects the first "sane" connector, which means that it is connected to a display device and it provides at least one useable mode. If the value is non-zero, it forces the selection of this connector. For example, on the ODROID-X2 the HDMI connector has index 2. - -The two fullscreen parameters select the mode the DRM should select. If zero, the native connector mode is selected. If non-zero, the DRM tries to select the wanted mode. This might fail if the mode is not available from the connector. - -## Issues and TODOs - -The driver still suffers from some issues. - - - The aspect ratio is wrong. The dimensions of the emulator framebuffer on the screen are not computed correctly at the moment. - - Limare should be able to handle a custom pitch, when uploading texture pixel data. This would save some memcpy for emulator cores which don't provide the framebuffer with full pitch (snes9x-next for example). - - Font rendering is kinda inefficient, since the whole font texture is invalidated each frame. It would be better to introduce something like an invalidated rectangle, which tracks the region which needs to be updated. diff --git a/driver.c b/driver.c index 94c80821d1..839eba3290 100644 --- a/driver.c +++ b/driver.c @@ -134,9 +134,6 @@ static const video_driver_t *video_drivers[] = { #ifdef HAVE_NULLVIDEO &video_null, #endif -#ifdef HAVE_LIMA - &video_lima, -#endif #ifdef HAVE_OMAP &video_omap, #endif diff --git a/driver.h b/driver.h index e4c268b7e5..6f020fd3c1 100644 --- a/driver.h +++ b/driver.h @@ -624,7 +624,6 @@ extern const video_driver_t video_xdk_d3d; extern const video_driver_t video_sdl; extern const video_driver_t video_vg; extern const video_driver_t video_null; -extern const video_driver_t video_lima; extern const video_driver_t video_omap; extern const video_driver_t video_exynos; extern const input_driver_t input_android; diff --git a/gfx/lima_gfx.c b/gfx/lima_gfx.c deleted file mode 100644 index e45fcfb156..0000000000 --- a/gfx/lima_gfx.c +++ /dev/null @@ -1,958 +0,0 @@ -/* RetroArch - A frontend for libretro. - * Copyright (C) 2013-2014 - Tobias Jakobi - * Copyright (C) 2013-2014 - Daniel Mehrwald - * - * 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 "../general.h" -#include "gfx_common.h" -#include "fonts/fonts.h" - -/* Rename to LIMA_GFX_DEBUG to enable debugging code. */ -#define NO_LIMA_GFX_DEBUG 1 - -/* Current limare only natively supports a limited amount of formats for texture * - * data. We compensate for this limitation by swizzling the texture data in the * - * pixel shader. */ - -#define LIMA_TEXEL_FORMAT_BGR_565 0x0e -#define LIMA_TEXEL_FORMAT_RGBA_5551 0x0f -#define LIMA_TEXEL_FORMAT_RGBA_4444 0x10 -#define LIMA_TEXEL_FORMAT_RGBA_8888 0x16 - -/* Limare is currently unable to deallocate individual texture objects and * - * only allows to destroy all objects at once. * - * We only create a maximum of 12 objects, before doing a full "reset", or * - * sooner, under the condition that limare's texture memory runs out. */ -static const unsigned num_max_textures = 12; - -typedef struct limare_state limare_state_t; - -typedef struct limare_texture { - unsigned width; - unsigned height; - - int handle; - unsigned format; - - bool menu; -} limare_texture_t; - -typedef struct vec2f { - float x, y; -} vec2f_t; - -typedef struct vec3f { - float x, y, z; -} vec3f_t; - -/* Create three shader programs. One is for displaying only the emulator core pixel data. * - * The other two are for displaying the menu, where the pixel data can be provided in * - * two different formats. Current RetroArch only seems to ever use a single format, but * - * this is not set in stone, therefore making two programs necessary. */ - -typedef struct limare_data { - limare_state_t *state; - - int program; - - int program_menu_rgba16; - int program_menu_rgba32; - - float screen_aspect; - float frame_aspect; - - unsigned upload_format; - unsigned upload_bpp; /* bytes per pixel */ - - vec3f_t *vertices; - vec2f_t *coords; - - /* Generic buffer to create contiguous pixel data for limare - * or to use for font blitting. */ - void *buffer; - unsigned buffer_size; - - limare_texture_t **textures; - unsigned texture_slots; - - limare_texture_t *cur_texture; - limare_texture_t *cur_texture_menu; - - unsigned font_width; - unsigned font_height; - limare_texture_t *font_texture; -} limare_data_t; - -/* Header for simple vertex shader. */ -static const char *vshader_src = - "attribute vec4 in_vertex;\n" - "attribute vec2 in_coord;\n" - "\n" - "varying vec2 coord;\n" - "\n" - "void main()\n" - "{\n" - " gl_Position = in_vertex;\n" - " coord = in_coord;\n" - "}\n"; - -/* Header for simple fragment shader. */ -static const char *fshader_header_src = - "precision highp float;\n" - "\n" - "varying vec2 coord;\n" - "\n" - "uniform sampler2D in_texture;\n" - "\n"; - -/* Main (template) for simple fragment shader. */ -static const char *fshader_main_src = - "void main()\n" - "{\n" - " vec3 pixel = texture2D(in_texture, coord)%s;\n" - " gl_FragColor = vec4(pixel, 1.0);\n" - "}\n"; - -/* Header for menu fragment shader. */ -/* Use mediump, which makes uColor into a (single-precision) float[4]. */ -static const char *fshader_menu_header_src = - "precision mediump float;\n" - "\n" - "varying vec2 coord;\n" - "uniform vec4 uColor;\n" - "\n" - "uniform sampler2D in_texture;\n" - "\n"; - -/* Main (template) for menu fragment shader. */ -static const char *fshader_menu_main_src = - "void main()\n" - "{\n" - " vec4 pixel = texture2D(in_texture, coord)%s;\n" - " gl_FragColor = pixel * uColor;\n" - "}\n"; - -static inline void put_pixel_rgba4444(uint16_t *p, unsigned r, unsigned g, unsigned b, unsigned a) { - *p = (a >> 4) | ((b >> 4) << 4) | ((g >> 4) << 8) | ((r >> 4) << 12); -} - -static inline unsigned align_common(unsigned i, unsigned j) { - return (i + j - 1) & ~(j - 1); -} - -static float get_screen_aspect(limare_state_t *state) { - unsigned w = 0, h = 0; - - limare_buffer_size(state, &w, &h); - - if (w != 0 && h != 0) { - return (float)w / (float)h; - } - - return 0.0f; -} - -static void apply_aspect(limare_data_t *pdata, float ratio) { - vec3f_t *vertices = pdata->vertices; - float x, y; - - if (fabsf(pdata->screen_aspect - pdata->frame_aspect) < 0.0001f) { - x = 1.0f; - y = 1.0f; - } else { - if (pdata->screen_aspect > pdata->frame_aspect) { - x = pdata->frame_aspect / pdata->screen_aspect; - y = 1.0f; - } else { - x = 1.0f; - y = pdata->screen_aspect / pdata->frame_aspect; - } - } - - /* TODO: use ratio parameter */ - - vertices[0].x = vertices[2].x = -x; - vertices[1].x = vertices[3].x = x; - - vertices[0].y = vertices[1].y = -y; - vertices[2].y = vertices[3].y = y; -} - -static int destroy_textures(limare_data_t *pdata) { - unsigned i; - int ret; - - pdata->cur_texture = NULL; - pdata->cur_texture_menu = NULL; - - for (i = 0; i < pdata->texture_slots; ++i) { - free(pdata->textures[i]); - pdata->textures[i] = NULL; - } - - ret = limare_texture_cleanup(pdata->state); - pdata->texture_slots = 0; - - return ret; -} - -static limare_texture_t *get_texture_handle(limare_data_t *pdata, - unsigned width, unsigned height, unsigned format) { - unsigned i; - - format = (format == 0) ? pdata->upload_format : format; - - for (i = 0; i < pdata->texture_slots; ++i) { - if (pdata->textures[i]->width == width && - pdata->textures[i]->height == height && - pdata->textures[i]->format == format) return pdata->textures[i]; - } - - if (pdata->texture_slots == num_max_textures) { - /* All texture slots are used, do a reset. */ - if (destroy_textures(pdata)) { - RARCH_ERR("video_lima: failed to reset texture storage\n"); - } - } - - return NULL; -} - -static limare_texture_t *add_texture(limare_data_t *pdata, - unsigned width, unsigned height, - const void *pixels, unsigned format) { - int texture = -1; - unsigned retries = 2; - const unsigned i = pdata->texture_slots; - - format = (format == 0) ? pdata->upload_format : format; - - /* limare_texture_upload returns -1 when the upload fails for some reason. */ - while (texture == -1 && retries > 0) { - texture = limare_texture_upload(pdata->state, pixels, width, height, format, 0); - - if (texture != -1) break; - - destroy_textures(pdata); - retries--; - } - - if (texture == -1) return NULL; - - /* Set magnification to linear and minification to nearest, since we will * - * probably only ever scale the image to larger dimensions. Also set * - * wrap mode for both coords to clamp, which should eliminate some artifacts. */ - limare_texture_parameters(pdata->state, texture, GL_LINEAR, GL_NEAREST, - GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); - - pdata->textures[i] = calloc(1, sizeof(limare_texture_t)); - - pdata->textures[i]->width = width; - pdata->textures[i]->height = height; - pdata->textures[i]->handle = texture; - pdata->textures[i]->format = format; - - pdata->texture_slots++; - - return pdata->textures[i]; -} - -static const void *make_contiguous(limare_data_t *pdata, - unsigned width, unsigned height, - const void *pixels, unsigned bpp, - unsigned pitch) { - unsigned i; - unsigned full_pitch; - - bpp = (bpp == 0) ? pdata->upload_bpp : bpp; - full_pitch = width * bpp; - - if (full_pitch == pitch) return pixels; - - RARCH_LOG("video_lima: input buffer not contiguous\n"); - - /* Enlarge our buffer, if it is currently too small. */ - if (pdata->buffer_size < full_pitch * height) { - const unsigned aligned_size = align_common(full_pitch * height, 16); - - free(pdata->buffer); - pdata->buffer = NULL; - - posix_memalign(&pdata->buffer, 16, aligned_size); - if (pdata->buffer == NULL) { - RARCH_ERR("video_lima: failed to allocate buffer to make pixel data contiguous\n"); - return NULL; - } - - pdata->buffer_size = aligned_size; - } - - for (i = 0; i < height; ++i) { - memcpy(pdata->buffer + i * full_pitch, pixels + i * pitch, full_pitch); - } - - return pdata->buffer; -} - -#ifdef LIMA_GFX_DEBUG -static void print_status(limare_data_t *pdata) { - unsigned i; - - RARCH_LOG("video_lima: upload format = 0x%x, upload bpp = %u\n", pdata->upload_format, pdata->upload_bpp); - RARCH_LOG("video_lima: buffer at %p, buffer size = %u\n", pdata->buffer, pdata->buffer_size); - RARCH_LOG("video_lima: used texture slots = %u (from %u)\n", pdata->texture_slots, num_max_textures); - - for (i = 0; i < pdata->texture_slots; ++i) { - RARCH_LOG("video_lima: texture slot %u, width = %u, height = %u, handle = %u, format = 0x%x\n", - i, pdata->textures[i]->width, pdata->textures[i]->height, - pdata->textures[i]->handle, pdata->textures[i]->format); - } -} -#endif - -static void destroy_data(limare_data_t *pdata) { - free(pdata->vertices); - free(pdata->coords); -} - -static int setup_data(limare_data_t *pdata) { - static const unsigned num_verts = 4; - static const unsigned num_coords = 4 * 4; - unsigned i; - - static const vec3f_t vertices[4] = { - {-1.0f, -1.0f, 0.0f}, - { 1.0f, -1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f}, - { 1.0f, 1.0f, 0.0f} - }; - - static const vec2f_t coords[16] = { - {0.0f, 1.0f}, {1.0f, 1.0f}, /* 0 degrees */ - {0.0f, 0.0f}, {1.0f, 0.0f}, - {0.0f, 0.0f}, {0.0f, 1.0f}, /* 90 degrees */ - {1.0f, 0.0f}, {1.0f, 1.0f}, - {1.0f, 0.0f}, {0.0f, 0.0f}, /* 180 degrees */ - {1.0f, 1.0f}, {0.0f, 1.0f}, - {1.0f, 1.0f}, {1.0f, 0.0f}, /* 270 degrees */ - {0.0f, 1.0f}, {0.0f, 0.0f} - }; - - pdata->vertices = calloc(num_verts, sizeof(vec3f_t)); - if (pdata->vertices == NULL) goto fail; - - pdata->coords = calloc(num_coords, sizeof(vec2f_t)); - if (pdata->coords == NULL) goto fail; - - for (i = 0; i < num_verts; ++i) { - pdata->vertices[i] = vertices[i]; - } - - for (i = 0; i < num_coords; ++i) { - pdata->coords[i] = coords[i]; - } - - return 0; - -fail: - return -1; -} - -static int create_programs(limare_data_t *pdata) { - char tmpbufm[1024]; /* temp buffer for main function */ - char tmpbuf[1024]; /* temp buffer for whole program */ - - const char* swz = (pdata->upload_bpp == 4) ? ".bgr" : ".rgb"; - - /* Create shader program for regular operation first. */ - pdata->program = limare_program_new(pdata->state); - if (pdata->program < 0) goto fail; - - snprintf(tmpbufm, 1024, fshader_main_src, swz); - strncpy(tmpbuf, fshader_header_src, 1024); - strcat(tmpbuf, tmpbufm); - - if (vertex_shader_attach(pdata->state, pdata->program, vshader_src)) goto fail; - if (fragment_shader_attach(pdata->state, pdata->program, tmpbuf)) goto fail; - if (limare_link(pdata->state)) goto fail; - - /* Create shader program for menu with RGBA4444 pixel data. */ - pdata->program_menu_rgba16 = limare_program_new(pdata->state); - if (pdata->program_menu_rgba16 < 0) goto fail; - - snprintf(tmpbufm, 1024, fshader_menu_main_src, ".abgr"); - strncpy(tmpbuf, fshader_menu_header_src, 1024); - strcat(tmpbuf, tmpbufm); - - if (vertex_shader_attach(pdata->state, pdata->program_menu_rgba16, vshader_src)) goto fail; - if (fragment_shader_attach(pdata->state, pdata->program_menu_rgba16, tmpbuf)) goto fail; - if (limare_link(pdata->state)) goto fail; - - /* Create shader program for menu with RGBA8888 pixel data. */ - pdata->program_menu_rgba32 = limare_program_new(pdata->state); - if (pdata->program_menu_rgba32 < 0) goto fail; - - snprintf(tmpbufm, 1024, fshader_menu_main_src, ".abgr"); - strncpy(tmpbuf, fshader_menu_header_src, 1024); - strcat(tmpbuf, tmpbufm); - - if (vertex_shader_attach(pdata->state, pdata->program_menu_rgba32, vshader_src)) goto fail; - if (fragment_shader_attach(pdata->state, pdata->program_menu_rgba32, tmpbuf)) goto fail; - if (limare_link(pdata->state)) goto fail; - - return 0; - -fail: - return -1; -} - -static void put_glyph_rgba4444(limare_data_t *pdata, const uint8_t *src, uint8_t *f_rgb, - unsigned g_width, unsigned g_height, unsigned g_pitch, - unsigned dst_x, unsigned dst_y) { - unsigned x, y; - uint16_t *dst; - - dst = (uint16_t*)pdata->buffer + dst_y * pdata->font_width + dst_x; - - for (y = 0; y < g_height; ++y, src += g_pitch, dst += pdata->font_width) { - for (x = 0; x < g_width; ++x) { - const uint8_t blend = src[x]; - - if (blend != 0) put_pixel_rgba4444(&dst[x], f_rgb[0], f_rgb[1], f_rgb[2], blend); - } - } -} - -typedef struct lima_video { - limare_data_t *lima; - - void *font; - const font_renderer_driver_t *font_driver; - uint8_t font_rgb[4]; - - /* current dimensions */ - unsigned width; - unsigned height; - - /* MENU data */ - int menu_rotation; - float menu_alpha; - bool menu_active; - bool menu_rgb32; - - bool aspect_changed; - -} lima_video_t; - -static void lima_gfx_free(void *data) { - lima_video_t *vid = data; - if (!vid) return; - - if (vid->lima && vid->lima->state) limare_finish(vid->lima->state); - if (vid->font) vid->font_driver->free(vid->font); - - destroy_data(vid->lima); - destroy_textures(vid->lima); - free(vid->lima->textures); - - free(vid->lima); - free(vid); -} - -static void lima_init_font(lima_video_t *vid, const char *font_path, unsigned font_size) { - if (!g_settings.video.font_enable) return; - - if (font_renderer_create_default(&vid->font_driver, &vid->font, - *g_settings.video.font_path ? g_settings.video.font_path : NULL, g_settings.video.font_size)) { - int r = g_settings.video.msg_color_r * 255; - int g = g_settings.video.msg_color_g * 255; - int b = g_settings.video.msg_color_b * 255; - - vid->font_rgb[0] = r < 0 ? 0 : (r > 255 ? 255 : r); - vid->font_rgb[1] = g < 0 ? 0 : (g > 255 ? 255 : g); - vid->font_rgb[2] = b < 0 ? 0 : (b > 255 ? 255 : b); - } else { - RARCH_LOG("video_lima: font init failed\n"); - } -} - -static void lima_render_msg(lima_video_t *vid, const char *msg) { - unsigned req_size; - limare_data_t *lima = vid->lima; - - int msg_base_x = g_settings.video.msg_pos_x * lima->font_width; - int msg_base_y = (1.0 - g_settings.video.msg_pos_y) * lima->font_height; - - if (vid->font == NULL) return; - - /* Font texture uses RGBA4444 pixel data (2 bytes per pixel). */ - req_size = lima->font_width * lima->font_height * 2; - - if (lima->buffer_size < req_size) { - const unsigned aligned_size = align_common(req_size, 16); - - free(lima->buffer); - lima->buffer = NULL; - - posix_memalign(&lima->buffer, 16, aligned_size); - if (lima->buffer == NULL) { - RARCH_ERR("video_lima: failed to allocate buffer to render fonts\n"); - return; - } - - lima->buffer_size = aligned_size; - } - - memset(lima->buffer, 0, req_size); - - /* FIXME: Untested new font rendering code. */ - const struct font_atlas *atlas = vid->font_driver->get_atlas(vid->font); - - for (; msg; msg++) { - const struct font_glyph *glyph = vid->font_driver->get_glyph(vid->font, (uint8_t)*msg); - if (!glyph) - continue; - - int base_x = msg_base_x + glyph->draw_offset_x; - int base_y = msg_base_y + glyph->draw_offset_y; - - const int max_width = lima->font_width - base_x; - const int max_height = lima->font_height - base_y; - - int glyph_width = glyph->width; - int glyph_height = glyph->height; - - const uint8_t *src = atlas->buffer + glyph->atlas_offset_x + glyph->atlas_offset_y * atlas->width; - - if (base_x < 0) { - src -= base_x; - glyph_width += base_x; - base_x = 0; - } - - if (base_y < 0) { - src -= base_y * (int)atlas->width; - glyph_height += base_y; - base_y = 0; - } - - if (max_width <= 0 || max_height <= 0) continue; - - if (glyph_width > max_width) glyph_width = max_width; - if (glyph_height > max_height) glyph_height = max_height; - - put_glyph_rgba4444(lima, src, vid->font_rgb, - glyph_width, glyph_height, - atlas->width, base_x, base_y); - - msg_base_x += glyph->advance_x; - msg_base_y += glyph->advance_y; - } -} - -static void *lima_gfx_init(const video_info_t *video, const input_driver_t **input, void **input_data) { - lima_video_t *vid = NULL; - limare_data_t *lima = NULL; - void *lima_input = NULL; - struct limare_windowsys_drm limare_config = { 0 }; - - vid = calloc(1, sizeof(lima_video_t)); - if (!vid) return NULL; - - vid->menu_alpha = 1.0f; - - lima = calloc(1, sizeof(limare_data_t)); - if (!lima) goto fail; - - /* Request the Exynos DRM backend for rendering. */ - limare_config.type = LIMARE_WINDOWSYS_DRM; - limare_config.connector_index = g_settings.video.monitor_index; - - lima->state = limare_init(&limare_config); - - if (!lima->state) { - RARCH_ERR("video_lima: limare initialization failed\n"); - goto fail; - } - - limare_buffer_clear(lima->state); - - if (limare_state_setup(lima->state, g_settings.video.fullscreen_x, - g_settings.video.fullscreen_y, 0xff000000)) { - RARCH_ERR("video_lima: limare state setup failed\n"); - goto fail_lima; - } - - lima->screen_aspect = get_screen_aspect(lima->state); - - lima->font_height = 368; - lima->font_width = align_common((unsigned)(lima->screen_aspect * (float)lima->font_height), 16); - - lima->upload_format = video->rgb32 ? - LIMA_TEXEL_FORMAT_RGBA_8888 : LIMA_TEXEL_FORMAT_BGR_565; - lima->upload_bpp = video->rgb32 ? 4 : 2; - - limare_enable(lima->state, GL_DEPTH_TEST); - limare_depth_func(lima->state, GL_ALWAYS); - limare_depth_mask(lima->state, GL_TRUE); - - limare_enable(lima->state, GL_CULL_FACE); - - limare_blend_func(lima->state, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - if (setup_data(lima)) { - RARCH_ERR("video_lima: data setup failed\n"); - goto fail_lima; - } - - if (create_programs(lima)) { - RARCH_ERR("video_lima: creating shader programs failed\n"); - goto fail_lima; - } - - lima->textures = calloc(num_max_textures, sizeof(limare_texture_t*)); - - if (input && input_data) { - *input = NULL; - *input_data = NULL; - } - - vid->lima = lima; - - lima_init_font(vid, g_settings.video.font_path, g_settings.video.font_size); - - return vid; - -fail_lima: - limare_finish(lima->state); -fail: - free(lima); - free(vid); - - return NULL; -} - -static bool lima_gfx_frame(void *data, const void *frame, - unsigned width, unsigned height, - unsigned pitch, const char *msg) { - lima_video_t *vid; - const void *pixels; - limare_data_t *lima; - bool upload_frame = true; - - vid = data; - - /* Check if neither menu nor emulator framebuffer is to be displayed. */ - if (!vid->menu_active && frame == NULL) return true; - - lima = vid->lima; - - if (frame != NULL) { - - /* Handle resolution changes from the emulation core. */ - if (width != vid->width || height != vid->height) { - limare_texture_t *tex; - - if (width == 0 || height == 0) return true; - - RARCH_LOG("video_lima: resolution was changed by core to %ux%u\n", width, height); - tex = get_texture_handle(lima, width, height, 0); - - if (tex == NULL) { - pixels = make_contiguous(lima, width, height, frame, 0, pitch); - - tex = add_texture(lima, width, height, pixels, 0); - - if (tex == NULL) { - RARCH_ERR("video_lima: failed to allocate new texture with dimensions %ux%u\n", - width, height); - return false; - } - - upload_frame = false; /* pixel data already got uploaded during texture allocation */ - } - - lima->cur_texture = tex; - - vid->width = width; - vid->height = height; - - lima->frame_aspect = (float)width / (float)height; - vid->aspect_changed = true; - } - - if (upload_frame) { - pixels = make_contiguous(lima, width, height, frame, 0, pitch); - limare_texture_mipmap_upload(lima->state, lima->cur_texture->handle, 0, pixels); - } - } - - if (g_settings.fps_show) { - char buffer[128], buffer_fps[128]; - - gfx_get_fps(buffer, sizeof(buffer), g_settings.fps_show ? buffer_fps : NULL, sizeof(buffer_fps)); - msg_queue_push(g_extern.msg_queue, buffer_fps, 1, 1); - } - - if (vid->aspect_changed) { - apply_aspect(lima, g_extern.system.aspect_ratio); - vid->aspect_changed = false; - } - - limare_frame_new(lima->state); - - if (lima->cur_texture != NULL) { - limare_program_current(lima->state, lima->program); - - limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT, - 3, 0, 4, lima->vertices); - limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT, - 2, 0, 4, lima->coords + vid->menu_rotation * 4); - - limare_texture_attach(lima->state, "in_texture", lima->cur_texture->handle); - - if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false; - } - - /* Handle font rendering. */ - if (msg) { - bool upload_font = true; - - /* Both font_vertices and font_color are constant, but we can't make them * - * const, since limare_attribute_pointer expects (non-const) void pointers. */ - static vec3f_t font_vertices[4] = { - {-1.0f, -1.0f, 0.0f}, - { 1.0f, -1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f}, - { 1.0f, 1.0f, 0.0f} - }; - static float font_color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - - lima_render_msg(vid, msg); - - if (lima->font_texture == NULL) { - lima->font_texture = add_texture(lima, lima->font_width, lima->font_height, - lima->buffer, LIMA_TEXEL_FORMAT_RGBA_4444); - upload_font = false; - } - - if (upload_font) - limare_texture_mipmap_upload(lima->state, lima->font_texture->handle, 0, lima->buffer); - - /* We re-use the RGBA16 menu program here. */ - limare_program_current(lima->state, lima->program_menu_rgba16); - - limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT, - 3, 0, 4, font_vertices); - limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT, - 2, 0, 4, lima->coords + vid->menu_rotation * 4); - - limare_texture_attach(lima->state, "in_texture", lima->font_texture->handle); - limare_uniform_attach(lima->state, "uColor", 4, font_color); - - limare_enable(lima->state, GL_BLEND); - if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false; - limare_disable(lima->state, GL_BLEND); - } - - if (vid->menu_active && lima->cur_texture_menu != NULL) { - float color[4] = {1.0f, 1.0f, 1.0f, vid->menu_alpha}; - - if (vid->menu_rgb32) - limare_program_current(lima->state, lima->program_menu_rgba32); - else - limare_program_current(lima->state, lima->program_menu_rgba16); - - limare_attribute_pointer(lima->state, "in_vertex", LIMARE_ATTRIB_FLOAT, - 3, 0, 4, lima->vertices); - limare_attribute_pointer(lima->state, "in_coord", LIMARE_ATTRIB_FLOAT, - 2, 0, 4, lima->coords + vid->menu_rotation * 4); - - limare_texture_attach(lima->state, "in_texture", lima->cur_texture_menu->handle); - limare_uniform_attach(lima->state, "uColor", 4, color); - - limare_enable(lima->state, GL_BLEND); - if (limare_draw_arrays(lima->state, GL_TRIANGLE_STRIP, 0, 4)) return false; - limare_disable(lima->state, GL_BLEND); - } - - if (limare_frame_flush(lima->state)) return false; - - limare_buffer_swap(lima->state); - - g_extern.frame_count++; - -#ifdef LIMA_GFX_DEBUG - print_status(lima); -#endif - - return true; -} - -static void lima_gfx_set_nonblock_state(void *data, bool state) { - (void)data; /* limare doesn't export vsync control yet */ - (void)state; -} - -static bool lima_gfx_alive(void *data) { - (void)data; - return true; /* always alive */ -} - -static bool lima_gfx_focus(void *data) { - (void)data; - return true; /* limare doesn't use windowing, so we always have focus */ -} - -static void lima_gfx_set_rotation(void *data, unsigned rotation) { - lima_video_t *vid = data; - - vid->menu_rotation = rotation; -} - -static void lima_gfx_viewport_info(void *data, struct rarch_viewport *vp) { - lima_video_t *vid = data; - vp->x = vp->y = 0; - - vp->width = vp->full_width = vid->width; - vp->height = vp->full_height = vid->height; -} - -static void lima_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) { - lima_video_t *vid = data; - - switch (aspect_ratio_idx) { - case ASPECT_RATIO_SQUARE: - gfx_set_square_pixel_viewport(g_extern.system.av_info.geometry.base_width, g_extern.system.av_info.geometry.base_height); - break; - - case ASPECT_RATIO_CORE: - gfx_set_core_viewport(); - break; - - case ASPECT_RATIO_CONFIG: - gfx_set_config_viewport(); - break; - - default: - break; - } - - g_extern.system.aspect_ratio = aspectratio_lut[aspect_ratio_idx].value; - vid->aspect_changed = true; -} - -static void lima_apply_state_changes(void *data) { - (void)data; -} - -static void lima_set_texture_frame(void *data, const void *frame, bool rgb32, - unsigned width, unsigned height, float alpha) { - lima_video_t *vid = data; - limare_texture_t* tex; - const unsigned format = rgb32 ? LIMA_TEXEL_FORMAT_RGBA_8888 : - LIMA_TEXEL_FORMAT_RGBA_4444; - - vid->menu_rgb32 = rgb32; - vid->menu_alpha = alpha; - - tex = vid->lima->cur_texture_menu; - - /* Current menu doesn't change dimensions, so we should hit this most of the time. */ - if (tex != NULL && tex->width == width && - tex->height == height && tex->format == format) goto upload; - - if (tex == NULL) { - tex = get_texture_handle(vid->lima, width, height, format); - if (tex == NULL) { - tex = add_texture(vid->lima, width, height, frame, format); - - if (tex != NULL) { - vid->lima->cur_texture_menu = tex; - goto upload; - } - - RARCH_ERR("video_lima: failed to allocate new menu texture with dimensions %ux%u\n", - width, height); - } - } - - return; - -upload: - limare_texture_mipmap_upload(vid->lima->state, tex->handle, 0, frame); -} - -static void lima_set_texture_enable(void *data, bool state, bool full_screen) { - lima_video_t *vid = data; - vid->menu_active = state; -} - -static void lima_set_osd_msg(void *data, const char *msg, const struct font_params *params) { - (void)data; - - /* TODO: what does this do? */ - (void)msg; - (void)params; -} - -static void lima_show_mouse(void *data, bool state) { - (void)data; -} - -static const video_poke_interface_t lima_poke_interface = { - NULL, /* set_filtering */ -#ifdef HAVE_FBO - NULL, /* get_current_framebuffer */ - NULL, /* get_proc_address */ -#endif - lima_set_aspect_ratio, - lima_apply_state_changes, -#ifdef HAVE_MENU - lima_set_texture_frame, - lima_set_texture_enable, -#endif - lima_set_osd_msg, - lima_show_mouse -}; - -static void lima_gfx_get_poke_interface(void *data, const video_poke_interface_t **iface) { - (void)data; - *iface = &lima_poke_interface; -} - -const video_driver_t video_lima = { - lima_gfx_init, - lima_gfx_frame, - lima_gfx_set_nonblock_state, - lima_gfx_alive, - lima_gfx_focus, - NULL, /* set_shader */ - lima_gfx_free, - "lima", - - lima_gfx_set_rotation, - lima_gfx_viewport_info, - NULL, /* read_viewport */ - -#ifdef HAVE_OVERLAY - NULL, /* overlay_interface */ -#endif - lima_gfx_get_poke_interface -}; diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 728fbaf321..8ee553c1bc 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -174,11 +174,6 @@ fi check_pkgconf ZLIB zlib -if [ "$HAVE_LIMA" = "yes" ]; then - check_lib LIMA -llimare limare_init - LIMA_LIBS="-llimare" -fi - if [ "$HAVE_THREADS" != 'no' ]; then if [ "$HAVE_FFMPEG" != 'no' ]; then check_pkgconf AVCODEC libavcodec 54 @@ -281,7 +276,6 @@ add_define_make OS "$OS" # Creates config.mk and config.h. add_define_make GLOBAL_CONFIG_DIR "$GLOBAL_CONFIG_DIR" -VARS="RGUI LAKKA ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL LIMA OMAP GLES GLES3 VG EGL KMS EXYNOS GBM DRM DYLIB GETOPT_LONG THREADS CG LIBXML2 ZLIB DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE FREETYPE XKBCOMMON XVIDEO X11 XEXT XF86VM XINERAMA MALI_FBDEV NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL STRCASESTR MMAP PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 BSV_MOVIE VIDEOCORE NEON FLOATHARD FLOATSOFTFP UDEV V4L2 AV_CHANNEL_LAYOUT" ->>>>>>> Add exynos video driver +VARS="RGUI LAKKA ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL OMAP GLES GLES3 VG EGL KMS EXYNOS GBM DRM DYLIB GETOPT_LONG THREADS CG LIBXML2 ZLIB DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL SWSCALE FREETYPE XKBCOMMON XVIDEO X11 XEXT XF86VM XINERAMA MALI_FBDEV NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL STRCASESTR MMAP PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 BSV_MOVIE VIDEOCORE NEON FLOATHARD FLOATSOFTFP UDEV V4L2 AV_CHANNEL_LAYOUT" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index 6cd98f20c0..bc9af0e459 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -16,7 +16,6 @@ HAVE_GLES=no # Use GLESv2 instead of desktop GL HAVE_MALI_FBDEV=no # Enable Mali fbdev context support HAVE_GLES3=no # Enable OpenGLES3 support HAVE_X11=auto # Disable everything X11. -HAVE_LIMA=no # Enable Lima video support HAVE_OMAP=no # Enable OMAP video support HAVE_XINERAMA=auto # Disable Xinerama support. HAVE_KMS=auto # Enable KMS context support From 63e99009df94516f3ac6093abbf46ff24a011ecb Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Tue, 8 Jul 2014 15:15:57 +0200 Subject: [PATCH 3/7] exynos: adapt to video_driver interface changes The restart API was removed. --- gfx/exynos_gfx.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c index 866dd07695..83cdaa31a5 100644 --- a/gfx/exynos_gfx.c +++ b/gfx/exynos_gfx.c @@ -1473,11 +1473,6 @@ const video_driver_t video_exynos = { NULL, /* set_shader */ exynos_gfx_free, "exynos", - -#ifdef HAVE_MENU - NULL, /* restart */ -#endif - exynos_gfx_set_rotation, exynos_gfx_viewport_info, NULL, /* read_viewport */ From fe57d064bafcf5c085b97cb6415701c0b511395c Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Tue, 8 Jul 2014 15:20:50 +0200 Subject: [PATCH 4/7] exynos: adapt to naming changes The RGUI is now just called 'menu'. --- gfx/exynos_gfx.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c index 83cdaa31a5..630c1bc30b 100644 --- a/gfx/exynos_gfx.c +++ b/gfx/exynos_gfx.c @@ -34,7 +34,7 @@ #include "gfx_common.h" #include "fonts/fonts.h" -/* TODO: Honor these properties: vsync, RGUI rotation, RGUI alpha, aspect ratio change */ +/* TODO: Honor these properties: vsync, menu rotation, menu alpha, aspect ratio change */ /* Set to '1' to enable debug logging code. */ #define EXYNOS_GFX_DEBUG_LOG 0 @@ -60,12 +60,12 @@ enum exynos_buffer_type { /* We have to handle three types of 'data' from the frontend, each abstracted by a * * G2D image object. The image objects are then backed by some storage buffer. * * (1) the emulator framebuffer (backed by main buffer) * - * (2) the RGUI buffer (backed by aux buffer) * + * (2) the menu buffer (backed by aux buffer) * * (3) the font rendering buffer (backed by aux buffer) */ enum exynos_image_type { exynos_image_frame = 0, exynos_image_font, - exynos_image_rgui, + exynos_image_menu, exynos_image_count }; @@ -77,7 +77,7 @@ static const struct exynos_config_default { } defaults[exynos_image_count] = { {1024, 640, exynos_buffer_main, G2D_COLOR_FMT_RGB565 | G2D_ORDER_AXRGB, 2}, /* frame */ {720, 368, exynos_buffer_aux, G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_AXRGB, 2}, /* font */ - {400, 240, exynos_buffer_aux, G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_RGBAX, 2} /* RGUI */ + {400, 240, exynos_buffer_aux, G2D_COLOR_FMT_ARGB4444 | G2D_ORDER_RGBAX, 2} /* menu */ }; @@ -968,9 +968,9 @@ static int exynos_blit_frame(struct exynos_data *pdata, const void *frame, return 0; } -static int exynos_blend_rgui(struct exynos_data *pdata, +static int exynos_blend_menu(struct exynos_data *pdata, unsigned rotation) { - struct g2d_image *src = pdata->src[exynos_image_rgui]; + struct g2d_image *src = pdata->src[exynos_image_menu]; #if (EXYNOS_GFX_DEBUG_PERF == 1) perf_g2d(&pdata->perf, true); @@ -981,7 +981,7 @@ static int exynos_blend_rgui(struct exynos_data *pdata, pdata->blit_params[1], pdata->blit_params[2], pdata->blit_params[3], G2D_OP_OVER) || g2d_exec(pdata->g2d)) { - RARCH_ERR("video_exynos: failed to blend RGUI\n"); + RARCH_ERR("video_exynos: failed to blend menu\n"); return -1; } @@ -1052,9 +1052,9 @@ struct exynos_video { unsigned width; unsigned height; - /* RGUI data */ - unsigned rgui_rotation; - bool rgui_active; + /* menu data */ + unsigned menu_rotation; + bool menu_active; bool aspect_changed; }; @@ -1271,8 +1271,8 @@ static bool exynos_gfx_frame(void *data, const void *frame, unsigned width, struct exynos_video *vid = data; struct exynos_page *page = NULL; - /* Check if neither RGUI nor emulator framebuffer is to be displayed. */ - if (!vid->rgui_active && frame == NULL) return true; + /* Check if neither menu nor emulator framebuffer is to be displayed. */ + if (!vid->menu_active && frame == NULL) return true; if (frame != NULL) { if (width != vid->width || height != vid->height) { @@ -1302,15 +1302,15 @@ static bool exynos_gfx_frame(void *data, const void *frame, unsigned width, if (vid->width == 0 || vid->height == 0) { /* If at this point the dimension parameters are still zero, setup some * - * fake blit parameters so that RGUI and font rendering work properly. */ + * fake blit parameters so that menu and font rendering work properly. */ exynos_set_fake_blit(vid->data); } if (page == NULL) page = exynos_free_page(vid->data); - if (vid->rgui_active) { - if (exynos_blend_rgui(vid->data, vid->rgui_rotation) != 0) + if (vid->menu_active) { + if (exynos_blend_menu(vid->data, vid->menu_rotation) != 0) goto fail; } @@ -1354,7 +1354,7 @@ static bool exynos_gfx_focus(void *data) { static void exynos_gfx_set_rotation(void *data, unsigned rotation) { struct exynos_video *vid = data; - vid->rgui_rotation = rotation; + vid->menu_rotation = rotation; } static void exynos_gfx_viewport_info(void *data, struct rarch_viewport *vp) { @@ -1396,11 +1396,11 @@ static void exynos_apply_state_changes(void *data) { static void exynos_set_texture_frame(void *data, const void *frame, bool rgb32, unsigned width, unsigned height, float alpha) { - const enum exynos_buffer_type buf_type = defaults[exynos_image_rgui].buf_type; + const enum exynos_buffer_type buf_type = defaults[exynos_image_menu].buf_type; struct exynos_video *vid = data; struct exynos_data *pdata = vid->data; - struct g2d_image *src = pdata->src[exynos_image_rgui]; + struct g2d_image *src = pdata->src[exynos_image_menu]; const unsigned size = width * height * (rgb32 ? 4 : 2); @@ -1428,7 +1428,7 @@ static void exynos_set_texture_frame(void *data, const void *frame, bool rgb32, static void exynos_set_texture_enable(void *data, bool state, bool full_screen) { struct exynos_video *vid = data; - vid->rgui_active = state; + vid->menu_active = state; } static void exynos_set_osd_msg(void *data, const char *msg, void *userdata) { @@ -1451,7 +1451,7 @@ static const video_poke_interface_t exynos_poke_interface = { #endif exynos_set_aspect_ratio, exynos_apply_state_changes, -#if defined(HAVE_RGUI) || defined(HAVE_RMENU) /* TODO: only HAVE_MENU i think? */ +#ifdef HAVE_MENU exynos_set_texture_frame, exynos_set_texture_enable, #endif From 7fea31e501a1596e1552c24dc2d47a1ca38847b2 Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Tue, 8 Jul 2014 16:13:19 +0200 Subject: [PATCH 5/7] exynos: adapt font rendering code --- gfx/exynos_gfx.c | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c index 630c1bc30b..1263c262e5 100644 --- a/gfx/exynos_gfx.c +++ b/gfx/exynos_gfx.c @@ -1069,7 +1069,9 @@ static int exynos_init_font(struct exynos_video *vid) { if (!g_settings.video.font_enable) return 0; - if (font_renderer_create_default(&vid->font_driver, &vid->font)) { + if (font_renderer_create_default(&vid->font_driver, &vid->font, + *g_settings.video.font_path ? g_settings.video.font_path : NULL, + g_settings.video.font_size)) { const int r = g_settings.video.msg_color_r * 255; const int g = g_settings.video.msg_color_g * 255; const int b = g_settings.video.msg_color_b * 255; @@ -1106,12 +1108,10 @@ static int exynos_render_msg(struct exynos_video *vid, struct exynos_data *pdata = vid->data; struct g2d_image *dst = pdata->src[exynos_image_font]; - struct font_output_list out; - struct font_output *head; - int ret; + const struct font_atlas *atlas; - const int msg_base_x = g_settings.video.msg_pos_x * dst->width; - const int msg_base_y = (1.0f - g_settings.video.msg_pos_y) * dst->height; + int msg_base_x = g_settings.video.msg_pos_x * dst->width; + int msg_base_y = (1.0f - g_settings.video.msg_pos_y) * dst->height; if (vid->font == NULL || vid->font_driver == NULL) return -1; @@ -1119,19 +1119,23 @@ static int exynos_render_msg(struct exynos_video *vid, if (clear_buffer(pdata->g2d, dst) != 0) return -1; - vid->font_driver->render_msg(vid->font, msg, &out); + atlas = vid->font_driver->get_atlas(vid->font); - for (head = out.head; head; head = head->next) { - int base_x = msg_base_x + head->off_x; - int base_y = msg_base_y - head->off_y - head->height; + for (; msg; ++msg) { + const struct font_glyph *glyph = vid->font_driver->get_glyph(vid->font, (uint8_t)*msg); + if (glyph == NULL) + continue; + + int base_x = msg_base_x + glyph->draw_offset_x; + int base_y = msg_base_y + glyph->draw_offset_y; const int max_width = dst->width - base_x; const int max_height = dst->height - base_y; - int glyph_width = head->width; - int glyph_height = head->height; + int glyph_width = glyph->width; + int glyph_height = glyph->height; - const uint8_t *src = head->output; + const uint8_t *src = atlas->buffer + glyph->atlas_offset_x + glyph->atlas_offset_y * atlas->width; if (base_x < 0) { src -= base_x; @@ -1140,7 +1144,7 @@ static int exynos_render_msg(struct exynos_video *vid, } if (base_y < 0) { - src -= base_y * (int)head->pitch; + src -= base_y * (int)atlas->width; glyph_height += base_y; base_y = 0; } @@ -1152,14 +1156,13 @@ static int exynos_render_msg(struct exynos_video *vid, put_glyph_rgba4444(pdata, src, vid->font_rgb, glyph_width, glyph_height, - head->pitch, base_x, base_y); + atlas->width, base_x, base_y); + + msg_base_x += glyph->advance_x; + msg_base_y += glyph->advance_y; } - ret = exynos_blend_font(pdata); - - vid->font_driver->free_output(vid->font, &out); - - return ret; + return exynos_blend_font(pdata); } @@ -1431,12 +1434,12 @@ static void exynos_set_texture_enable(void *data, bool state, bool full_screen) vid->menu_active = state; } -static void exynos_set_osd_msg(void *data, const char *msg, void *userdata) { +static void exynos_set_osd_msg(void *data, const char *msg, const struct font_params *params) { struct exynos_video *vid = data; /* TODO: what does this do? */ (void)msg; - (void)userdata; + (void)params; } static void exynos_show_mouse(void *data, bool state) { From 19419b04af634a488285ae5cb5891cc98f923b8f Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Tue, 8 Jul 2014 16:13:40 +0200 Subject: [PATCH 6/7] exynos: fix typo in comment --- gfx/exynos_gfx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c index 1263c262e5..97ff24c755 100644 --- a/gfx/exynos_gfx.c +++ b/gfx/exynos_gfx.c @@ -1331,8 +1331,8 @@ static bool exynos_gfx_frame(void *data, const void *frame, unsigned width, return true; fail: - /* Since we didn't managed to issue a pageflip to this page, set * - * it to 'unused' again, and hope that it works next time. */ + /* Since we didn't manage to issue a pageflip to this page, set * + * it to 'unused' again, and hope that it works next time. */ page->used = false; return false; From e197e97d67883cacd0f97f3d32fc37e1e5c2f9c0 Mon Sep 17 00:00:00 2001 From: Tobias Jakobi Date: Fri, 11 Jul 2014 00:39:12 +0200 Subject: [PATCH 7/7] exynos: disable crtc before freeing buffers The crtc should be disabled before deallocating the buffers, otherwise leading to a use-after-free scenario, which can trigger all sorts of funny effects. --- gfx/exynos_gfx.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gfx/exynos_gfx.c b/gfx/exynos_gfx.c index 97ff24c755..8b3c8a5887 100644 --- a/gfx/exynos_gfx.c +++ b/gfx/exynos_gfx.c @@ -821,6 +821,10 @@ fail_alloc: static void exynos_free(struct exynos_data *pdata) { unsigned i; + /* Disable the CRTC. */ + drmModeSetCrtc(pdata->fd, pdata->drm->crtc_id, 0, + 0, 0, &pdata->drm->connector_id, 1, NULL); + clean_up_pages(pdata->pages, pdata->num_pages); free(pdata->pages);