/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2012-2015 - Michael Lelli * * 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 #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #ifdef HAVE_MENU #include "../../menu/menu_driver.h" #endif #include "../font_driver.h" #include "../../retroarch.h" #include "../../driver.h" #include "../../content.h" #include "../../verbosity.h" #include "../../configuration.h" typedef struct { bool should_resize; bool keep_aspect; bool mEglImageBuf; bool mFontsOn; float mScreenAspect; unsigned mTextureWidth; unsigned mTextureHeight; unsigned mRenderWidth; unsigned mRenderHeight; unsigned x1, y1, x2, y2; uint32_t mFontHeight; char *mLastMsg; VGint scissor[4]; VGImageFormat mTexType; VGImage mImage; math_matrix_3x3 mTransformMatrix; EGLImageKHR last_egl_image; VGFont mFont; void *mFontRenderer; const font_renderer_driver_t *font_driver; VGuint mMsgLength; VGuint mGlyphIndices[1024]; VGPaint mPaintFg; VGPaint mPaintBg; void *ctx_data; const gfx_ctx_driver_t *ctx_driver; } vg_t; static PFNVGCREATEEGLIMAGETARGETKHRPROC pvgCreateEGLImageTargetKHR; static void vg_set_nonblock_state(void *data, bool state, bool adaptive_vsync_enabled, unsigned swap_interval) { vg_t *vg = (vg_t*)data; int interval = state ? 0 : 1; if (vg->ctx_driver && vg->ctx_driver->swap_interval) { if (adaptive_vsync_enabled && interval == 1) interval = -1; vg->ctx_driver->swap_interval(vg->ctx_data, interval); } } static INLINE bool vg_query_extension(const char *ext) { const char *str = (const char*)vgGetString(VG_EXTENSIONS); bool ret = str && strstr(str, ext); RARCH_LOG("[VG]: Querying VG extension: %s => %s\n", ext, ret ? "exists" : "doesn't exist"); return ret; } static void *vg_init(const video_info_t *video, input_driver_t **input, void **input_data) { unsigned win_width, win_height; VGfloat clearColor[4] = {0, 0, 0, 1}; int interval = 0; unsigned mode_width = 0; unsigned mode_height = 0; unsigned temp_width = 0; unsigned temp_height = 0; void *ctx_data = NULL; settings_t *settings = config_get_ptr(); const char *path_font = settings->paths.path_font; float video_font_size = settings->floats.video_font_size; float video_msg_color_r = settings->floats.video_msg_color_r; float video_msg_color_g = settings->floats.video_msg_color_g; float video_msg_color_b = settings->floats.video_msg_color_b; vg_t *vg = (vg_t*)calloc(1, sizeof(vg_t)); const gfx_ctx_driver_t *ctx = video_context_driver_init_first( vg, settings->arrays.video_context_driver, GFX_CTX_OPENVG_API, 0, 0, false, &ctx_data); bool adaptive_vsync_enabled = video_driver_test_all_flags( GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && video->adaptive_vsync; if (!vg || !ctx) goto error; if (ctx_data) vg->ctx_data = ctx_data; vg->ctx_driver = ctx; video_context_driver_set((void*)ctx); if (vg->ctx_driver->get_video_size) vg->ctx_driver->get_video_size(vg->ctx_data, &mode_width, &mode_height); temp_width = mode_width; temp_height = mode_height; RARCH_LOG("[VG]: Detecting screen resolution: %ux%u.\n", temp_width, temp_height); if (temp_width != 0 && temp_height != 0) video_driver_set_size(temp_width, temp_height); interval = video->vsync ? 1 : 0; if (ctx->swap_interval) { if (adaptive_vsync_enabled && interval == 1) interval = -1; ctx->swap_interval(vg->ctx_data, interval); } vg->mTexType = video->rgb32 ? VG_sXRGB_8888 : VG_sRGB_565; vg->keep_aspect = video->force_aspect; win_width = video->width; win_height = video->height; if (video->fullscreen && (win_width == 0) && (win_height == 0)) { video_driver_get_size(&temp_width, &temp_height); win_width = temp_width; win_height = temp_height; } if ( !vg->ctx_driver->set_video_mode || !vg->ctx_driver->set_video_mode(vg->ctx_data, win_width, win_height, video->fullscreen)) goto error; video_driver_get_size(&temp_width, &temp_height); temp_width = 0; temp_height = 0; mode_width = 0; mode_height = 0; if (vg->ctx_driver->get_video_size) vg->ctx_driver->get_video_size(vg->ctx_data, &mode_width, &mode_height); temp_width = mode_width; temp_height = mode_height; vg->should_resize = true; if (temp_width != 0 && temp_height != 0) { RARCH_LOG("[VG]: Verified window resolution %ux%u.\n", temp_width, temp_height); video_driver_set_size(temp_width, temp_height); } video_driver_get_size(&temp_width, &temp_height); vg->mScreenAspect = (float)temp_width / temp_height; if (vg->ctx_driver->translate_aspect) vg->mScreenAspect = vg->ctx_driver->translate_aspect( vg->ctx_data, temp_width, temp_height); vgSetfv(VG_CLEAR_COLOR, 4, clearColor); vg->mTextureWidth = vg->mTextureHeight = video->input_scale * RARCH_SCALE_BASE; vg->mImage = vgCreateImage( vg->mTexType, vg->mTextureWidth, vg->mTextureHeight, video->smooth ? VG_IMAGE_QUALITY_BETTER : VG_IMAGE_QUALITY_NONANTIALIASED); vg_set_nonblock_state(vg, !video->vsync, adaptive_vsync_enabled, interval); if (vg->ctx_driver->input_driver) { const char *joypad_name = settings->arrays.input_joypad_driver; vg->ctx_driver->input_driver( vg->ctx_data, joypad_name, input, input_data); } if ( video->font_enable && font_renderer_create_default( &vg->font_driver, &vg->mFontRenderer, *path_font ? path_font : NULL, video_font_size)) { vg->mFont = vgCreateFont(0); if (vg->mFont != VG_INVALID_HANDLE) { VGfloat paintFg[4]; VGfloat paintBg[4]; vg->mFontsOn = true; vg->mFontHeight = video_font_size; vg->mPaintFg = vgCreatePaint(); vg->mPaintBg = vgCreatePaint(); paintFg[0] = video_msg_color_r; paintFg[1] = video_msg_color_g; paintFg[2] = video_msg_color_b; paintFg[3] = 1.0f; paintBg[0] = video_msg_color_r / 2.0f; paintBg[1] = video_msg_color_g / 2.0f; paintBg[2] = video_msg_color_b / 2.0f; paintBg[3] = 0.5f; vgSetParameteri(vg->mPaintFg, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(vg->mPaintFg, VG_PAINT_COLOR, 4, paintFg); vgSetParameteri(vg->mPaintBg, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(vg->mPaintBg, VG_PAINT_COLOR, 4, paintBg); } } if (vg_query_extension("KHR_EGL_image") && vg->ctx_driver->image_buffer_init && vg->ctx_driver->image_buffer_init(vg->ctx_data, (void*)video)) { if (vg->ctx_driver->get_proc_address) pvgCreateEGLImageTargetKHR = (PFNVGCREATEEGLIMAGETARGETKHRPROC)vg->ctx_driver->get_proc_address("vgCreateEGLImageTargetKHR"); if (pvgCreateEGLImageTargetKHR) { RARCH_LOG("[VG] Using EGLImage buffer\n"); vg->mEglImageBuf = true; } } #if 0 const char *ext = (const char*)vgGetString(VG_EXTENSIONS); if (ext) RARCH_LOG("[VG] Supported extensions: %s\n", ext); #endif return vg; error: video_context_driver_free(); if (vg) free(vg); return NULL; } static void vg_free(void *data) { vg_t *vg = (vg_t*)data; if (!vg) return; vgDestroyImage(vg->mImage); if (vg->mFontsOn) { vgDestroyFont(vg->mFont); vg->font_driver->free(vg->mFontRenderer); vgDestroyPaint(vg->mPaintFg); vgDestroyPaint(vg->mPaintBg); } if (vg->ctx_driver && vg->ctx_driver->destroy) vg->ctx_driver->destroy(vg->ctx_data); video_context_driver_free(); free(vg); } static void vg_calculate_quad(vg_t *vg, unsigned width, unsigned height) { /* set viewport for aspect ratio, taken from the OpenGL driver. */ if (vg->keep_aspect) { float desired_aspect = video_driver_get_aspect_ratio(); /* If the aspect ratios of screen and desired aspect ratio * are sufficiently equal (floating point stuff), * assume they are actually equal. */ if (fabs(vg->mScreenAspect - desired_aspect) < 0.0001) { vg->x1 = 0; vg->y1 = 0; vg->x2 = width; vg->y2 = height; } else if (vg->mScreenAspect > desired_aspect) { float delta = (desired_aspect / vg->mScreenAspect - 1.0) / 2.0 + 0.5; vg->x1 = width * (0.5 - delta); vg->y1 = 0; vg->x2 = 2.0 * width * delta + vg->x1; vg->y2 = height + vg->y1; } else { float delta = (vg->mScreenAspect / desired_aspect - 1.0) / 2.0 + 0.5; vg->x1 = 0; vg->y1 = height * (0.5 - delta); vg->x2 = width + vg->x1; vg->y2 = 2.0 * height * delta + vg->y1; } } else { vg->x1 = 0; vg->y1 = 0; vg->x2 = width; vg->y2 = height; } vg->scissor[0] = vg->x1; vg->scissor[1] = vg->y1; vg->scissor[2] = vg->x2 - vg->x1; vg->scissor[3] = vg->y2 - vg->y1; vgSetiv(VG_SCISSOR_RECTS, 4, vg->scissor); } static void vg_copy_frame(void *data, const void *frame, unsigned width, unsigned height, unsigned pitch) { vg_t *vg = (vg_t*)data; if (vg->mEglImageBuf) { EGLImageKHR img = 0; bool new_egl = false; if (vg->ctx_driver->image_buffer_write) new_egl = vg->ctx_driver->image_buffer_write( vg->ctx_data, frame, width, height, pitch, (vg->mTexType == VG_sXRGB_8888), 0, &img); retro_assert(img != EGL_NO_IMAGE_KHR); if (new_egl) { vgDestroyImage(vg->mImage); vg->mImage = pvgCreateEGLImageTargetKHR((VGeglImageKHR) img); if (!vg->mImage) { RARCH_ERR( "[VG:EGLImage] Error creating image: %08x\n", vgGetError()); exit(2); } vg->last_egl_image = img; } } else vgImageSubData(vg->mImage, frame, pitch, vg->mTexType, 0, 0, width, height); } static bool vg_frame(void *data, const void *frame, unsigned frame_width, unsigned frame_height, uint64_t frame_count, unsigned pitch, const char *msg, video_frame_info_t *video_info) { vg_t *vg = (vg_t*)data; unsigned width = video_info->width; unsigned height = video_info->height; #ifdef HAVE_MENU bool menu_is_alive = video_info->menu_is_alive; #endif if ( frame_width != vg->mRenderWidth || frame_height != vg->mRenderHeight || vg->should_resize) { vg->mRenderWidth = frame_width; vg->mRenderHeight = frame_height; vg_calculate_quad(vg, width, height); matrix_3x3_quad_to_quad( vg->x1, vg->y1, vg->x2, vg->y1, vg->x2, vg->y2, vg->x1, vg->y2, /* needs to be flipped, Khronos loves their bottom-left origin */ 0, frame_height, frame_width, frame_height, frame_width, 0, 0, 0, &vg->mTransformMatrix); vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE); vgLoadMatrix(vg->mTransformMatrix.data); vg->should_resize = false; } vgSeti(VG_SCISSORING, VG_FALSE); vgClear(0, 0, width, height); vgSeti(VG_SCISSORING, VG_TRUE); vg_copy_frame(vg, frame, frame_width, frame_height, pitch); #ifdef HAVE_MENU menu_driver_frame(menu_is_alive, video_info); #endif vgDrawImage(vg->mImage); #if 0 if (msg && vg->mFontsOn) vg_draw_message(vg, msg); #endif if (vg->ctx_driver->update_window_title) vg->ctx_driver->update_window_title(vg->ctx_data); if (vg->ctx_driver->swap_buffers) vg->ctx_driver->swap_buffers(vg->ctx_data); return true; } static bool vg_alive(void *data) { bool quit = false; bool resize = false; unsigned temp_width = 0; unsigned temp_height = 0; vg_t *vg = (vg_t*)data; vg->ctx_driver->check_window(vg->ctx_data, &quit, &resize, &temp_width, &temp_height); if (temp_width != 0 && temp_height != 0) video_driver_set_size(temp_width, temp_height); return !quit; } static bool vg_suppress_screensaver(void *data, bool enable) { bool enabled = enable; vg_t *vg = (vg_t*)data; if (vg->ctx_data && vg->ctx_driver->suppress_screensaver) return vg->ctx_driver->suppress_screensaver(vg->ctx_data, enabled); return false; } static bool vg_set_shader(void *data, enum rarch_shader_type type, const char *path) { return false; } static void vg_get_poke_interface(void *data, const video_poke_interface_t **iface) { } static bool vg_has_windowed(void *data) { vg_t *vg = (vg_t*)data; if (vg && vg->ctx_driver) return vg->ctx_driver->has_windowed; return false; } static bool vg_focus(void *data) { vg_t *vg = (vg_t*)data; if (vg && vg->ctx_driver && vg->ctx_driver->has_focus) return vg->ctx_driver->has_focus(vg->ctx_data); return true; } video_driver_t video_vg = { vg_init, vg_frame, vg_set_nonblock_state, vg_alive, vg_focus, vg_suppress_screensaver, vg_has_windowed, vg_set_shader, vg_free, "vg", NULL, /* set_viewport */ NULL, /* set_rotation */ NULL, /* viewport_info */ NULL, /* read_viewport */ NULL, /* read_frame_raw */ #ifdef HAVE_OVERLAY NULL, /* overlay_interface */ #endif #ifdef HAVE_VIDEO_LAYOUT NULL, #endif vg_get_poke_interface };