Initial work on Vulkan support for Citra.

Signed-off-by: Patrick Martin <patrick.martin.r@gmail.com>
This commit is contained in:
Patrick Martin 2016-02-22 17:15:16 -08:00
parent 0d086616d1
commit 1ac50c17e8
12 changed files with 866 additions and 35 deletions

View File

@ -42,6 +42,12 @@ option(ENABLE_QT "Enable the Qt frontend" ON)
option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
option(CITRA_FORCE_QT4 "Use Qt4 even if Qt5 is available." 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) if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook") message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit file(COPY hooks/pre-commit
@ -136,9 +142,14 @@ find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR}) include_directories(${OPENGL_INCLUDE_DIR})
if (ENABLE_GLFW) if (ENABLE_GLFW)
if (ENABLE_VULKAN)
add_definitions(-DGLFW_INCLUDE_VULKAN)
endif()
if (CITRA_USE_BUNDLED_GLFW) if (CITRA_USE_BUNDLED_GLFW)
# Detect toolchain and platform # 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") set(GLFW_VER "glfw-3.1.1-msvc2015_64")
elseif (MSVC12 AND ARCHITECTURE_x86_64) elseif (MSVC12 AND ARCHITECTURE_x86_64)
set(GLFW_VER "glfw-3.1.1-msvc2013_64") set(GLFW_VER "glfw-3.1.1-msvc2013_64")
@ -211,6 +222,13 @@ if (ENABLE_QT)
endif() endif()
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 # 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 # file groups following the directory hierarchy, so that the layout of the files in IDEs matches the
# one in the filesystem. # one in the filesystem.

View File

@ -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)

View File

@ -0,0 +1,265 @@
# Version 2.2
# Public Domain, originally written by Lasse Kärkkäinen <tronic>
# 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(<prefix> <another 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(<prefix> <pkg-config args> FIND_PATH <name> [other args] FIND_LIBRARY <name> [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 <PREFIX>_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} \"<version>\" 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 "<not found>")
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()

View File

@ -18,7 +18,7 @@ link_directories(${GLFW_LIBRARY_DIRS})
add_executable(citra ${SRCS} ${HEADERS}) add_executable(citra ${SRCS} ${HEADERS})
target_link_libraries(citra core video_core common) 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) if (MSVC)
target_link_libraries(citra getopt) target_link_libraries(citra getopt)
endif() endif()

View File

@ -8,9 +8,18 @@
// Lets use our own GL header, instead of one from GLFW. // Lets use our own GL header, instead of one from GLFW.
#include <glad/glad.h> #include <glad/glad.h>
#ifdef VKENABLED
#ifdef _WIN32
#define VK_USE_PLATFORM_WIN32_KHR
#else
#define VK_USE_PLATFORM_XCB_KHR
#endif
#include <vulkan/vulkan.h>
#endif
#define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/key_map.h" #include "common/key_map.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -27,7 +36,26 @@
EmuWindow_GLFW* EmuWindow_GLFW::GetEmuWindow(GLFWwindow* win) { EmuWindow_GLFW* EmuWindow_GLFW::GetEmuWindow(GLFWwindow* win) {
return static_cast<EmuWindow_GLFW*>(glfwGetWindowUserPointer(win)); return static_cast<EmuWindow_GLFW*>(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) { void EmuWindow_GLFW::OnMouseButtonEvent(GLFWwindow* win, int button, int action, int mods) {
if (button == GLFW_MOUSE_BUTTON_LEFT) { if (button == GLFW_MOUSE_BUTTON_LEFT) {
auto emu_window = GetEmuWindow(win); auto emu_window = GetEmuWindow(win);
@ -88,28 +116,49 @@ EmuWindow_GLFW::EmuWindow_GLFW() {
LOG_CRITICAL(Frontend, "Failed to initialize GLFW! Exiting..."); LOG_CRITICAL(Frontend, "Failed to initialize GLFW! Exiting...");
exit(1); 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; int width, height;
glfwGetFramebufferSize(m_render_window, &width, &height); #ifdef VKENABLED
OnFramebufferResizeEvent(m_render_window, width, height); 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); glfwGetWindowSize(m_render_window, &width, &height);
OnClientAreaResizeEvent(m_render_window, width, height); OnClientAreaResizeEvent(m_render_window, width, height);

View File

@ -41,7 +41,13 @@ public:
static void OnFramebufferResizeEvent(GLFWwindow* win, int width, int height); static void OnFramebufferResizeEvent(GLFWwindow* win, int width, int height);
void ReloadSetKeymaps() override; 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: private:
void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override; void OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) override;

View File

@ -11,6 +11,23 @@
#include "emu_window.h" #include "emu_window.h"
#include "video_core/video_core.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) { void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) {
pad_state.hex |= KeyMap::GetPadKey(key).hex; pad_state.hex |= KeyMap::GetPadKey(key).hex;
} }

View File

@ -143,7 +143,28 @@ public:
const FramebufferLayout& GetFramebufferLayout() const { const FramebufferLayout& GetFramebufferLayout() const {
return framebuffer_layout; 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: protected:
EmuWindow() { EmuWindow() {
// TODO: Find a better place to set this. // TODO: Find a better place to set this.

View File

@ -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.cpp
renderer_opengl/gl_rasterizer_cache.cpp renderer_opengl/gl_rasterizer_cache.cpp
renderer_opengl/gl_shader_gen.cpp renderer_opengl/gl_shader_gen.cpp
renderer_opengl/gl_shader_util.cpp renderer_opengl/gl_shader_util.cpp
renderer_opengl/gl_state.cpp renderer_opengl/gl_state.cpp
renderer_opengl/renderer_opengl.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 debug_utils/debug_utils.cpp
clipper.cpp clipper.cpp
command_processor.cpp command_processor.cpp
@ -21,14 +43,7 @@ set(SRCS
set(HEADERS set(HEADERS
debug_utils/debug_utils.h debug_utils/debug_utils.h
renderer_opengl/gl_rasterizer.h ${RENDER_HEADERS}
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
clipper.h clipper.h
command_processor.h command_processor.h
gpu_debugger.h gpu_debugger.h

View File

@ -0,0 +1,314 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#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<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, const float height) {
std::array<GLfloat, 3 * 2> 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);
}

View File

@ -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 <array>
#include <glad/glad.h>
#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 <vulkan/vulkan.h>
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<TextureInfo, 2> textures; ///< Textures for top and bottom screens respectively
};

View File

@ -14,6 +14,9 @@
#include "video_core/pica.h" #include "video_core/pica.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/video_core.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" #include "video_core/renderer_opengl/renderer_opengl.h"
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
@ -32,7 +35,15 @@ void Init(EmuWindow* emu_window) {
Pica::Init(); Pica::Init();
g_emu_window = emu_window; g_emu_window = emu_window;
#ifdef VKENABLED
if (emu_window->VulkanSupported()){
g_renderer = Common::make_unique<RendererVulkan>();
}else{
g_renderer = Common::make_unique<RendererOpenGL>();
}
#else
g_renderer = Common::make_unique<RendererOpenGL>(); g_renderer = Common::make_unique<RendererOpenGL>();
#endif
g_renderer->SetWindow(g_emu_window); g_renderer->SetWindow(g_emu_window);
g_renderer->Init(); g_renderer->Init();