/* RetroArch - A frontend for libretro. * Copyright (C) 2013-2014 - Jason Fetters * Copyright (C) 2011-2017 - Daniel De Matteis * * 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 . */ #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #if TARGET_OS_IPHONE #include #else #include #endif #ifdef OSX #include #endif #include #include #include #include "../../ui/drivers/ui_cocoa.h" #include "../../ui/drivers/cocoa/cocoa_common.h" #include "../../ui/drivers/cocoa/apple_platform.h" #include "../../configuration.h" #include "../../retroarch.h" #include "../../verbosity.h" #include "../common/vulkan_common.h" #ifdef HAVE_METAL #include "../common/metal_common.h" #endif typedef struct cocoa_vk_ctx_data { gfx_ctx_vulkan_data_t vk; int swap_interval; unsigned width; unsigned height; } cocoa_vk_ctx_data_t; /* TODO/FIXME - static globals */ static unsigned g_vk_minor = 0; static unsigned g_vk_major = 0; /* Forward declaration */ CocoaView *cocoaview_get(void); static uint32_t cocoa_vk_gfx_ctx_get_flags(void *data) { uint32_t flags = 0; #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG); #endif return flags; } static void cocoa_vk_gfx_ctx_set_flags(void *data, uint32_t flags) { } static void cocoa_vk_gfx_ctx_destroy(void *data) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; if (!cocoa_ctx) return; vulkan_context_destroy(&cocoa_ctx->vk, cocoa_ctx->vk.vk_surface != VK_NULL_HANDLE); if (cocoa_ctx->vk.context.queue_lock) slock_free(cocoa_ctx->vk.context.queue_lock); memset(&cocoa_ctx->vk, 0, sizeof(cocoa_ctx->vk)); free(cocoa_ctx); } static enum gfx_ctx_api cocoa_vk_gfx_ctx_get_api(void *data) { return GFX_CTX_VULKAN_API; } static bool cocoa_vk_gfx_ctx_suppress_screensaver(void *data, bool disable) { return [apple_platform setDisableDisplaySleep:disable]; } static void cocoa_vk_gfx_ctx_input_driver(void *data, const char *name, input_driver_t **input, void **input_data) { *input = NULL; *input_data = NULL; } #if MAC_OS_X_VERSION_10_7 && defined(OSX) /* NOTE: convertRectToBacking only available on MacOS X 10.7 and up. * Therefore, make specialized version of this function instead of * going through a selector for every call. */ static void cocoa_vk_gfx_ctx_get_video_size_osx10_7_and_up(void *data, unsigned* width, unsigned* height) { CocoaView *g_view = cocoaview_get(); CGRect _cgrect = NSRectToCGRect(g_view.frame); CGRect bounds = CGRectMake(0, 0, CGRectGetWidth(_cgrect), CGRectGetHeight(_cgrect)); CGRect cgrect = NSRectToCGRect([g_view convertRectToBacking:bounds]); GLsizei backingPixelWidth = CGRectGetWidth(cgrect); GLsizei backingPixelHeight = CGRectGetHeight(cgrect); CGRect size = CGRectMake(0, 0, backingPixelWidth, backingPixelHeight); *width = CGRectGetWidth(size); *height = CGRectGetHeight(size); } #elif defined(OSX) static void cocoa_vk_gfx_ctx_get_video_size(void *data, unsigned* width, unsigned* height) { CocoaView *g_view = cocoaview_get(); CGRect cgrect = NSRectToCGRect([g_view frame]); GLsizei backingPixelWidth = CGRectGetWidth(cgrect); GLsizei backingPixelHeight = CGRectGetHeight(cgrect); CGRect size = CGRectMake(0, 0, backingPixelWidth, backingPixelHeight); *width = CGRectGetWidth(size); *height = CGRectGetHeight(size); } #else static void cocoa_vk_gfx_ctx_get_video_size(void *data, unsigned* width, unsigned* height) { float screenscale = cocoa_screen_get_native_scale(); CGRect size = [apple_platform.renderView bounds]; *width = CGRectGetWidth(size) * screenscale; *height = CGRectGetHeight(size) * screenscale; } #endif static float cocoa_vk_gfx_ctx_get_refresh_rate(void *data) { #ifdef OSX CGDirectDisplayID mainDisplayID = CGMainDisplayID(); CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(mainDisplayID); float currentRate = CGDisplayModeGetRefreshRate(currentMode); CFRelease(currentMode); return currentRate; #else return [UIScreen mainScreen].maximumFramesPerSecond; #endif } static gfx_ctx_proc_t cocoa_vk_gfx_ctx_get_proc_address(const char *symbol_name) { return NULL; } static void cocoa_vk_gfx_ctx_bind_hw_render(void *data, bool enable) { } static void cocoa_vk_gfx_ctx_check_window(void *data, bool *quit, bool *resize, unsigned *width, unsigned *height) { unsigned new_width, new_height; cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; *quit = false; *resize = (cocoa_ctx->vk.flags & VK_DATA_FLAG_NEED_NEW_SWAPCHAIN) ? true : false; #if MAC_OS_X_VERSION_10_7 && defined(OSX) cocoa_vk_gfx_ctx_get_video_size_osx10_7_and_up(data, &new_width, &new_height); #else cocoa_vk_gfx_ctx_get_video_size(data, &new_width, &new_height); #endif if (new_width != *width || new_height != *height) { *width = new_width; *height = new_height; *resize = true; } } static void cocoa_vk_gfx_ctx_swap_interval(void *data, int i) { unsigned interval = (unsigned)i; cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; if (cocoa_ctx->swap_interval != interval) { cocoa_ctx->swap_interval = interval; if (cocoa_ctx->vk.swapchain) cocoa_ctx->vk.flags |= VK_DATA_FLAG_NEED_NEW_SWAPCHAIN; } } static void cocoa_vk_gfx_ctx_swap_buffers(void *data) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; if (cocoa_ctx->vk.context.flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) { cocoa_ctx->vk.context.flags &= ~VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN; if (cocoa_ctx->vk.swapchain == VK_NULL_HANDLE) { retro_sleep(10); } else vulkan_present(&cocoa_ctx->vk, cocoa_ctx->vk.context.current_swapchain_index); } vulkan_acquire_next_image(&cocoa_ctx->vk); } static bool cocoa_vk_gfx_ctx_bind_api(void *data, enum gfx_ctx_api api, unsigned major, unsigned minor) { g_vk_minor = minor; g_vk_major = major; return true; } static void *cocoa_vk_gfx_ctx_get_context_data(void *data) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; return &cocoa_ctx->vk.context; } #ifdef OSX static bool cocoa_vk_gfx_ctx_set_video_mode(void *data, unsigned width, unsigned height, bool fullscreen) { gfx_ctx_mode_t mode; #if defined(HAVE_COCOA_METAL) NSView *g_view = apple_platform.renderView; #elif defined(HAVE_COCOA) CocoaView *g_view = (CocoaView*)nsview_get_ptr(); #endif cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; static bool has_went_fullscreen = false; cocoa_ctx->width = width; cocoa_ctx->height = height; RARCH_LOG("[macOS]: Native window size: %u x %u.\n", cocoa_ctx->width, cocoa_ctx->height); if (!vulkan_surface_create( &cocoa_ctx->vk, VULKAN_WSI_MVK_MACOS, NULL, (BRIDGE void *)g_view.layer, cocoa_ctx->width, cocoa_ctx->height, cocoa_ctx->swap_interval)) { RARCH_ERR("[macOS]: Failed to create surface.\n"); return false; } mode.width = width; mode.height = height; mode.fullscreen = fullscreen; [apple_platform setVideoMode:mode]; cocoa_show_mouse(data, !fullscreen); has_went_fullscreen = fullscreen; return true; } static void *cocoa_vk_gfx_ctx_init(void *video_driver) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*) calloc(1, sizeof(cocoa_vk_ctx_data_t)); if (!cocoa_ctx) return NULL; [apple_platform setViewType:APPLE_VIEW_TYPE_VULKAN]; if (!vulkan_context_init(&cocoa_ctx->vk, VULKAN_WSI_MVK_MACOS)) { free(cocoa_ctx); return NULL; } return cocoa_ctx; } #else static bool cocoa_vk_gfx_ctx_set_video_mode(void *data, unsigned width, unsigned height, bool fullscreen) { id g_view = apple_platform.renderView; cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; cocoa_ctx->width = width; cocoa_ctx->height = height; if (!vulkan_surface_create(&cocoa_ctx->vk, VULKAN_WSI_MVK_IOS, NULL, (BRIDGE void *)((MetalLayerView*)g_view).metalLayer, cocoa_ctx->width, cocoa_ctx->height, cocoa_ctx->swap_interval)) { RARCH_ERR("[iOS Vulkan]: Failed to create surface.\n"); return false; } /* TODO: Maybe iOS users should be able to * show/hide the status bar here? */ return true; } static void *cocoa_vk_gfx_ctx_init(void *video_driver) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*) calloc(1, sizeof(cocoa_vk_ctx_data_t)); if (!cocoa_ctx) return NULL; [apple_platform setViewType:APPLE_VIEW_TYPE_VULKAN]; if (!vulkan_context_init(&cocoa_ctx->vk, VULKAN_WSI_MVK_IOS)) { free(cocoa_ctx); return NULL; } return cocoa_ctx; } #endif #ifdef HAVE_COCOA_METAL static bool cocoa_vk_gfx_ctx_set_resize(void *data, unsigned width, unsigned height) { cocoa_vk_ctx_data_t *cocoa_ctx = (cocoa_vk_ctx_data_t*)data; cocoa_ctx->width = width; cocoa_ctx->height = height; if (!vulkan_create_swapchain(&cocoa_ctx->vk, width, height, cocoa_ctx->swap_interval)) { RARCH_ERR("[macOS/Vulkan]: Failed to update swapchain.\n"); return false; } cocoa_ctx->vk.context.flags |= VK_CTX_FLAG_INVALID_SWAPCHAIN; if (cocoa_ctx->vk.flags & VK_DATA_FLAG_CREATED_NEW_SWAPCHAIN) vulkan_acquire_next_image(&cocoa_ctx->vk); cocoa_ctx->vk.flags &= ~VK_DATA_FLAG_NEED_NEW_SWAPCHAIN; return true; } #endif const gfx_ctx_driver_t gfx_ctx_cocoavk = { cocoa_vk_gfx_ctx_init, cocoa_vk_gfx_ctx_destroy, cocoa_vk_gfx_ctx_get_api, cocoa_vk_gfx_ctx_bind_api, cocoa_vk_gfx_ctx_swap_interval, cocoa_vk_gfx_ctx_set_video_mode, #if MAC_OS_X_VERSION_10_7 && defined(OSX) cocoa_vk_gfx_ctx_get_video_size_osx10_7_and_up, #else cocoa_vk_gfx_ctx_get_video_size, #endif cocoa_vk_gfx_ctx_get_refresh_rate, NULL, /* get_video_output_size */ NULL, /* get_video_output_prev */ NULL, /* get_video_output_next */ cocoa_get_metrics, NULL, /* translate_aspect */ #ifdef OSX video_driver_update_title, #else NULL, /* update_title */ #endif cocoa_vk_gfx_ctx_check_window, #if defined(HAVE_COCOA_METAL) cocoa_vk_gfx_ctx_set_resize, #else NULL, /* set_resize */ #endif cocoa_has_focus, cocoa_vk_gfx_ctx_suppress_screensaver, #if defined(HAVE_COCOATOUCH) false, #else true, #endif cocoa_vk_gfx_ctx_swap_buffers, cocoa_vk_gfx_ctx_input_driver, cocoa_vk_gfx_ctx_get_proc_address, NULL, /* image_buffer_init */ NULL, /* image_buffer_write */ NULL, /* show_mouse */ "cocoavk", cocoa_vk_gfx_ctx_get_flags, cocoa_vk_gfx_ctx_set_flags, cocoa_vk_gfx_ctx_bind_hw_render, cocoa_vk_gfx_ctx_get_context_data, NULL /* make_current */ };