From 1ac50c17e8d6aceae198ec1ed3cea12875554828 Mon Sep 17 00:00:00 2001 From: Patrick Martin Date: Mon, 22 Feb 2016 17:15:16 -0800 Subject: [PATCH] Initial work on Vulkan support for Citra. Signed-off-by: Patrick Martin --- CMakeLists.txt | 20 +- externals/cmake-modules/FindVulkan.cmake | 32 ++ externals/cmake-modules/LibFindMacros.cmake | 265 +++++++++++++++ src/citra/CMakeLists.txt | 2 +- src/citra/emu_window/emu_window_glfw.cpp | 93 ++++-- src/citra/emu_window/emu_window_glfw.h | 8 +- src/common/emu_window.cpp | 17 + src/common/emu_window.h | 23 +- src/video_core/CMakeLists.txt | 33 +- .../renderer_vulkan/renderer_vulkan.cpp | 314 ++++++++++++++++++ .../renderer_vulkan/renderer_vulkan.h | 83 +++++ src/video_core/video_core.cpp | 11 + 12 files changed, 866 insertions(+), 35 deletions(-) create mode 100644 externals/cmake-modules/FindVulkan.cmake create mode 100644 externals/cmake-modules/LibFindMacros.cmake create mode 100644 src/video_core/renderer_vulkan/renderer_vulkan.cpp create mode 100644 src/video_core/renderer_vulkan/renderer_vulkan.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b8a981711..5f84afe4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,12 @@ option(ENABLE_QT "Enable the Qt frontend" ON) option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) option(CITRA_FORCE_QT4 "Use Qt4 even if Qt5 is available." OFF) +if (NOT ENABLE_QT) + option(ENABLE_VULKAN "Enable Vulkan support." OFF) +else() + set(ENABLE_VULKAN OFF) +endif() + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/pre-commit) message(STATUS "Copying pre-commit hook") file(COPY hooks/pre-commit @@ -136,9 +142,14 @@ find_package(OpenGL REQUIRED) include_directories(${OPENGL_INCLUDE_DIR}) if (ENABLE_GLFW) + if (ENABLE_VULKAN) + add_definitions(-DGLFW_INCLUDE_VULKAN) + endif() if (CITRA_USE_BUNDLED_GLFW) # Detect toolchain and platform - if (MSVC14 AND ARCHITECTURE_x86_64) + if (ENABLE_VULKAN) + message(FATAL_ERROR "No beta GLFW binaries are available bundled, please install glfw 3.2 manually.") + elseif (MSVC14 AND ARCHITECTURE_x86_64) set(GLFW_VER "glfw-3.1.1-msvc2015_64") elseif (MSVC12 AND ARCHITECTURE_x86_64) set(GLFW_VER "glfw-3.1.1-msvc2013_64") @@ -211,6 +222,13 @@ if (ENABLE_QT) endif() endif() +if (ENABLE_VULKAN) + find_package(Vulkan REQUIRED) + include_directories(${Vulkan_INCLUDE_DIRS}) + set(VK_LIBRARYS ${Vulkan_LIBRARIES}) + add_definitions(-DVKENABLED) +endif() + # This function should be passed a list of all files in a target. It will automatically generate # file groups following the directory hierarchy, so that the layout of the files in IDEs matches the # one in the filesystem. diff --git a/externals/cmake-modules/FindVulkan.cmake b/externals/cmake-modules/FindVulkan.cmake new file mode 100644 index 000000000..f5a2d521e --- /dev/null +++ b/externals/cmake-modules/FindVulkan.cmake @@ -0,0 +1,32 @@ +# - Try to find Vulkan SDK +# Once done, this will define +# +# Vulkan_FOUND - system has Vulkan SDK +# Vulkan_INCLUDE_DIRS - the Vulkan include directories +# Vulkan_LIBRARIES - link these to use Vulkan + +include(LibFindMacros) + +# Dependencies +#libfind_package(PACKAGE DEPENDANCY) + +# Use pkg-config to get hints about paths +libfind_pkg_check_modules(Vulkan_PKGCONF Vulkan) + +# Include dir +find_path(Vulkan_INCLUDE_DIR + NAMES vulkan/vulkan.h + PATHS ${Vulkan_PKGCONF_INCLUDE_DIRS} +) + +# Finally the library itself +find_library(Vulkan_LIBRARY + NAMES vulkan-1 + PATHS ${Vulkan_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest. +# NOTE: Singular variables for this library, plural for libraries this this lib depends on. +set(Vulkan_PROCESS_INCLUDES Vulkan_INCLUDE_DIR Vulkan_INCLUDE_DIRS) +set(Vulkan_PROCESS_LIBS Vulkan_LIBRARY Vulkan_LIBRARIES) +libfind_process(Vulkan) \ No newline at end of file diff --git a/externals/cmake-modules/LibFindMacros.cmake b/externals/cmake-modules/LibFindMacros.cmake new file mode 100644 index 000000000..f6800aa7b --- /dev/null +++ b/externals/cmake-modules/LibFindMacros.cmake @@ -0,0 +1,265 @@ +# Version 2.2 +# Public Domain, originally written by Lasse Kärkkäinen +# Maintained at https://github.com/Tronic/cmake-modules +# Please send your improvements as pull requests on Github. + +# Find another package and make it a dependency of the current package. +# This also automatically forwards the "REQUIRED" argument. +# Usage: libfind_package( [extra args to find_package]) +macro (libfind_package PREFIX PKG) + set(${PREFIX}_args ${PKG} ${ARGN}) + if (${PREFIX}_FIND_REQUIRED) + set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) + endif() + find_package(${${PREFIX}_args}) + set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) + unset(${PREFIX}_args) +endmacro() + +# A simple wrapper to make pkg-config searches a bit easier. +# Works the same as CMake's internal pkg_check_modules but is always quiet. +macro (libfind_pkg_check_modules) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${ARGN} QUIET) + endif() +endmacro() + +# Avoid useless copy&pasta by doing what most simple libraries do anyway: +# pkg-config, find headers, find library. +# Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) +# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) +function (libfind_pkg_detect PREFIX) + # Parse arguments + set(argname pkgargs) + foreach (i ${ARGN}) + if ("${i}" STREQUAL "FIND_PATH") + set(argname pathargs) + elseif ("${i}" STREQUAL "FIND_LIBRARY") + set(argname libraryargs) + else() + set(${argname} ${${argname}} ${i}) + endif() + endforeach() + if (NOT pkgargs) + message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") + endif() + # Find library + libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + if (pathargs) + find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) + endif() + if (libraryargs) + find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) + endif() +endfunction() + +# Extracts a version #define from a version.h file, output stored to _VERSION. +# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) +# Fourth argument "QUIET" may be used for silently testing different define names. +# This function does nothing if the version variable is already defined. +function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) + # Skip processing if we already have a version or if the include dir was not found + if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR) + return() + endif() + set(quiet ${${PREFIX}_FIND_QUIETLY}) + # Process optional arguments + foreach(arg ${ARGN}) + if (arg STREQUAL "QUIET") + set(quiet TRUE) + else() + message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.") + endif() + endforeach() + # Read the header and parse for version number + set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + if (NOT EXISTS ${filename}) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + file(READ "${filename}" header) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") + # No regex match? + if (match STREQUAL header) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + # Export the version string + set(${PREFIX}_VERSION "${match}" PARENT_SCOPE) +endfunction() + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +function (libfind_process PREFIX) + # Skip processing if already processed during this configuration run + if (${PREFIX}_FOUND) + return() + endif() + + set(found TRUE) # Start with the assumption that the package was found + + # Did we find any files? Did we miss includes? These are for formatting better error messages. + set(some_files FALSE) + set(missing_headers FALSE) + + # Shorthands for some variables that we need often + set(quiet ${${PREFIX}_FIND_QUIETLY}) + set(required ${${PREFIX}_FIND_REQUIRED}) + set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) + set(findver "${${PREFIX}_FIND_VERSION}") + set(version "${${PREFIX}_VERSION}") + + # Lists of config option names (all, includes, libs) + unset(configopts) + set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) + set(libraryopts ${${PREFIX}_PROCESS_LIBS}) + + # Process deps to add to + foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) + if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) + # The package seems to export option lists that we can use, woohoo! + list(APPEND includeopts ${${i}_INCLUDE_OPTS}) + list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) + else() + # If plural forms don't exist or they equal singular forms + if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR + ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) + # Singular forms can be used + if (DEFINED ${i}_INCLUDE_DIR) + list(APPEND includeopts ${i}_INCLUDE_DIR) + endif() + if (DEFINED ${i}_LIBRARY) + list(APPEND libraryopts ${i}_LIBRARY) + endif() + else() + # Oh no, we don't know the option names + message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") + endif() + endif() + endforeach() + + if (includeopts) + list(REMOVE_DUPLICATES includeopts) + endif() + + if (libraryopts) + list(REMOVE_DUPLICATES libraryopts) + endif() + + string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") + if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") + message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") + endif() + + # Include/library names separated by spaces (notice: not CMake lists) + unset(includes) + unset(libs) + + # Process all includes and set found false if any are missing + foreach (i ${includeopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND includes "${${i}}") + else() + set(found FALSE) + set(missing_headers TRUE) + endif() + endforeach() + + # Process all libraries and set found false if any are missing + foreach (i ${libraryopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND libs "${${i}}") + else() + set (found FALSE) + endif() + endforeach() + + # Version checks + if (found AND findver) + if (NOT version) + message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") + elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) + set(found FALSE) + set(version_unsuitable TRUE) + endif() + endif() + + # If all-OK, hide all config options, export variables, print status and exit + if (found) + foreach (i ${configopts}) + mark_as_advanced(${i}) + endforeach() + if (NOT quiet) + message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + if (LIBFIND_DEBUG) + message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") + message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") + message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") + message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") + message(STATUS " ${PREFIX}_LIBRARIES=${libs}") + endif() + set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) + set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) + set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) + set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) + set (${PREFIX}_FOUND TRUE PARENT_SCOPE) + endif() + return() + endif() + + # Format messages for debug info and the type of error + set(vars "Relevant CMake configuration variables:\n") + foreach (i ${configopts}) + mark_as_advanced(CLEAR ${i}) + set(val ${${i}}) + if ("${val}" STREQUAL "${i}-NOTFOUND") + set (val "") + elseif (val AND NOT EXISTS ${val}) + set (val "${val} (does not exist)") + else() + set(some_files TRUE) + endif() + set(vars "${vars} ${i}=${val}\n") + endforeach() + set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") + if (version_unsuitable) + set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") + if (exactver) + set(msg "${msg} only version ${findver} is acceptable.") + else() + set(msg "${msg} version ${findver} is the minimum requirement.") + endif() + else() + if (missing_headers) + set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") + elseif (some_files) + set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") + if(findver) + set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") + endif() + else() + set(msg "We were unable to find package ${PREFIX}.") + endif() + endif() + + # Fatal error out if REQUIRED + if (required) + set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") + message(FATAL_ERROR "${msg}\n${vars}") + endif() + # Otherwise just print a nasty warning + if (NOT quiet) + message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") + endif() +endfunction() diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index e7f8a17f9..a0dbf43dd 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -18,7 +18,7 @@ link_directories(${GLFW_LIBRARY_DIRS}) add_executable(citra ${SRCS} ${HEADERS}) target_link_libraries(citra core video_core common) -target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} inih glad) +target_link_libraries(citra ${GLFW_LIBRARIES} ${OPENGL_gl_LIBRARY} ${VK_LIBRARYS} inih glad) if (MSVC) target_link_libraries(citra getopt) endif() diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp index 9453b1f48..012b7839d 100644 --- a/src/citra/emu_window/emu_window_glfw.cpp +++ b/src/citra/emu_window/emu_window_glfw.cpp @@ -8,9 +8,18 @@ // Let’s use our own GL header, instead of one from GLFW. #include +#ifdef VKENABLED +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#else +#define VK_USE_PLATFORM_XCB_KHR +#endif +#include +#endif #define GLFW_INCLUDE_NONE #include + #include "common/assert.h" #include "common/key_map.h" #include "common/logging/log.h" @@ -27,7 +36,26 @@ EmuWindow_GLFW* EmuWindow_GLFW::GetEmuWindow(GLFWwindow* win) { return static_cast(glfwGetWindowUserPointer(win)); } - +#ifdef VKENABLED +bool EmuWindow_GLFW::VulkanSupported(){ + return glfwVulkanSupported() == GLFW_TRUE; +} +bool EmuWindow_GLFW::CanDevicePresent(void * instance, void * device,uint32_t queue){ + return glfwGetPhysicalDevicePresentationSupport((VkInstance)instance,(VkPhysicalDevice)device,queue); +} +void * EmuWindow_GLFW::CreateVulkanSurface(void * instance){ + VkSurfaceKHR surface; + if(glfwCreateWindowSurface((VkInstance)instance, this->m_render_window, VK_NULL_HANDLE, &surface)==VK_SUCCESS) + return surface; + return NULL; +} +void EmuWindow_GLFW::DestroyVulkanSurface(void * instance, void * surface){ + vkDestroySurfaceKHR((VkInstance)instance, (VkSurfaceKHR)surface, VK_NULL_HANDLE); +} +const char** EmuWindow_GLFW::RequiredVulkanExtensions(uint32_t * count){ + return glfwGetRequiredInstanceExtensions(count); +} +#endif void EmuWindow_GLFW::OnMouseButtonEvent(GLFWwindow* win, int button, int action, int mods) { if (button == GLFW_MOUSE_BUTTON_LEFT) { auto emu_window = GetEmuWindow(win); @@ -88,28 +116,49 @@ EmuWindow_GLFW::EmuWindow_GLFW() { LOG_CRITICAL(Frontend, "Failed to initialize GLFW! Exiting..."); exit(1); } - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - // GLFW on OSX requires these window hints to be set to create a 3.2+ GL context. - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); - m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth, - (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight), - window_title.c_str(), nullptr, nullptr); - - if (m_render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create GLFW window! Exiting..."); - exit(1); - } - - glfwSetWindowUserPointer(m_render_window, this); - - // Notify base interface about window state int width, height; - glfwGetFramebufferSize(m_render_window, &width, &height); - OnFramebufferResizeEvent(m_render_window, width, height); +#ifdef VKENABLED + if (!VulkanSupported()){ +#endif + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + // GLFW on OSX requires these window hints to be set to create a 3.2+ GL context. + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth, + (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight), + window_title.c_str(), nullptr, nullptr); + + if (m_render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create GLFW window! Exiting..."); + exit(1); + } + + glfwSetWindowUserPointer(m_render_window, this); + + // Notify base interface about window state + glfwGetFramebufferSize(m_render_window, &width, &height); + OnFramebufferResizeEvent(m_render_window, width, height); + +#ifdef VKENABLED + }else{ + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + std::string window_title = Common::StringFromFormat("Citra | %s-%s (Vulkan)", Common::g_scm_branch, Common::g_scm_desc); + m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth, + (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight), + window_title.c_str(), nullptr, nullptr); + + if (m_render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create GLFW window! Exiting..."); + exit(1); + } + + glfwSetWindowUserPointer(m_render_window, this); + } +#endif glfwGetWindowSize(m_render_window, &width, &height); OnClientAreaResizeEvent(m_render_window, width, height); diff --git a/src/citra/emu_window/emu_window_glfw.h b/src/citra/emu_window/emu_window_glfw.h index 7ccd5e6aa..e6ee3faf4 100644 --- a/src/citra/emu_window/emu_window_glfw.h +++ b/src/citra/emu_window/emu_window_glfw.h @@ -41,7 +41,13 @@ public: static void OnFramebufferResizeEvent(GLFWwindow* win, int width, int height); void ReloadSetKeymaps() override; - +#ifdef VKENABLED + bool VulkanSupported() override; + bool CanDevicePresent(void * instance, void * device,uint32_t queue) override; + void * CreateVulkanSurface(void * instance) override; + void DestroyVulkanSurface(void * instance, void * surface) override; + const char** RequiredVulkanExtensions(uint32_t * count) override; +#endif private: void OnMinimalClientAreaChangeRequest(const std::pair& minimal_size) override; diff --git a/src/common/emu_window.cpp b/src/common/emu_window.cpp index b2807354a..cb794895a 100644 --- a/src/common/emu_window.cpp +++ b/src/common/emu_window.cpp @@ -11,6 +11,23 @@ #include "emu_window.h" #include "video_core/video_core.h" +#ifdef VKENABLED +bool EmuWindow::VulkanSupported(){ + return false; +} +bool EmuWindow::CanDevicePresent(void * instance, void* device,uint32_t queue){ + return false; +} +void * EmuWindow::CreateVulkanSurface(void * instance){ + return NULL; +} +void EmuWindow::DestroyVulkanSurface(void * instance, void * surface){} +const char** EmuWindow::RequiredVulkanExtensions(uint32_t * count){ + *count = 0; + return NULL; +} +#endif + void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) { pad_state.hex |= KeyMap::GetPadKey(key).hex; } diff --git a/src/common/emu_window.h b/src/common/emu_window.h index a0ae4c9fa..b70d756b4 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -143,7 +143,28 @@ public: const FramebufferLayout& GetFramebufferLayout() const { return framebuffer_layout; } - +#ifdef VKENABLED + /** + * Query if the vulkan api is supported. + */ + virtual bool VulkanSupported(); + /** + * Check if the device can present. + */ + virtual bool CanDevicePresent(void* instance, void* device,uint32_t queue); + /** + * Gets a vulkan surface for this emu window returns NULL if unsupported. + */ + virtual void * CreateVulkanSurface(void * instance); + /** + * Destroys the passed vulkan surface for the passed instance and this emu window. + */ + virtual void DestroyVulkanSurface(void * instance, void * surface); + /** + * Lists all required vulkan extensions for this emu window + */ + virtual const char** RequiredVulkanExtensions(uint32_t * count); +#endif protected: EmuWindow() { // TODO: Find a better place to set this. diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 4b5d298f3..e5f3d1e62 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,10 +1,32 @@ -set(SRCS +if(ENABLE_VULKAN) +set(RENDER_VK_SRCS + renderer_vulkan/renderer_vulkan.cpp + ) +set(RENDER_VK_HEADERS + renderer_vulkan/renderer_vulkan.h + ) +endif() +set(RENDER_SRCS renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer_cache.cpp renderer_opengl/gl_shader_gen.cpp renderer_opengl/gl_shader_util.cpp renderer_opengl/gl_state.cpp renderer_opengl/renderer_opengl.cpp + ${RENDER_VK_SRCS} + ) +set(RENDER_HEADERS + renderer_opengl/gl_rasterizer.h + renderer_opengl/gl_rasterizer_cache.h + renderer_opengl/gl_resource_manager.h + renderer_opengl/gl_shader_gen.h + renderer_opengl/gl_shader_util.h + renderer_opengl/gl_state.h + renderer_opengl/pica_to_gl.h + renderer_opengl/renderer_opengl.h + ${RENDER_VK_HEADERS} + ) +set(SRCS ${RENDER_SRCS} debug_utils/debug_utils.cpp clipper.cpp command_processor.cpp @@ -21,14 +43,7 @@ set(SRCS set(HEADERS debug_utils/debug_utils.h - renderer_opengl/gl_rasterizer.h - renderer_opengl/gl_rasterizer_cache.h - renderer_opengl/gl_resource_manager.h - renderer_opengl/gl_shader_gen.h - renderer_opengl/gl_shader_util.h - renderer_opengl/gl_state.h - renderer_opengl/pica_to_gl.h - renderer_opengl/renderer_opengl.h + ${RENDER_HEADERS} clipper.h command_processor.h gpu_debugger.h diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp new file mode 100644 index 000000000..e21ff6919 --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -0,0 +1,314 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/assert.h" +#include "common/emu_window.h" +#include "common/logging/log.h" +#include "common/profiler_reporting.h" + +#include "core/memory.h" +#include "core/settings.h" +#include "core/hw/gpu.h" +#include "core/hw/hw.h" +#include "core/hw/lcd.h" + +#include "video_core/video_core.h" +#include "video_core/debug_utils/debug_utils.h" + +#include "video_core/renderer_opengl/gl_shader_util.h" + +#include "renderer_vulkan.h" + +#define INIT_INSTANCE 1 +#define INIT_DEVICE 2 +#define INIT_SURFACE 4 +#define INIT_SWAPCHAIN 8 +#define INIT_TOPSCREEN 16 +#define INIT_BOTTOMSCREEN 32 +#define INIT_MEMORYTOP 64 +#define INIT_MEMORYBOTTOM 128 +#define INIT_VIEWTOP 256 +#define INIT_VIEWBOTTOM 512 + +static const char vertex_shader[] = R"( +#version 150 core + +in vec2 vert_position; +in vec2 vert_tex_coord; +out vec2 frag_tex_coord; + +// This is a truncated 3x3 matrix for 2D transformations: +// The upper-left 2x2 submatrix performs scaling/rotation/mirroring. +// The third column performs translation. +// The third row could be used for projection, which we don't need in 2D. It hence is assumed to +// implicitly be [0, 0, 1] +uniform mat3x2 modelview_matrix; + +void main() { + // Multiply input position by the rotscale part of the matrix and then manually translate by + // the last column. This is equivalent to using a full 3x3 matrix and expanding the vector + // to `vec3(vert_position.xy, 1.0)` + gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0); + frag_tex_coord = vert_tex_coord; +} +)"; + +static const char fragment_shader[] = R"( +#version 150 core + +in vec2 frag_tex_coord; +out vec4 color; + +uniform sampler2D color_texture; + +void main() { + color = texture(color_texture, frag_tex_coord); +} +)"; + +inline bool HasPicaFeatures(VkPhysicalDeviceFeatures * features){ + return features->geometryShader;//&& features-> +} + +/** + * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left + * corner and (width, height) on the lower-bottom. + * + * The projection part of the matrix is trivial, hence these operations are represented + * by a 3x2 matrix. + */ +static std::array MakeOrthographicMatrix(const float width, const float height) { + std::array matrix; + + matrix[0] = 2.f / width; matrix[2] = 0.f; matrix[4] = -1.f; + matrix[1] = 0.f; matrix[3] = -2.f / height; matrix[5] = 1.f; + // Last matrix row is implicitly assumed to be [0, 0, 1]. + + return matrix; +} + +RendererVulkan::RendererVulkan(){ + resolution_width = std::max(VideoCore::kScreenTopWidth, VideoCore::kScreenBottomWidth); + resolution_height = VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight; +} + +void RendererVulkan::SetWindow(EmuWindow* window){ + this->render_window = window; +} + +void RendererVulkan::Init(){ + VkApplicationInfo appinfo; + appinfo.apiVersion = VK_API_VERSION; + appinfo.applicationVersion = 0; + appinfo.pApplicationName = "CTR"; + appinfo.pEngineName = "Citra Emulator"; + appinfo.pNext = VK_NULL_HANDLE; + appinfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + uint32_t extensionCount; + const char ** enabledExtensions = render_window->RequiredVulkanExtensions(&extensionCount); + VkInstanceCreateInfo info; + info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + info.pNext = VK_NULL_HANDLE; + info.ppEnabledLayerNames = VK_NULL_HANDLE; + info.enabledLayerCount = 0; + info.pApplicationInfo = &appinfo; + info.enabledExtensionCount = extensionCount; + info.ppEnabledExtensionNames = enabledExtensions; + VkResult result = vkCreateInstance(&info, VK_NULL_HANDLE, &instance); + + if (result == VK_SUCCESS){ + this->Initialized |= INIT_INSTANCE; + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, VK_NULL_HANDLE); + VkPhysicalDevice * devices = (VkPhysicalDevice*)malloc(deviceCount*sizeof(VkPhysicalDevice)); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices); + VkPhysicalDeviceProperties deviceProps; + VkPhysicalDeviceFeatures deviceFeatures; + uint32_t lastAPIVersion = 0; + VkPhysicalDevice device = VK_NULL_HANDLE; + for (int i = 0; i < deviceCount; i++){ + vkGetPhysicalDeviceProperties(devices[i], &deviceProps); + vkGetPhysicalDeviceFeatures(devices[i], &deviceFeatures); + if (deviceProps.apiVersion > lastAPIVersion && HasPicaFeatures(&deviceFeatures)){ + device = devices[i]; + } + } + if (device != VK_NULL_HANDLE){ + VkDeviceCreateInfo deviceInfo; + vkGetPhysicalDeviceFeatures(device, &deviceFeatures); + deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceInfo.pEnabledFeatures = &deviceFeatures; + deviceInfo.enabledExtensionCount = 0; + deviceInfo.enabledLayerCount = 0; + deviceInfo.queueCreateInfoCount = 0; + deviceInfo.ppEnabledExtensionNames = VK_NULL_HANDLE; + deviceInfo.ppEnabledLayerNames = VK_NULL_HANDLE; + deviceInfo.pNext = VK_NULL_HANDLE; + deviceInfo.pQueueCreateInfos = VK_NULL_HANDLE; + if (vkCreateDevice(device, &deviceInfo, VK_NULL_HANDLE, &this->device) == VK_SUCCESS){ + this->Initialized |= INIT_DEVICE; + surface = (VkSurfaceKHR)render_window->CreateVulkanSurface(instance); + if (surface != NULL){ + this->Initialized |= INIT_SURFACE; + VkSwapchainCreateInfoKHR swapchainInfo = {}; + swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchainInfo.imageFormat = VK_FORMAT_R8G8B8_UINT; + swapchainInfo.surface = surface; + swapchainInfo.minImageCount = 2; + swapchainInfo.clipped = true; + swapchainInfo.oldSwapchain = VK_NULL_HANDLE; + vkCreateSwapchainKHR(this->device, &swapchainInfo, VK_NULL_HANDLE, &swapchain); + } + } + } + } +} + +void RendererVulkan::InitVulkanObjects(){ + if (this->Initialized & INIT_INSTANCE){ + VkImageCreateInfo imageTop = {}; + VkImageCreateInfo imageBottom = {}; + + imageTop.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageTop.pNext = VK_NULL_HANDLE; + imageTop.imageType = VK_IMAGE_TYPE_2D; + imageTop.format = VK_FORMAT_R8G8B8_UINT; + imageTop.extent = { VideoCore::kScreenTopHeight, VideoCore::kScreenTopWidth, 1 }; + imageTop.mipLevels = 1; + imageTop.arrayLayers = 1; + imageBottom.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageTop.samples = VK_SAMPLE_COUNT_1_BIT; + imageTop.tiling = VK_IMAGE_TILING_OPTIMAL; + + imageBottom.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageBottom.pNext = VK_NULL_HANDLE; + imageBottom.imageType = VK_IMAGE_TYPE_2D; + imageBottom.format = VK_FORMAT_R8G8B8_UINT; + imageBottom.extent = { VideoCore::kScreenBottomWidth, VideoCore::kScreenBottomHeight, 1 }; + imageBottom.mipLevels = 1; + imageBottom.arrayLayers = 1; + imageBottom.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageBottom.samples = VK_SAMPLE_COUNT_1_BIT; + imageBottom.tiling = VK_IMAGE_TILING_OPTIMAL; + + VkResult result = vkCreateImage(device, &imageTop, VK_NULL_HANDLE, &textures[0].texture); + bool success = true; + if (result == VK_SUCCESS){ + this->Initialized |= INIT_TOPSCREEN; + }else{ + success = false; + } + result = vkCreateImage(device, &imageBottom, VK_NULL_HANDLE, &textures[1].texture); + if (result == VK_SUCCESS){ + this->Initialized |= INIT_BOTTOMSCREEN; + }else{ + success = false; + } + if (success){ + VkMemoryAllocateInfo imageTopAllocation; + VkMemoryRequirements imageTopMemoryReqs; + VkMemoryAllocateInfo imageBottomAllocation; + VkMemoryRequirements imageBottomMemoryReqs; + vkGetImageMemoryRequirements(device, textures[0].texture, &imageTopMemoryReqs); + vkGetImageMemoryRequirements(device, textures[1].texture, &imageBottomMemoryReqs); + imageTopAllocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + imageTopAllocation.pNext = VK_NULL_HANDLE; + imageTopAllocation.allocationSize = imageTopMemoryReqs.size; + imageTopAllocation.memoryTypeIndex = 0; //TODO: figgure out what goes here. + result = vkAllocateMemory(device,&imageTopAllocation,VK_NULL_HANDLE,&textures[0].memory); + + if (result == VK_SUCCESS){ + this->Initialized |= INIT_MEMORYTOP; + }else{ + success = false; + } + + imageBottomAllocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + imageBottomAllocation.pNext = VK_NULL_HANDLE; + imageBottomAllocation.allocationSize = imageBottomMemoryReqs.size; + imageBottomAllocation.memoryTypeIndex = 0; //TODO: figgure out what goes here. + result = vkAllocateMemory(device,&imageBottomAllocation,VK_NULL_HANDLE,&textures[0].memory); + + if (result == VK_SUCCESS){ + this->Initialized |= INIT_MEMORYBOTTOM; + }else{ + success = false; + } + + if (success){ + VkImageViewCreateInfo viewTopInfo; + VkImageViewCreateInfo viewBottomInfo; + + viewTopInfo.image = textures[0].texture; + viewBottomInfo.image = textures[1].texture; + + viewTopInfo.format = VK_FORMAT_R8G8B8_UINT; + viewBottomInfo.format = VK_FORMAT_R8G8B8_UINT; + + viewTopInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewBottomInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + + result = vkCreateImageView(device,&viewTopInfo,VK_NULL_HANDLE,&textures[0].view); + if (result == VK_SUCCESS){ + this->Initialized |= INIT_VIEWTOP; + }else{ + success = false; + } + result = vkCreateImageView(device,&viewBottomInfo,VK_NULL_HANDLE,&textures[1].view); + if (result == VK_SUCCESS){ + this->Initialized |= INIT_VIEWBOTTOM; + }else{ + success = false; + } + } + } + } +} + +/// Swap buffers (render frame) +void RendererVulkan::SwapBuffers() { + + for (int i : {0, 1}) { + const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; + + u32 lcd_color_addr = (i == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom); + lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr; + LCD::Regs::ColorFill color_fill = {0}; + LCD::Read(color_fill.raw, lcd_color_addr); + if (color_fill.is_enabled) { + } + else{ + } + } +} + +/** + * Draws the emulated screens to the emulator window. + */ +void RendererVulkan::DrawScreens() { + auto layout = render_window->GetFramebufferLayout(); + DrawSingleScreenRotated(textures[0], (float)layout.top_screen.left, (float)layout.top_screen.top, + (float)layout.top_screen.GetWidth(), (float)layout.top_screen.GetHeight()); + DrawSingleScreenRotated(textures[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, + (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight()); + + m_current_frame++; +} + +RendererVulkan::~RendererVulkan(){ + if (this->Initialized&INIT_VIEWBOTTOM)vkDestroyImageView(device, textures[1].view, VK_NULL_HANDLE); + if (this->Initialized&INIT_VIEWTOP)vkDestroyImageView(device, textures[0].view, VK_NULL_HANDLE); + if (this->Initialized&INIT_MEMORYBOTTOM)vkFreeMemory(this->device, this->textures[1].memory, VK_NULL_HANDLE); + if (this->Initialized&INIT_MEMORYTOP)vkFreeMemory(this->device, this->textures[0].memory, VK_NULL_HANDLE); + if (this->Initialized&INIT_BOTTOMSCREEN)vkDestroyImage(this->device,this->textures[1].texture,VK_NULL_HANDLE); + if (this->Initialized&INIT_TOPSCREEN)vkDestroyImage(this->device,this->textures[0].texture,VK_NULL_HANDLE); + //if (this->Initialized&INIT_SWAPCHAIN)vkDestroySwapchainKHR(device, swapchain, VK_NULL_HANDLE); + if (this->Initialized&INIT_SURFACE)render_window->DestroyVulkanSurface(instance, surface); + if (this->Initialized&INIT_DEVICE)vkDestroyDevice(this->device,VK_NULL_HANDLE); + if (this->Initialized&INIT_INSTANCE)vkDestroyInstance(this->instance,VK_NULL_HANDLE); +} \ No newline at end of file diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h new file mode 100644 index 000000000..fd2cb081d --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -0,0 +1,83 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "core/hw/gpu.h" + +#include "video_core/renderer_base.h" + +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#else +#define VK_USE_PLATFORM_XCB_KHR +#endif +#include + +class EmuWindow; + +class RendererVulkan : public RendererBase { +public: + + RendererVulkan(); + ~RendererVulkan() override; + + /// Swap buffers (render frame) + void SwapBuffers() override; + + /** + * Set the emulator window to use for renderer + * @param window EmuWindow handle to emulator window to use for rendering + */ + void SetWindow(EmuWindow* window) override; + + /// Initialize the renderer + void Init() override; + + /// Shutdown the renderer + void ShutDown() override; + +private: + VkSurfaceKHR screen; + /// Structure used for storing information about the textures for each 3DS screen + struct TextureInfo { + VkSampler sampler; + VkImage texture; + VkImageLayout layout; + VkDeviceMemory memory; + VkImageView view; + }; + + void InitVulkanObjects(); + void ConfigureFramebufferTexture(TextureInfo& texture, + const GPU::Regs::FramebufferConfig& framebuffer); + void DrawScreens(); + void DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h); + void UpdateFramerate(); + + // Loads framebuffer from emulated memory into the active Vulkan texture. + void LoadFBToActiveVKTexture(const GPU::Regs::FramebufferConfig& framebuffer, + const TextureInfo& texture); + // Fills active OpenGL texture with the given RGB color. + void LoadColorToActiveVKTexture(u8 color_r, u8 color_g, u8 color_b, + const TextureInfo& texture); + + short Initialized = 0; + + EmuWindow* render_window; ///< Handle to render window + + int resolution_width; ///< Current resolution width + int resolution_height; ///< Current resolution height + + VkInstance instance; + VkDevice device; + + VkSurfaceKHR surface; + VkSwapchainKHR swapchain; + std::array textures; ///< Textures for top and bottom screens respectively +}; diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 912db91a4..769bf1554 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -14,6 +14,9 @@ #include "video_core/pica.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" +#ifdef VKENABLED +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#endif #include "video_core/renderer_opengl/renderer_opengl.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -32,7 +35,15 @@ void Init(EmuWindow* emu_window) { Pica::Init(); g_emu_window = emu_window; +#ifdef VKENABLED + if (emu_window->VulkanSupported()){ + g_renderer = Common::make_unique(); + }else{ + g_renderer = Common::make_unique(); + } +#else g_renderer = Common::make_unique(); +#endif g_renderer->SetWindow(g_emu_window); g_renderer->Init();