mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Statically link a vulkan loader on Linux. Use to to init vulkan on X11. Unfortunately, it's broken.
This commit is contained in:
1 parent
6d6ad3ed14
commit
7f758d52dc
7 files changed
+381
-297
No files matched your search
+20
-1
@@ -76,6 +76,25 @@ else()
|
||||
add_definitions(-DNO_VULKAN)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
# Use FindVulkan module added with CMAKE 3.7
|
||||
if (NOT CMAKE_VERSION VERSION_LESS 3.7.0)
|
||||
message(STATUS "Using module to find Vulkan")
|
||||
find_package(Vulkan)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT WIN32 AND VULKAN)
|
||||
IF (NOT Vulkan_FOUND)
|
||||
find_library(Vulkan_LIBRARY NAMES vulkan HINTS "$ENV{VULKAN_SDK}/lib" "${CMAKE_SOURCE_DIR}/ext/vulkan" REQUIRED)
|
||||
IF (Vulkan_LIBRARY)
|
||||
set(Vulkan_FOUND ON)
|
||||
MESSAGE("Using bundled Vulkan library version")
|
||||
add_definitions(-DVULKAN_STATIC)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED HEADLESS)
|
||||
set(HEADLESS OFF)
|
||||
endif()
|
||||
@@ -1653,7 +1672,7 @@ set(CoreExtraLibs ${CoreExtraLibs} armips)
|
||||
set(GlslangLibs glslang OGLCompiler OSDependent SPIRV SPVRemapper spirv-cross-glsl)
|
||||
|
||||
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash ${GlslangLibs}
|
||||
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
|
||||
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS} ${Vulkan_LIBRARY})
|
||||
|
||||
if(FFmpeg_FOUND)
|
||||
target_compile_definitions(${CoreLibName} PRIVATE USE_FFMPEG=1)
|
||||
|
||||
@@ -113,6 +113,8 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
|
||||
instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
instance_extensions_enabled_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_MIR_KHR)
|
||||
instance_extensions_enabled_.push_back(VK_KHR_MIR_SURFACE_EXTENSION_NAME);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
instance_extensions.enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
|
||||
#else
|
||||
@@ -432,6 +434,7 @@ int VulkanContext::GetBestPhysicalDevice() {
|
||||
|
||||
void VulkanContext::ChooseDevice(int physical_device) {
|
||||
physical_device_ = physical_device;
|
||||
ILOG("Chose physical device %d: %p", physical_device, physical_devices_[physical_device]);
|
||||
|
||||
GetDeviceLayerProperties();
|
||||
if (!CheckLayers(device_layer_properties_, device_layer_names_)) {
|
||||
@@ -553,6 +556,7 @@ VkResult VulkanContext::CreateDevice() {
|
||||
} else {
|
||||
VulkanLoadDeviceFunctions(device_);
|
||||
}
|
||||
ILOG("Device created.\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -571,7 +575,7 @@ VkResult VulkanContext::InitDebugMsgCallback(PFN_vkDebugReportCallbackEXT dbgFun
|
||||
cb.flags = bits;
|
||||
cb.pfnCallback = dbgFunc;
|
||||
cb.pUserData = userdata;
|
||||
VkResult res = vkCreateDebugReportCallbackEXT(instance_, &cb, nullptr, &msg_callback);
|
||||
VkResult res = dyn_vkCreateDebugReportCallbackEXT(instance_, &cb, nullptr, &msg_callback);
|
||||
switch (res) {
|
||||
case VK_SUCCESS:
|
||||
msg_callbacks.push_back(msg_callback);
|
||||
@@ -586,7 +590,7 @@ VkResult VulkanContext::InitDebugMsgCallback(PFN_vkDebugReportCallbackEXT dbgFun
|
||||
|
||||
void VulkanContext::DestroyDebugMsgCallback() {
|
||||
while (msg_callbacks.size() > 0) {
|
||||
vkDestroyDebugReportCallbackEXT(instance_, msg_callbacks.back(), nullptr);
|
||||
dyn_vkDestroyDebugReportCallbackEXT(instance_, msg_callbacks.back(), nullptr);
|
||||
msg_callbacks.pop_back();
|
||||
}
|
||||
}
|
||||
@@ -650,7 +654,6 @@ void VulkanContext::ReinitSurface(int width, int height) {
|
||||
assert(res == VK_SUCCESS);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
#if defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
case WINDOWSYSTEM_XCB:
|
||||
@@ -718,16 +721,17 @@ bool VulkanContext::InitQueue() {
|
||||
graphics_queue_family_index_ = graphicsQueueNodeIndex;
|
||||
|
||||
// Get the list of VkFormats that are supported:
|
||||
uint32_t formatCount;
|
||||
uint32_t formatCount = 0;
|
||||
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, nullptr);
|
||||
assert(res == VK_SUCCESS);
|
||||
if (res != VK_SUCCESS)
|
||||
_assert_msg_(G3D, res == VK_SUCCESS, "Failed to get formats for device %p: %d surface: %p", physical_devices_[physical_device_], (int)res, surface_);
|
||||
if (res != VK_SUCCESS) {
|
||||
return false;
|
||||
VkSurfaceFormatKHR *surfFormats = new VkSurfaceFormatKHR[formatCount];
|
||||
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, surfFormats);
|
||||
}
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surfFormats(formatCount);
|
||||
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, surfFormats.data());
|
||||
assert(res == VK_SUCCESS);
|
||||
if (res != VK_SUCCESS) {
|
||||
delete[] surfFormats;
|
||||
return false;
|
||||
}
|
||||
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
|
||||
@@ -754,7 +758,6 @@ bool VulkanContext::InitQueue() {
|
||||
}
|
||||
ILOG("swapchain_format: %d (/%d)", swapchainFormat_, formatCount);
|
||||
}
|
||||
delete[] surfFormats;
|
||||
|
||||
vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &gfx_queue_);
|
||||
ILOG("gfx_queue_: %p", gfx_queue_);
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
#include "Common/Vulkan/VulkanLoader.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/basictypes.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#ifndef VULKAN_STATIC
|
||||
|
||||
PFN_vkCreateInstance vkCreateInstance;
|
||||
PFN_vkDestroyInstance vkDestroyInstance;
|
||||
@@ -181,10 +183,12 @@ PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
|
||||
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||
PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||
#endif
|
||||
|
||||
// And the DEBUG_REPORT extension. We dynamically load this.
|
||||
PFN_vkCreateDebugReportCallbackEXT dyn_vkCreateDebugReportCallbackEXT;
|
||||
PFN_vkDestroyDebugReportCallbackEXT dyn_vkDestroyDebugReportCallbackEXT;
|
||||
|
||||
// And the DEBUG_REPORT extension.
|
||||
PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
|
||||
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
|
||||
|
||||
#ifdef _WIN32
|
||||
static HINSTANCE vulkanLibrary;
|
||||
@@ -197,15 +201,26 @@ static void *vulkanLibrary;
|
||||
#define LOAD_DEVICE_FUNC(instance, x) x = (PFN_ ## x)vkGetDeviceProcAddr(instance, #x); if (!x) {ILOG("Missing (device): %s", #x);}
|
||||
#define LOAD_GLOBAL_FUNC(x) x = (PFN_ ## x)dlsym(vulkanLibrary, #x); if (!x) {ILOG("Missing (global): %s", #x);}
|
||||
|
||||
static const char *so_names[] = {
|
||||
"libvulkan.so",
|
||||
"libvulkan.so.1",
|
||||
};
|
||||
|
||||
bool VulkanMayBeAvailable() {
|
||||
#ifdef VULKAN_STATIC
|
||||
return true;
|
||||
#else
|
||||
if (vulkanLibrary)
|
||||
return true;
|
||||
bool available = false;
|
||||
#ifndef _WIN32
|
||||
void *lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
available = lib != nullptr;
|
||||
if (lib) {
|
||||
dlclose(lib);
|
||||
for (int i = 0; i < ARRAY_SIZE(so_names); i++) {
|
||||
void *lib = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL);
|
||||
if (lib) {
|
||||
available = true;
|
||||
dlclose(lib);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// LoadLibrary etc
|
||||
@@ -216,12 +231,18 @@ bool VulkanMayBeAvailable() {
|
||||
}
|
||||
#endif
|
||||
return available;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VulkanLoad() {
|
||||
#ifndef VULKAN_STATIC
|
||||
if (!vulkanLibrary) {
|
||||
#ifndef _WIN32
|
||||
vulkanLibrary = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
for (int i = 0; i < ARRAY_SIZE(so_names); i++) {
|
||||
vulkanLibrary = dlopen(so_names[i], RTLD_NOW | RTLD_LOCAL);
|
||||
if (vulkanLibrary)
|
||||
break;
|
||||
}
|
||||
#else
|
||||
// LoadLibrary etc
|
||||
vulkanLibrary = LoadLibrary(L"vulkan-1.dll");
|
||||
@@ -239,10 +260,12 @@ bool VulkanLoad() {
|
||||
LOAD_GLOBAL_FUNC(vkEnumerateInstanceLayerProperties);
|
||||
|
||||
WLOG("Vulkan base functions loaded.");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanLoadInstanceFunctions(VkInstance instance) {
|
||||
#ifndef VULKAN_STATIC
|
||||
// OK, let's use the above functions to get the rest.
|
||||
LOAD_INSTANCE_FUNC(instance, vkDestroyInstance);
|
||||
LOAD_INSTANCE_FUNC(instance, vkEnumeratePhysicalDevices);
|
||||
@@ -397,9 +420,11 @@ void VulkanLoadInstanceFunctions(VkInstance instance) {
|
||||
#endif
|
||||
|
||||
LOAD_INSTANCE_FUNC(instance, vkDestroySurfaceKHR);
|
||||
#endif
|
||||
|
||||
dyn_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
|
||||
dyn_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
|
||||
|
||||
LOAD_INSTANCE_FUNC(instance, vkCreateDebugReportCallbackEXT);
|
||||
LOAD_INSTANCE_FUNC(instance, vkDestroyDebugReportCallbackEXT);
|
||||
WLOG("Vulkan instance functions loaded.");
|
||||
}
|
||||
|
||||
@@ -408,15 +433,18 @@ void VulkanLoadInstanceFunctions(VkInstance instance) {
|
||||
// good for multi-device.
|
||||
void VulkanLoadDeviceFunctions(VkDevice device) {
|
||||
WLOG("Vulkan device functions loaded.");
|
||||
#ifndef VULKAN_STATIC
|
||||
// TODO: Move more functions VulkanLoadInstanceFunctions to here.
|
||||
LOAD_DEVICE_FUNC(device, vkCreateSwapchainKHR);
|
||||
LOAD_DEVICE_FUNC(device, vkDestroySwapchainKHR);
|
||||
LOAD_DEVICE_FUNC(device, vkGetSwapchainImagesKHR);
|
||||
LOAD_DEVICE_FUNC(device, vkAcquireNextImageKHR);
|
||||
LOAD_DEVICE_FUNC(device, vkQueuePresentKHR);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanFree() {
|
||||
#ifndef VULKAN_STATIC
|
||||
if (vulkanLibrary) {
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(vulkanLibrary);
|
||||
@@ -425,4 +453,5 @@ void VulkanFree() {
|
||||
#endif
|
||||
vulkanLibrary = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -25,10 +25,13 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#ifndef VULKAN_STATIC
|
||||
#define VK_NO_PROTOTYPES
|
||||
#endif
|
||||
|
||||
#include "ext/vulkan/vulkan.h"
|
||||
|
||||
#ifndef VULKAN_STATIC
|
||||
extern PFN_vkCreateInstance vkCreateInstance;
|
||||
extern PFN_vkDestroyInstance vkDestroyInstance;
|
||||
extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
|
||||
@@ -189,9 +192,13 @@ extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||
extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||
extern PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||
|
||||
// And the DEBUG_REPORT extension.
|
||||
extern PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
|
||||
extern PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
|
||||
#endif
|
||||
|
||||
// And the DEBUG_REPORT extension. Since we load this dynamically even in static
|
||||
// linked mode, we have to rename it :(
|
||||
extern PFN_vkCreateDebugReportCallbackEXT dyn_vkCreateDebugReportCallbackEXT;
|
||||
extern PFN_vkDestroyDebugReportCallbackEXT dyn_vkDestroyDebugReportCallbackEXT;
|
||||
|
||||
|
||||
// Way to do a quick check before even attempting to load.
|
||||
bool VulkanMayBeAvailable();
|
||||
@@ -200,4 +207,3 @@ bool VulkanLoad();
|
||||
void VulkanLoadInstanceFunctions(VkInstance instance);
|
||||
void VulkanLoadDeviceFunctions(VkDevice device);
|
||||
void VulkanFree();
|
||||
|
||||
@@ -169,27 +169,27 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer, bool *use
|
||||
}
|
||||
|
||||
if (useHWTransform)
|
||||
WRITE(p, "layout (location = %d) in vec3 position;\n", PspAttributeLocation::POSITION);
|
||||
WRITE(p, "layout (location = %d) in vec3 position;\n", (int)PspAttributeLocation::POSITION);
|
||||
else
|
||||
// we pass the fog coord in w
|
||||
WRITE(p, "layout (location = %d) in vec4 position;\n", PspAttributeLocation::POSITION);
|
||||
WRITE(p, "layout (location = %d) in vec4 position;\n", (int)PspAttributeLocation::POSITION);
|
||||
|
||||
if (useHWTransform && hasNormal)
|
||||
WRITE(p, "layout (location = %d) in vec3 normal;\n", PspAttributeLocation::NORMAL);
|
||||
WRITE(p, "layout (location = %d) in vec3 normal;\n", (int)PspAttributeLocation::NORMAL);
|
||||
|
||||
bool texcoordInVec3 = false;
|
||||
if (doTexture && hasTexcoord) {
|
||||
if (!useHWTransform && doTextureTransform && !throughmode) {
|
||||
WRITE(p, "layout (location = %d) in vec3 texcoord;\n", PspAttributeLocation::TEXCOORD);
|
||||
WRITE(p, "layout (location = %d) in vec3 texcoord;\n", (int)PspAttributeLocation::TEXCOORD);
|
||||
texcoordInVec3 = true;
|
||||
}
|
||||
else
|
||||
WRITE(p, "layout (location = %d) in vec2 texcoord;\n", PspAttributeLocation::TEXCOORD);
|
||||
WRITE(p, "layout (location = %d) in vec2 texcoord;\n", (int)PspAttributeLocation::TEXCOORD);
|
||||
}
|
||||
if (hasColor) {
|
||||
WRITE(p, "layout (location = %d) in vec4 color0;\n", PspAttributeLocation::COLOR0);
|
||||
WRITE(p, "layout (location = %d) in vec4 color0;\n", (int)PspAttributeLocation::COLOR0);
|
||||
if (lmode && !useHWTransform) // only software transform supplies color1 as vertex data
|
||||
WRITE(p, "layout (location = %d) in vec3 color1;\n", PspAttributeLocation::COLOR1);
|
||||
WRITE(p, "layout (location = %d) in vec3 color1;\n", (int)PspAttributeLocation::COLOR1);
|
||||
}
|
||||
|
||||
WRITE(p, "layout (location = 1) %sout vec4 v_color0;\n", shading);
|
||||
|
||||
+291
-264
@@ -42,156 +42,15 @@ SDLJoystick *joystick = NULL;
|
||||
#include "SDL_syswm.h"
|
||||
#endif
|
||||
|
||||
#if defined(USING_EGL)
|
||||
#include "EGL/egl.h"
|
||||
#endif
|
||||
|
||||
#include "Core/System.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Common/GraphicsContext.h"
|
||||
|
||||
class SDLGLGraphicsContext : public DummyGraphicsContext {
|
||||
public:
|
||||
SDLGLGraphicsContext(SDL_Window *window) : window_(window) {
|
||||
CheckGLExtensions();
|
||||
draw_ = Draw::T3DCreateGLContext();
|
||||
bool success = draw_->CreatePresets();
|
||||
assert(success);
|
||||
}
|
||||
~SDLGLGraphicsContext() {
|
||||
delete draw_;
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
#ifdef USING_EGL
|
||||
eglSwapBuffers(g_eglDisplay, g_eglSurface);
|
||||
#else
|
||||
SDL_GL_SwapWindow(window_);
|
||||
#endif
|
||||
}
|
||||
|
||||
Draw::DrawContext *GetDrawContext() override {
|
||||
return draw_;
|
||||
}
|
||||
|
||||
private:
|
||||
Draw::DrawContext *draw_ = nullptr;
|
||||
SDL_Window *window_;
|
||||
};
|
||||
|
||||
static VulkanLogOptions g_LogOptions;
|
||||
|
||||
class SDLVulkanGraphicsContext : public GraphicsContext {
|
||||
public:
|
||||
SDLVulkanGraphicsContext() {}
|
||||
~SDLVulkanGraphicsContext() {
|
||||
delete draw_;
|
||||
}
|
||||
|
||||
bool Init(SDL_Window *sdl_window, std::string *error_message) {
|
||||
SDL_SysWMinfo sys_info;
|
||||
SDL_VERSION(&sys_info.version); //Set SDL version
|
||||
SDL_GetWindowWMInfo(sdl_window, &sys_info);
|
||||
|
||||
Display *display = sys_info.info.x11.display;
|
||||
Window window = sys_info.info.x11.window;
|
||||
|
||||
init_glslang();
|
||||
|
||||
g_LogOptions.breakOnError = true;
|
||||
g_LogOptions.breakOnWarning = true;
|
||||
g_LogOptions.msgBoxOnError = false;
|
||||
|
||||
Version gitVer(PPSSPP_GIT_VERSION);
|
||||
|
||||
vulkan_ = new VulkanContext();
|
||||
if (vulkan_->InitError().size()) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
int vulkanFlags = VULKAN_FLAG_PRESENT_MAILBOX;
|
||||
// vulkanFlags |= VULKAN_FLAG_VALIDATE;
|
||||
VulkanContext::CreateInfo info{};
|
||||
info.app_name = "PPSSPP";
|
||||
info.app_ver = gitVer.ToInteger();
|
||||
info.flags = VULKAN_FLAG_PRESENT_MAILBOX;
|
||||
if (VK_SUCCESS != vulkan_->CreateInstance(info)) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
vulkan_->ChooseDevice(vulkan_->GetBestPhysicalDevice());
|
||||
if (vulkan_->CreateDevice() != VK_SUCCESS) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
vulkan_->InitSurface(WINDOWSYSTEM_XLIB, (void *)display, (void *)(intptr_t)window, pixel_xres, pixel_yres);
|
||||
|
||||
if (!vulkan_->InitObjects()) {
|
||||
*error_message = vulkan_->InitError();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
draw_ = Draw::T3DCreateVulkanContext(vulkan_, false);
|
||||
bool success = draw_->CreatePresets();
|
||||
assert(success);
|
||||
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
if (draw_)
|
||||
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
delete draw_;
|
||||
draw_ = nullptr;
|
||||
vulkan_->WaitUntilQueueIdle();
|
||||
vulkan_->DestroyObjects();
|
||||
vulkan_->DestroyDevice();
|
||||
vulkan_->DestroyDebugMsgCallback();
|
||||
vulkan_->DestroyInstance();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
finalize_glslang();
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
// We don't do it this way.
|
||||
}
|
||||
|
||||
void Resize() override {
|
||||
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
vulkan_->DestroyObjects();
|
||||
// TODO: Take from real window dimensions
|
||||
int width = 1024;
|
||||
int height = 768;
|
||||
vulkan_->ReinitSurface(width, height);
|
||||
vulkan_->InitObjects();
|
||||
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
}
|
||||
|
||||
void SwapInterval(int interval) override {
|
||||
}
|
||||
void *GetAPIContext() override {
|
||||
return vulkan_;
|
||||
}
|
||||
|
||||
Draw::DrawContext *GetDrawContext() override {
|
||||
return draw_;
|
||||
}
|
||||
private:
|
||||
Draw::DrawContext *draw_ = nullptr;
|
||||
VulkanContext *vulkan_ = nullptr;
|
||||
};
|
||||
|
||||
GlobalUIState lastUIState = UISTATE_MENU;
|
||||
GlobalUIState GetUIState();
|
||||
|
||||
@@ -202,8 +61,9 @@ static int g_QuitRequested = 0;
|
||||
static int g_DesktopWidth = 0;
|
||||
static int g_DesktopHeight = 0;
|
||||
|
||||
static VulkanLogOptions g_LogOptions;
|
||||
|
||||
#if defined(USING_EGL)
|
||||
#include "EGL/egl.h"
|
||||
|
||||
static EGLDisplay g_eglDisplay = NULL;
|
||||
static EGLContext g_eglContext = NULL;
|
||||
@@ -334,6 +194,264 @@ void EGL_Close() {
|
||||
}
|
||||
#endif
|
||||
|
||||
class SDLGLGraphicsContext : public DummyGraphicsContext {
|
||||
public:
|
||||
SDLGLGraphicsContext() {
|
||||
}
|
||||
~SDLGLGraphicsContext() {
|
||||
delete draw_;
|
||||
}
|
||||
|
||||
bool Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message);
|
||||
|
||||
void Shutdown() override {
|
||||
#ifdef USING_EGL
|
||||
EGL_Close();
|
||||
#else
|
||||
SDL_GL_DeleteContext(glContext);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
#ifdef USING_EGL
|
||||
eglSwapBuffers(g_eglDisplay, g_eglSurface);
|
||||
#else
|
||||
SDL_GL_SwapWindow(window_);
|
||||
#endif
|
||||
}
|
||||
|
||||
Draw::DrawContext *GetDrawContext() override {
|
||||
return draw_;
|
||||
}
|
||||
|
||||
private:
|
||||
Draw::DrawContext *draw_ = nullptr;
|
||||
SDL_Window *window_;
|
||||
SDL_GLContext glContext = nullptr;
|
||||
};
|
||||
|
||||
bool SDLGLGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) {
|
||||
struct GLVersionPair {
|
||||
int major;
|
||||
int minor;
|
||||
};
|
||||
GLVersionPair attemptVersions[] = {
|
||||
#ifdef USING_GLES2
|
||||
{3, 2}, {3, 1}, {3, 0}, {2, 0},
|
||||
#else
|
||||
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
|
||||
{3, 3}, {3, 2}, {3, 1}, {3, 0},
|
||||
#endif
|
||||
};
|
||||
|
||||
SDL_GLContext glContext = nullptr;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) {
|
||||
const auto &ver = attemptVersions[i];
|
||||
// Make sure to request a somewhat modern GL context at least - the
|
||||
// latest supported by MacOS X (really, really sad...)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
|
||||
#ifdef USING_GLES2
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SetGLCoreContext(false);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SetGLCoreContext(true);
|
||||
#endif
|
||||
|
||||
window = SDL_CreateWindow("PPSSPP", x,y, pixel_xres, pixel_yres, mode);
|
||||
if (!window) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
continue;
|
||||
}
|
||||
|
||||
glContext = SDL_GL_CreateContext(window);
|
||||
if (glContext != nullptr) {
|
||||
// Victory, got one.
|
||||
break;
|
||||
}
|
||||
|
||||
// Let's keep trying. To be safe, destroy the window - docs say needed to change profile.
|
||||
// in practice, it doesn't seem to matter, but maybe it differs by platform.
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
if (glContext == nullptr) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SetGLCoreContext(false);
|
||||
|
||||
window = SDL_CreateWindow("PPSSPP", x,y, pixel_xres, pixel_yres, mode);
|
||||
if (window == nullptr) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return 2;
|
||||
}
|
||||
|
||||
glContext = SDL_GL_CreateContext(window);
|
||||
if (glContext == nullptr) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USING_EGL
|
||||
EGL_Init();
|
||||
#endif
|
||||
|
||||
#ifndef USING_GLES2
|
||||
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
|
||||
// glewExperimental allows us to force GLEW to search for the pointers anyway.
|
||||
if (gl_extensions.IsCoreContext) {
|
||||
glewExperimental = true;
|
||||
}
|
||||
if (GLEW_OK != glewInit()) {
|
||||
printf("Failed to initialize glew!\n");
|
||||
return 1;
|
||||
}
|
||||
// Unfortunately, glew will generate an invalid enum error, ignore.
|
||||
if (gl_extensions.IsCoreContext)
|
||||
glGetError();
|
||||
|
||||
if (GLEW_VERSION_2_0) {
|
||||
printf("OpenGL 2.0 or higher.\n");
|
||||
} else {
|
||||
printf("Sorry, this program requires OpenGL 2.0.\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Finally we can do the regular initialization.
|
||||
CheckGLExtensions();
|
||||
draw_ = Draw::T3DCreateGLContext();
|
||||
bool success = draw_->CreatePresets();
|
||||
assert(success);
|
||||
window_ = window;
|
||||
}
|
||||
|
||||
class SDLVulkanGraphicsContext : public GraphicsContext {
|
||||
public:
|
||||
SDLVulkanGraphicsContext() {}
|
||||
~SDLVulkanGraphicsContext() {
|
||||
delete draw_;
|
||||
}
|
||||
|
||||
bool Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message);
|
||||
|
||||
void Shutdown() override;
|
||||
|
||||
void SwapBuffers() override {
|
||||
// We don't do it this way.
|
||||
}
|
||||
|
||||
void Resize() override {
|
||||
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
vulkan_->DestroyObjects();
|
||||
// TODO: Take from real window dimensions
|
||||
int width = 1024;
|
||||
int height = 768;
|
||||
vulkan_->ReinitSurface(width, height);
|
||||
vulkan_->InitObjects();
|
||||
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
}
|
||||
|
||||
void SwapInterval(int interval) override {
|
||||
}
|
||||
void *GetAPIContext() override {
|
||||
return vulkan_;
|
||||
}
|
||||
|
||||
Draw::DrawContext *GetDrawContext() override {
|
||||
return draw_;
|
||||
}
|
||||
private:
|
||||
Draw::DrawContext *draw_ = nullptr;
|
||||
VulkanContext *vulkan_ = nullptr;
|
||||
};
|
||||
|
||||
bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) {
|
||||
mode |= SDL_WINDOW_VULKAN;
|
||||
window = SDL_CreateWindow("Initializing Vulkan...", x, y, pixel_xres, pixel_yres, mode);
|
||||
SDL_SysWMinfo sys_info;
|
||||
SDL_VERSION(&sys_info.version);
|
||||
SDL_GetWindowWMInfo(window, &sys_info);
|
||||
Display *display = sys_info.info.x11.display;
|
||||
Window x11_window = sys_info.info.x11.window;
|
||||
|
||||
ILOG("Display: %p", display);
|
||||
|
||||
init_glslang();
|
||||
|
||||
g_LogOptions.breakOnError = true;
|
||||
g_LogOptions.breakOnWarning = true;
|
||||
g_LogOptions.msgBoxOnError = false;
|
||||
|
||||
Version gitVer(PPSSPP_GIT_VERSION);
|
||||
|
||||
vulkan_ = new VulkanContext();
|
||||
if (vulkan_->InitError().size()) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
int vulkanFlags = VULKAN_FLAG_PRESENT_MAILBOX;
|
||||
// vulkanFlags |= VULKAN_FLAG_VALIDATE;
|
||||
VulkanContext::CreateInfo info{};
|
||||
info.app_name = "PPSSPP";
|
||||
info.app_ver = gitVer.ToInteger();
|
||||
info.flags = vulkanFlags;
|
||||
if (VK_SUCCESS != vulkan_->CreateInstance(info)) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
vulkan_->ChooseDevice(vulkan_->GetBestPhysicalDevice());
|
||||
if (vulkan_->CreateDevice() != VK_SUCCESS) {
|
||||
*error_message = vulkan_->InitError();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
vulkan_->InitSurface(WINDOWSYSTEM_XLIB, (void *)display, (void *)(intptr_t)x11_window, pixel_xres, pixel_yres);
|
||||
|
||||
if (!vulkan_->InitObjects()) {
|
||||
*error_message = vulkan_->InitError();
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
draw_ = Draw::T3DCreateVulkanContext(vulkan_, false);
|
||||
bool success = draw_->CreatePresets();
|
||||
assert(success);
|
||||
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLVulkanGraphicsContext::Shutdown() {
|
||||
if (draw_)
|
||||
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
|
||||
delete draw_;
|
||||
draw_ = nullptr;
|
||||
vulkan_->WaitUntilQueueIdle();
|
||||
vulkan_->DestroyObjects();
|
||||
vulkan_->DestroyDevice();
|
||||
vulkan_->DestroyDebugMsgCallback();
|
||||
vulkan_->DestroyInstance();
|
||||
delete vulkan_;
|
||||
vulkan_ = nullptr;
|
||||
finalize_glslang();
|
||||
}
|
||||
|
||||
int getDisplayNumber(void) {
|
||||
int displayNumber = 0;
|
||||
char * displayNumberStr;
|
||||
@@ -545,6 +663,12 @@ int main(int argc, char *argv[]) {
|
||||
putenv((char*)"SDL_VIDEO_CENTERED=1");
|
||||
SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
|
||||
|
||||
if (VulkanMayBeAvailable()) {
|
||||
printf("Vulkan might be available.\n");
|
||||
} else {
|
||||
printf("Vulkan is not available.\n");
|
||||
}
|
||||
|
||||
int set_xres = -1;
|
||||
int set_yres = -1;
|
||||
bool portrait = false;
|
||||
@@ -593,6 +717,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
bool joystick_enabled = true;
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) < 0) {
|
||||
fprintf(stderr, "Failed to initialize SDL with joystick support. Retrying without.\n");
|
||||
joystick_enabled = false;
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
|
||||
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
|
||||
@@ -600,6 +725,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: How do we get this into the GraphicsContext?
|
||||
#ifdef USING_EGL
|
||||
if (EGL_Open())
|
||||
return 1;
|
||||
@@ -624,12 +750,6 @@ int main(int argc, char *argv[]) {
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
#ifdef USING_GLES2
|
||||
mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN;
|
||||
#else
|
||||
mode |= SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
|
||||
#endif
|
||||
|
||||
// Is resolution is too low to run windowed
|
||||
if (g_DesktopWidth < 480 * 2 && g_DesktopHeight < 272 * 2) {
|
||||
mode |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
@@ -694,109 +814,6 @@ int main(int argc, char *argv[]) {
|
||||
int x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(getDisplayNumber());
|
||||
int y = SDL_WINDOWPOS_UNDEFINED;
|
||||
|
||||
struct GLVersionPair {
|
||||
int major;
|
||||
int minor;
|
||||
};
|
||||
GLVersionPair attemptVersions[] = {
|
||||
#ifdef USING_GLES2
|
||||
{3, 2}, {3, 1}, {3, 0}, {2, 0},
|
||||
#else
|
||||
{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
|
||||
{3, 3}, {3, 2}, {3, 1}, {3, 0},
|
||||
#endif
|
||||
};
|
||||
|
||||
SDL_Window *window = nullptr;
|
||||
SDL_GLContext glContext = nullptr;
|
||||
for (size_t i = 0; i < ARRAY_SIZE(attemptVersions); ++i) {
|
||||
const auto &ver = attemptVersions[i];
|
||||
// Make sure to request a somewhat modern GL context at least - the
|
||||
// latest supported by MacOS X (really, really sad...)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, ver.major);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, ver.minor);
|
||||
#ifdef USING_GLES2
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SetGLCoreContext(false);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SetGLCoreContext(true);
|
||||
#endif
|
||||
|
||||
window = SDL_CreateWindow(app_name_nice.c_str(), x,y, pixel_xres, pixel_yres, mode);
|
||||
if (!window) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
continue;
|
||||
}
|
||||
|
||||
glContext = SDL_GL_CreateContext(window);
|
||||
if (glContext != nullptr) {
|
||||
// Victory, got one.
|
||||
break;
|
||||
}
|
||||
|
||||
// Let's keep trying. To be safe, destroy the window - docs say needed to change profile.
|
||||
// in practice, it doesn't seem to matter, but maybe it differs by platform.
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
if (glContext == nullptr) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SetGLCoreContext(false);
|
||||
|
||||
window = SDL_CreateWindow(app_name_nice.c_str(), x,y, pixel_xres, pixel_yres, mode);
|
||||
if (window == nullptr) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return 2;
|
||||
}
|
||||
|
||||
glContext = SDL_GL_CreateContext(window);
|
||||
if (glContext == nullptr) {
|
||||
NativeShutdown();
|
||||
fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USING_EGL
|
||||
EGL_Init();
|
||||
#endif
|
||||
|
||||
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
|
||||
|
||||
#ifdef MOBILE_DEVICE
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef USING_GLES2
|
||||
// Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
|
||||
// glewExperimental allows us to force GLEW to search for the pointers anyway.
|
||||
if (gl_extensions.IsCoreContext)
|
||||
glewExperimental = true;
|
||||
if (GLEW_OK != glewInit()) {
|
||||
printf("Failed to initialize glew!\n");
|
||||
return 1;
|
||||
}
|
||||
// Unfortunately, glew will generate an invalid enum error, ignore.
|
||||
if (gl_extensions.IsCoreContext)
|
||||
glGetError();
|
||||
|
||||
if (GLEW_VERSION_2_0) {
|
||||
printf("OpenGL 2.0 or higher.\n");
|
||||
} else {
|
||||
printf("Sorry, this program requires OpenGL 2.0.\n");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
pixel_in_dps_x = (float)pixel_xres / dp_xres;
|
||||
pixel_in_dps_y = (float)pixel_yres / dp_yres;
|
||||
g_dpi_scale_x = dp_xres / (float)pixel_xres;
|
||||
@@ -807,21 +824,35 @@ int main(int argc, char *argv[]) {
|
||||
printf("Pixels: %i x %i\n", pixel_xres, pixel_yres);
|
||||
printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres);
|
||||
|
||||
GraphicsContext *graphicsContext;
|
||||
GraphicsContext *graphicsContext = nullptr;
|
||||
SDL_Window *window = nullptr;
|
||||
std::string error_message;
|
||||
if (g_Config.iGPUBackend == GPU_BACKEND_OPENGL) {
|
||||
graphicsContext = new SDLGLGraphicsContext(window);
|
||||
SDLGLGraphicsContext *ctx = new SDLGLGraphicsContext();
|
||||
if (!ctx->Init(window, x, y, mode, &error_message)) {
|
||||
printf("GL init error '%s' - falling back to GL\n", error_message.c_str());
|
||||
}
|
||||
graphicsContext = ctx;
|
||||
} else if (g_Config.iGPUBackend == GPU_BACKEND_VULKAN) {
|
||||
SDLVulkanGraphicsContext *ctx = new SDLVulkanGraphicsContext();
|
||||
std::string error_message;
|
||||
if (!ctx->Init(window, &error_message)) {
|
||||
if (!ctx->Init(window, x, y, mode, &error_message)) {
|
||||
printf("Vulkan init error '%s' - falling back to GL\n", error_message.c_str());
|
||||
g_Config.iGPUBackend = GPU_BACKEND_OPENGL;
|
||||
delete ctx;
|
||||
graphicsContext = new SDLGLGraphicsContext(window);
|
||||
SDLGLGraphicsContext *glctx = new SDLGLGraphicsContext();
|
||||
glctx->Init(window, x, y, mode, &error_message);
|
||||
graphicsContext = glctx;
|
||||
} else {
|
||||
graphicsContext = ctx;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str());
|
||||
|
||||
#ifdef MOBILE_DEVICE
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
#endif
|
||||
|
||||
NativeInitGraphics(graphicsContext);
|
||||
|
||||
NativeResized();
|
||||
@@ -1054,16 +1085,12 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
delete joystick;
|
||||
NativeShutdownGraphics();
|
||||
SDL_GL_DeleteContext(glContext);
|
||||
graphicsContext->Shutdown();
|
||||
NativeShutdown();
|
||||
delete graphicsContext;
|
||||
|
||||
SDL_PauseAudio(1);
|
||||
SDL_CloseAudio();
|
||||
#ifdef USING_EGL
|
||||
EGL_Close();
|
||||
#endif
|
||||
SDL_Quit();
|
||||
#if PPSSPP_PLATFORM(RPI)
|
||||
bcm_host_deinit();
|
||||
|
||||
@@ -191,7 +191,7 @@ void GameSettingsScreen::CreateViews() {
|
||||
}
|
||||
#endif
|
||||
bool vulkanAvailable = false;
|
||||
#if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID)
|
||||
#ifndef IOS
|
||||
vulkanAvailable = VulkanMayBeAvailable();
|
||||
#endif
|
||||
if (!vulkanAvailable) {
|
||||
|
||||
Reference in new issue
Block a user