ppsspp/Common/GPU/Vulkan/VulkanContext.cpp

1931 lines
72 KiB
C++

#define __STDC_LIMIT_MACROS
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <iostream>
#include "Core/Config.h"
#include "Common/System/System.h"
#include "Common/System/Display.h"
#include "Common/Log.h"
#include "Common/GPU/Shader.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanDebug.h"
#include "Common/StringUtils.h"
#ifdef USE_CRT_DBG
#undef new
#endif
#include "ext/vma/vk_mem_alloc.h"
// Change this to 1, 2, and 3 to fake failures in a few places, so that
// we can test our fallback-to-GL code.
#define SIMULATE_VULKAN_FAILURE 0
#include "ext/glslang/SPIRV/GlslangToSpv.h"
#ifdef USE_CRT_DBG
#define new DBG_NEW
#endif
using namespace PPSSPP_VK;
VulkanLogOptions g_LogOptions;
static const char * const validationLayers[] = {
"VK_LAYER_KHRONOS_validation",
/*
// For layers included in the Android NDK.
"VK_LAYER_GOOGLE_threading",
"VK_LAYER_LUNARG_parameter_validation",
"VK_LAYER_LUNARG_core_validation",
"VK_LAYER_LUNARG_image",
"VK_LAYER_LUNARG_object_tracker",
"VK_LAYER_LUNARG_swapchain",
"VK_LAYER_GOOGLE_unique_objects",
*/
};
std::string VulkanVendorString(uint32_t vendorId) {
switch (vendorId) {
case VULKAN_VENDOR_INTEL: return "Intel";
case VULKAN_VENDOR_NVIDIA: return "NVIDIA";
case VULKAN_VENDOR_AMD: return "AMD";
case VULKAN_VENDOR_ARM: return "ARM";
case VULKAN_VENDOR_QUALCOMM: return "Qualcomm";
case VULKAN_VENDOR_IMGTEC: return "Imagination";
case VULKAN_VENDOR_APPLE: return "Apple";
case VULKAN_VENDOR_MESA: return "Mesa";
default:
return StringFromFormat("%08x", vendorId);
}
}
const char *VulkanPresentModeToString(VkPresentModeKHR presentMode) {
switch (presentMode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR: return "IMMEDIATE";
case VK_PRESENT_MODE_MAILBOX_KHR: return "MAILBOX";
case VK_PRESENT_MODE_FIFO_KHR: return "FIFO";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR: return "FIFO_RELAXED";
case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR: return "SHARED_DEMAND_REFRESH_KHR";
case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR: return "SHARED_CONTINUOUS_REFRESH_KHR";
default: return "UNKNOWN";
}
}
const char *VulkanImageLayoutToString(VkImageLayout imageLayout) {
switch (imageLayout) {
case VK_IMAGE_LAYOUT_UNDEFINED: return "UNDEFINED";
case VK_IMAGE_LAYOUT_GENERAL: return "GENERAL";
case VK_IMAGE_LAYOUT_PREINITIALIZED: return "PREINITIALIZED";
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: return "TRANSFER_SRC_OPTIMAL";
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: return "TRANSFER_DST_OPTIMAL";
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: return "SHADER_READ_ONLY_OPTIMAL";
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return "COLOR_ATTACHMENT_OPTIMAL";
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: return "DEPTH_STENCIL_ATTACHMENT_OPTIMAL";
default: return "OTHER";
}
}
VulkanContext::VulkanContext() {
// Do nothing here.
}
VkResult VulkanContext::CreateInstance(const CreateInfo &info) {
if (!vkCreateInstance) {
init_error_ = "Vulkan not loaded - can't create instance";
return VK_ERROR_INITIALIZATION_FAILED;
}
// Check which Vulkan version we should request.
// Our code is fine with any version from 1.0 to 1.2, we don't know about higher versions.
vulkanApiVersion_ = VK_API_VERSION_1_0;
if (vkEnumerateInstanceVersion) {
vkEnumerateInstanceVersion(&vulkanApiVersion_);
vulkanApiVersion_ &= 0xFFFFF000; // Remove patch version.
vulkanApiVersion_ = std::min(VK_API_VERSION_1_3, vulkanApiVersion_);
}
instance_layer_names_.clear();
device_layer_names_.clear();
// We can get the list of layers and extensions without an instance so we can use this information
// to enable the extensions we need that are available.
GetInstanceLayerProperties();
GetInstanceLayerExtensionList(nullptr, instance_extension_properties_);
if (!IsInstanceExtensionAvailable(VK_KHR_SURFACE_EXTENSION_NAME)) {
// Cannot create a Vulkan display without VK_KHR_SURFACE_EXTENSION.
init_error_ = "Vulkan not loaded - no surface extension";
return VK_ERROR_INITIALIZATION_FAILED;
}
flags_ = info.flags;
// List extensions to try to enable.
instance_extensions_enabled_.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
#ifdef _WIN32
instance_extensions_enabled_.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(__ANDROID__)
instance_extensions_enabled_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#else
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (IsInstanceExtensionAvailable(VK_KHR_XLIB_SURFACE_EXTENSION_NAME)) {
instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
}
#endif
//#if defined(VK_USE_PLATFORM_XCB_KHR)
// instance_extensions_enabled_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
//#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (IsInstanceExtensionAvailable(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME)) {
instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
}
#endif
#if defined(VK_USE_PLATFORM_DISPLAY_KHR)
if (IsInstanceExtensionAvailable(VK_KHR_DISPLAY_EXTENSION_NAME)) {
instance_extensions_enabled_.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
}
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (IsInstanceExtensionAvailable(VK_EXT_METAL_SURFACE_EXTENSION_NAME)) {
instance_extensions_enabled_.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
}
#endif
#endif
if ((flags_ & VULKAN_FLAG_VALIDATE) && g_Config.sCustomDriver.empty()) {
if (IsInstanceExtensionAvailable(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
// Enable the validation layers
for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) {
instance_layer_names_.push_back(validationLayers[i]);
device_layer_names_.push_back(validationLayers[i]);
}
instance_extensions_enabled_.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
extensionsLookup_.EXT_debug_utils = true;
INFO_LOG(Log::G3D, "Vulkan debug_utils validation enabled.");
} else {
ERROR_LOG(Log::G3D, "Validation layer extension not available - not enabling Vulkan validation.");
flags_ &= ~VULKAN_FLAG_VALIDATE;
}
}
// Temporary hack for libretro. For some reason, when we try to load the functions from this extension,
// we get null pointers when running libretro. Quite strange.
#if !defined(__LIBRETRO__)
if (EnableInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, VK_API_VERSION_1_1)) {
extensionsLookup_.KHR_get_physical_device_properties2 = true;
}
#endif
if (EnableInstanceExtension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME, 0)) {
extensionsLookup_.EXT_swapchain_colorspace = true;
}
#if PPSSPP_PLATFORM(IOS_APP_STORE)
if (EnableInstanceExtension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, 0)) {
}
#endif
// Validate that all the instance extensions we ask for are actually available.
for (auto ext : instance_extensions_enabled_) {
if (!IsInstanceExtensionAvailable(ext))
WARN_LOG(Log::G3D, "WARNING: Does not seem that instance extension '%s' is available. Trying to proceed anyway.", ext);
}
VkApplicationInfo app_info{ VK_STRUCTURE_TYPE_APPLICATION_INFO };
app_info.pApplicationName = info.app_name;
app_info.applicationVersion = info.app_ver;
app_info.pEngineName = info.app_name;
// Let's increment this when we make major engine/context changes.
app_info.engineVersion = 2;
app_info.apiVersion = vulkanApiVersion_;
VkInstanceCreateInfo inst_info{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
inst_info.flags = 0;
inst_info.pApplicationInfo = &app_info;
inst_info.enabledLayerCount = (uint32_t)instance_layer_names_.size();
inst_info.ppEnabledLayerNames = instance_layer_names_.size() ? instance_layer_names_.data() : nullptr;
inst_info.enabledExtensionCount = (uint32_t)instance_extensions_enabled_.size();
inst_info.ppEnabledExtensionNames = instance_extensions_enabled_.size() ? instance_extensions_enabled_.data() : nullptr;
#if PPSSPP_PLATFORM(IOS_APP_STORE)
inst_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
#if SIMULATE_VULKAN_FAILURE == 2
VkResult res = VK_ERROR_INCOMPATIBLE_DRIVER;
#else
VkResult res = vkCreateInstance(&inst_info, nullptr, &instance_);
#endif
if (res != VK_SUCCESS) {
if (res == VK_ERROR_LAYER_NOT_PRESENT) {
WARN_LOG(Log::G3D, "Validation on but instance layer not available - dropping layers");
// Drop the validation layers and try again.
instance_layer_names_.clear();
device_layer_names_.clear();
inst_info.enabledLayerCount = 0;
inst_info.ppEnabledLayerNames = nullptr;
res = vkCreateInstance(&inst_info, nullptr, &instance_);
if (res != VK_SUCCESS)
ERROR_LOG(Log::G3D, "Failed to create instance even without validation: %d", res);
} else {
ERROR_LOG(Log::G3D, "Failed to create instance : %d", res);
}
}
if (res != VK_SUCCESS) {
init_error_ = "Failed to create Vulkan instance";
return res;
}
VulkanLoadInstanceFunctions(instance_, extensionsLookup_, vulkanApiVersion_);
if (!CheckLayers(instance_layer_properties_, instance_layer_names_)) {
WARN_LOG(Log::G3D, "CheckLayers for instance failed");
// init_error_ = "Failed to validate instance layers";
// return;
}
uint32_t gpu_count = 1;
#if SIMULATE_VULKAN_FAILURE == 3
gpu_count = 0;
#else
res = vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr);
#endif
if (gpu_count <= 0) {
ERROR_LOG(Log::G3D, "Vulkan driver found but no supported GPU is available");
init_error_ = "No Vulkan physical devices found";
vkDestroyInstance(instance_, nullptr);
instance_ = nullptr;
return VK_ERROR_INITIALIZATION_FAILED;
}
_dbg_assert_(gpu_count > 0);
physical_devices_.resize(gpu_count);
physicalDeviceProperties_.resize(gpu_count);
res = vkEnumeratePhysicalDevices(instance_, &gpu_count, physical_devices_.data());
if (res != VK_SUCCESS) {
init_error_ = "Failed to enumerate physical devices";
vkDestroyInstance(instance_, nullptr);
instance_ = nullptr;
return res;
}
if (extensionsLookup_.KHR_get_physical_device_properties2 && vkGetPhysicalDeviceProperties2) {
for (uint32_t i = 0; i < gpu_count; i++) {
VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
VkPhysicalDevicePushDescriptorPropertiesKHR pushProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR};
VkPhysicalDeviceExternalMemoryHostPropertiesEXT extHostMemProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT};
VkPhysicalDeviceDepthStencilResolveProperties depthStencilResolveProps{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES};
props2.pNext = &pushProps;
pushProps.pNext = &extHostMemProps;
extHostMemProps.pNext = &depthStencilResolveProps;
vkGetPhysicalDeviceProperties2(physical_devices_[i], &props2);
// Don't want bad pointers sitting around.
props2.pNext = nullptr;
pushProps.pNext = nullptr;
extHostMemProps.pNext = nullptr;
depthStencilResolveProps.pNext = nullptr;
physicalDeviceProperties_[i].properties = props2.properties;
physicalDeviceProperties_[i].pushDescriptorProperties = pushProps;
physicalDeviceProperties_[i].externalMemoryHostProperties = extHostMemProps;
physicalDeviceProperties_[i].depthStencilResolve = depthStencilResolveProps;
}
} else {
for (uint32_t i = 0; i < gpu_count; i++) {
vkGetPhysicalDeviceProperties(physical_devices_[i], &physicalDeviceProperties_[i].properties);
}
}
if (extensionsLookup_.EXT_debug_utils) {
_assert_(vkCreateDebugUtilsMessengerEXT != nullptr);
InitDebugUtilsCallback();
}
return VK_SUCCESS;
}
VulkanContext::~VulkanContext() {
_dbg_assert_(instance_ == VK_NULL_HANDLE);
}
void VulkanContext::DestroyInstance() {
if (extensionsLookup_.EXT_debug_utils) {
while (utils_callbacks.size() > 0) {
vkDestroyDebugUtilsMessengerEXT(instance_, utils_callbacks.back(), nullptr);
utils_callbacks.pop_back();
}
}
vkDestroyInstance(instance_, nullptr);
VulkanFree();
instance_ = VK_NULL_HANDLE;
}
void VulkanContext::BeginFrame(VkCommandBuffer firstCommandBuffer) {
FrameData *frame = &frame_[curFrame_];
// Process pending deletes.
frame->deleteList.PerformDeletes(this, allocator_);
// VK_NULL_HANDLE when profiler is disabled.
if (firstCommandBuffer) {
frame->profiler.BeginFrame(this, firstCommandBuffer);
}
}
void VulkanContext::EndFrame() {
frame_[curFrame_].deleteList.Take(globalDeleteList_);
curFrame_++;
if (curFrame_ >= inflightFrames_) {
curFrame_ = 0;
}
}
void VulkanContext::UpdateInflightFrames(int n) {
_dbg_assert_(n >= 1 && n <= MAX_INFLIGHT_FRAMES);
inflightFrames_ = n;
if (curFrame_ >= inflightFrames_) {
curFrame_ = 0;
}
}
void VulkanContext::WaitUntilQueueIdle() {
// Should almost never be used
vkQueueWaitIdle(gfx_queue_);
}
bool VulkanContext::MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) {
// Search memtypes to find first index with those properties
for (uint32_t i = 0; i < 32; i++) {
if ((typeBits & 1) == 1) {
// Type is available, does it match user properties?
if ((memory_properties_.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) {
*typeIndex = i;
return true;
}
}
typeBits >>= 1;
}
// No memory types matched, return failure
return false;
}
void VulkanContext::DestroySwapchain() {
if (swapchain_ != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device_, swapchain_, nullptr);
swapchain_ = VK_NULL_HANDLE;
}
}
void VulkanContext::DestroySurface() {
if (surface_ != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(instance_, surface_, nullptr);
surface_ = VK_NULL_HANDLE;
}
}
VkResult VulkanContext::GetInstanceLayerExtensionList(const char *layerName, std::vector<VkExtensionProperties> &extensions) {
VkResult res;
do {
uint32_t instance_extension_count;
res = vkEnumerateInstanceExtensionProperties(layerName, &instance_extension_count, nullptr);
if (res != VK_SUCCESS)
return res;
if (instance_extension_count == 0)
return VK_SUCCESS;
extensions.resize(instance_extension_count);
res = vkEnumerateInstanceExtensionProperties(layerName, &instance_extension_count, extensions.data());
} while (res == VK_INCOMPLETE);
return res;
}
VkResult VulkanContext::GetInstanceLayerProperties() {
/*
* It's possible, though very rare, that the number of
* instance layers could change. For example, installing something
* could include new layers that the loader would pick up
* between the initial query for the count and the
* request for VkLayerProperties. The loader indicates that
* by returning a VK_INCOMPLETE status and will update the
* the count parameter.
* The count parameter will be updated with the number of
* entries loaded into the data pointer - in case the number
* of layers went down or is smaller than the size given.
*/
uint32_t instance_layer_count;
std::vector<VkLayerProperties> vk_props;
VkResult res;
do {
res = vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr);
if (res != VK_SUCCESS)
return res;
if (!instance_layer_count)
return VK_SUCCESS;
vk_props.resize(instance_layer_count);
res = vkEnumerateInstanceLayerProperties(&instance_layer_count, vk_props.data());
} while (res == VK_INCOMPLETE);
// Now gather the extension list for each instance layer.
for (uint32_t i = 0; i < instance_layer_count; i++) {
LayerProperties layer_props;
layer_props.properties = vk_props[i];
res = GetInstanceLayerExtensionList(layer_props.properties.layerName, layer_props.extensions);
if (res != VK_SUCCESS)
return res;
instance_layer_properties_.push_back(layer_props);
}
return res;
}
// Pass layerName == nullptr to get the extension list for the device.
VkResult VulkanContext::GetDeviceLayerExtensionList(const char *layerName, std::vector<VkExtensionProperties> &extensions) {
VkResult res;
do {
uint32_t device_extension_count;
res = vkEnumerateDeviceExtensionProperties(physical_devices_[physical_device_], layerName, &device_extension_count, nullptr);
if (res != VK_SUCCESS)
return res;
if (!device_extension_count)
return VK_SUCCESS;
extensions.resize(device_extension_count);
res = vkEnumerateDeviceExtensionProperties(physical_devices_[physical_device_], layerName, &device_extension_count, extensions.data());
} while (res == VK_INCOMPLETE);
return res;
}
VkResult VulkanContext::GetDeviceLayerProperties() {
/*
* It's possible, though very rare, that the number of
* instance layers could change. For example, installing something
* could include new layers that the loader would pick up
* between the initial query for the count and the
* request for VkLayerProperties. The loader indicates that
* by returning a VK_INCOMPLETE status and will update the
* the count parameter.
* The count parameter will be updated with the number of
* entries loaded into the data pointer - in case the number
* of layers went down or is smaller than the size given.
*/
uint32_t device_layer_count;
std::vector<VkLayerProperties> vk_props;
VkResult res;
do {
res = vkEnumerateDeviceLayerProperties(physical_devices_[physical_device_], &device_layer_count, nullptr);
if (res != VK_SUCCESS)
return res;
if (device_layer_count == 0)
return VK_SUCCESS;
vk_props.resize(device_layer_count);
res = vkEnumerateDeviceLayerProperties(physical_devices_[physical_device_], &device_layer_count, vk_props.data());
} while (res == VK_INCOMPLETE);
// Gather the list of extensions for each device layer.
for (uint32_t i = 0; i < device_layer_count; i++) {
LayerProperties layer_props;
layer_props.properties = vk_props[i];
res = GetDeviceLayerExtensionList(layer_props.properties.layerName, layer_props.extensions);
if (res != VK_SUCCESS)
return res;
device_layer_properties_.push_back(layer_props);
}
return res;
}
// Returns true if all layer names specified in check_names can be found in given layer properties.
bool VulkanContext::CheckLayers(const std::vector<LayerProperties> &layer_props, const std::vector<const char *> &layer_names) const {
uint32_t check_count = (uint32_t)layer_names.size();
uint32_t layer_count = (uint32_t)layer_props.size();
for (uint32_t i = 0; i < check_count; i++) {
bool found = false;
for (uint32_t j = 0; j < layer_count; j++) {
if (!strcmp(layer_names[i], layer_props[j].properties.layerName)) {
found = true;
}
}
if (!found) {
std::cout << "Cannot find layer: " << layer_names[i] << std::endl;
return false;
}
}
return true;
}
int VulkanContext::GetPhysicalDeviceByName(const std::string &name) {
for (size_t i = 0; i < physical_devices_.size(); i++) {
if (physicalDeviceProperties_[i].properties.deviceName == name)
return (int)i;
}
return -1;
}
int VulkanContext::GetBestPhysicalDevice() {
// Rules: Prefer discrete over embedded.
// Prefer nVidia over Intel.
int maxScore = -1;
int best = -1;
for (size_t i = 0; i < physical_devices_.size(); i++) {
int score = 0;
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(physical_devices_[i], &props);
switch (props.deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_CPU:
score += 1;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
score += 2;
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
score += 20;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
score += 10;
break;
default:
break;
}
if (props.vendorID == VULKAN_VENDOR_AMD) {
score += 5;
} else if (props.vendorID == VULKAN_VENDOR_NVIDIA) {
score += 5;
}
if (score > maxScore) {
best = (int)i;
maxScore = score;
}
}
return best;
}
bool VulkanContext::EnableDeviceExtension(const char *extension, uint32_t coreVersion) {
if (coreVersion != 0 && vulkanApiVersion_ >= coreVersion) {
return true;
}
for (auto &iter : device_extension_properties_) {
if (!strcmp(iter.extensionName, extension)) {
device_extensions_enabled_.push_back(extension);
return true;
}
}
return false;
}
bool VulkanContext::EnableInstanceExtension(const char *extension, uint32_t coreVersion) {
if (coreVersion != 0 && vulkanApiVersion_ >= coreVersion) {
return true;
}
for (auto &iter : instance_extension_properties_) {
if (!strcmp(iter.extensionName, extension)) {
instance_extensions_enabled_.push_back(extension);
return true;
}
}
return false;
}
VkResult VulkanContext::CreateDevice(int physical_device) {
physical_device_ = physical_device;
INFO_LOG(Log::G3D, "Chose physical device %d: %s", physical_device, physicalDeviceProperties_[physical_device].properties.deviceName);
GetDeviceLayerProperties();
if (!CheckLayers(device_layer_properties_, device_layer_names_)) {
WARN_LOG(Log::G3D, "CheckLayers for device %d failed", physical_device);
}
vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, nullptr);
_dbg_assert_(queue_count >= 1);
queueFamilyProperties_.resize(queue_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_devices_[physical_device_], &queue_count, queueFamilyProperties_.data());
_dbg_assert_(queue_count >= 1);
// Detect preferred depth/stencil formats, in this order. All supported devices will support at least one of these.
static const VkFormat depthStencilFormats[] = {
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D32_SFLOAT_S8_UINT,
VK_FORMAT_D16_UNORM_S8_UINT,
};
deviceInfo_.preferredDepthStencilFormat = VK_FORMAT_UNDEFINED;
for (size_t i = 0; i < ARRAY_SIZE(depthStencilFormats); i++) {
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physical_devices_[physical_device_], depthStencilFormats[i], &props);
if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
deviceInfo_.preferredDepthStencilFormat = depthStencilFormats[i];
break;
}
}
_assert_msg_(deviceInfo_.preferredDepthStencilFormat != VK_FORMAT_UNDEFINED, "Could not find a usable depth stencil format.");
VkFormatProperties preferredProps;
vkGetPhysicalDeviceFormatProperties(physical_devices_[physical_device_], deviceInfo_.preferredDepthStencilFormat, &preferredProps);
if ((preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) &&
(preferredProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) {
deviceInfo_.canBlitToPreferredDepthStencilFormat = true;
}
// This is as good a place as any to do this. Though, we don't use this much anymore after we added
// support for VMA.
vkGetPhysicalDeviceMemoryProperties(physical_devices_[physical_device_], &memory_properties_);
INFO_LOG(Log::G3D, "Memory Types (%d):", memory_properties_.memoryTypeCount);
for (int i = 0; i < (int)memory_properties_.memoryTypeCount; i++) {
// Don't bother printing dummy memory types.
if (!memory_properties_.memoryTypes[i].propertyFlags)
continue;
INFO_LOG(Log::G3D, " %d: Heap %d; Flags: %s%s%s%s ", i, memory_properties_.memoryTypes[i].heapIndex,
(memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) ? "DEVICE_LOCAL " : "",
(memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? "HOST_VISIBLE " : "",
(memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) ? "HOST_CACHED " : "",
(memory_properties_.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) ? "HOST_COHERENT " : "");
}
// Optional features
if (extensionsLookup_.KHR_get_physical_device_properties2 && vkGetPhysicalDeviceFeatures2) {
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR };
// Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs.
VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR };
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR };
features2.pNext = &multiViewFeatures;
multiViewFeatures.pNext = &presentWaitFeatures;
presentWaitFeatures.pNext = &presentIdFeatures;
presentIdFeatures.pNext = nullptr;
vkGetPhysicalDeviceFeatures2(physical_devices_[physical_device_], &features2);
deviceFeatures_.available.standard = features2.features;
deviceFeatures_.available.multiview = multiViewFeatures;
deviceFeatures_.available.presentWait = presentWaitFeatures;
deviceFeatures_.available.presentId = presentIdFeatures;
} else {
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
deviceFeatures_.available.multiview = {};
}
GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
device_extensions_enabled_.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
if (!init_error_.empty() || physical_device_ < 0) {
ERROR_LOG(Log::G3D, "Vulkan init failed: %s", init_error_.c_str());
return VK_ERROR_INITIALIZATION_FAILED;
}
VkDeviceQueueCreateInfo queue_info{ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
float queue_priorities[1] = { 1.0f };
queue_info.queueCount = 1;
queue_info.pQueuePriorities = queue_priorities;
bool found = false;
for (int i = 0; i < (int)queue_count; i++) {
if (queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_info.queueFamilyIndex = i;
found = true;
break;
}
}
_dbg_assert_(found);
// TODO: A lot of these are on by default in later Vulkan versions, should check for that, technically.
extensionsLookup_.KHR_maintenance1 = EnableDeviceExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME, VK_API_VERSION_1_1);
extensionsLookup_.KHR_maintenance2 = EnableDeviceExtension(VK_KHR_MAINTENANCE2_EXTENSION_NAME, VK_API_VERSION_1_1);
extensionsLookup_.KHR_maintenance3 = EnableDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME, VK_API_VERSION_1_1);
extensionsLookup_.KHR_multiview = EnableDeviceExtension(VK_KHR_MULTIVIEW_EXTENSION_NAME, VK_API_VERSION_1_1);
if (EnableDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, VK_API_VERSION_1_1)) {
extensionsLookup_.KHR_get_memory_requirements2 = true;
extensionsLookup_.KHR_dedicated_allocation = EnableDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, VK_API_VERSION_1_1);
}
if (EnableDeviceExtension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, VK_API_VERSION_1_2)) {
extensionsLookup_.KHR_create_renderpass2 = true;
extensionsLookup_.KHR_depth_stencil_resolve = EnableDeviceExtension(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, VK_API_VERSION_1_2);
}
extensionsLookup_.EXT_shader_stencil_export = EnableDeviceExtension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, 0);
extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME, 0);
extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME, 0);
#if !PPSSPP_PLATFORM(MAC) && !PPSSPP_PLATFORM(IOS)
extensionsLookup_.GOOGLE_display_timing = EnableDeviceExtension(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, 0);
#endif
if (!extensionsLookup_.GOOGLE_display_timing) {
extensionsLookup_.KHR_present_id = EnableDeviceExtension(VK_KHR_PRESENT_ID_EXTENSION_NAME, 0);
extensionsLookup_.KHR_present_wait = EnableDeviceExtension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME, 0);
}
deviceFeatures_.enabled = {};
// Enable a few safe ones if they are available.
deviceFeatures_.enabled.standard.dualSrcBlend = deviceFeatures_.available.standard.dualSrcBlend;
deviceFeatures_.enabled.standard.logicOp = deviceFeatures_.available.standard.logicOp;
deviceFeatures_.enabled.standard.depthClamp = deviceFeatures_.available.standard.depthClamp;
deviceFeatures_.enabled.standard.depthBounds = deviceFeatures_.available.standard.depthBounds;
deviceFeatures_.enabled.standard.samplerAnisotropy = deviceFeatures_.available.standard.samplerAnisotropy;
deviceFeatures_.enabled.standard.shaderClipDistance = deviceFeatures_.available.standard.shaderClipDistance;
deviceFeatures_.enabled.standard.shaderCullDistance = deviceFeatures_.available.standard.shaderCullDistance;
deviceFeatures_.enabled.standard.geometryShader = deviceFeatures_.available.standard.geometryShader;
deviceFeatures_.enabled.standard.sampleRateShading = deviceFeatures_.available.standard.sampleRateShading;
#ifdef _DEBUG
// For debugging! Although, it might hide problems, so turning it off. Can be useful to rule out classes of issues.
// deviceFeatures_.enabled.standard.robustBufferAccess = deviceFeatures_.available.standard.robustBufferAccess;
#endif
deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
if (extensionsLookup_.KHR_multiview) {
deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview;
}
// Strangely, on Intel, it reports these as available even though the extension isn't in the list.
deviceFeatures_.enabled.presentId = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR };
if (extensionsLookup_.KHR_present_id) {
deviceFeatures_.enabled.presentId.presentId = deviceFeatures_.available.presentId.presentId;
}
deviceFeatures_.enabled.presentWait = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR };
if (extensionsLookup_.KHR_present_wait) {
deviceFeatures_.enabled.presentWait.presentWait = deviceFeatures_.available.presentWait.presentWait;
}
// deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader;
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
device_info.enabledLayerCount = (uint32_t)device_layer_names_.size();
device_info.ppEnabledLayerNames = device_info.enabledLayerCount ? device_layer_names_.data() : nullptr;
device_info.enabledExtensionCount = (uint32_t)device_extensions_enabled_.size();
device_info.ppEnabledExtensionNames = device_info.enabledExtensionCount ? device_extensions_enabled_.data() : nullptr;
if (extensionsLookup_.KHR_get_physical_device_properties2) {
device_info.pNext = &features2;
features2.features = deviceFeatures_.enabled.standard;
features2.pNext = &deviceFeatures_.enabled.multiview;
deviceFeatures_.enabled.multiview.pNext = &deviceFeatures_.enabled.presentWait;
deviceFeatures_.enabled.presentWait.pNext = &deviceFeatures_.enabled.presentId;
deviceFeatures_.enabled.presentId.pNext = nullptr;
} else {
device_info.pEnabledFeatures = &deviceFeatures_.enabled.standard;
}
VkResult res = vkCreateDevice(physical_devices_[physical_device_], &device_info, nullptr, &device_);
if (res != VK_SUCCESS) {
init_error_ = "Unable to create Vulkan device";
ERROR_LOG(Log::G3D, "Unable to create Vulkan device");
} else {
VulkanLoadDeviceFunctions(device_, extensionsLookup_, vulkanApiVersion_);
}
INFO_LOG(Log::G3D, "Vulkan Device created: %s", physicalDeviceProperties_[physical_device_].properties.deviceName);
// Since we successfully created a device (however we got here, might be interesting in debug), we force the choice to be visible in the menu.
VulkanSetAvailable(true);
VmaAllocatorCreateInfo allocatorInfo = {};
allocatorInfo.vulkanApiVersion = vulkanApiVersion_;
allocatorInfo.physicalDevice = physical_devices_[physical_device_];
allocatorInfo.device = device_;
allocatorInfo.instance = instance_;
VkResult result = vmaCreateAllocator(&allocatorInfo, &allocator_);
_assert_(result == VK_SUCCESS);
_assert_(allocator_ != VK_NULL_HANDLE);
// Examine the physical device to figure out super rough performance grade.
// Basically all we want to do is to identify low performance mobile devices
// so we can make decisions on things like texture scaling strategy.
auto &props = physicalDeviceProperties_[physical_device_].properties;
switch (props.vendorID) {
case VULKAN_VENDOR_AMD:
case VULKAN_VENDOR_NVIDIA:
case VULKAN_VENDOR_INTEL:
devicePerfClass_ = PerfClass::FAST;
break;
case VULKAN_VENDOR_ARM:
devicePerfClass_ = PerfClass::SLOW;
{
// Parse the device name as an ultra rough heuristic.
int maliG = 0;
if (sscanf(props.deviceName, "Mali-G%d", &maliG) == 1) {
if (maliG >= 72) {
devicePerfClass_ = PerfClass::FAST;
}
}
}
break;
case VULKAN_VENDOR_QUALCOMM:
devicePerfClass_ = PerfClass::SLOW;
#if PPSSPP_PLATFORM(ANDROID)
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) {
devicePerfClass_ = PerfClass::FAST;
}
#endif
break;
case VULKAN_VENDOR_IMGTEC:
default:
devicePerfClass_ = PerfClass::SLOW;
break;
}
return res;
}
VkResult VulkanContext::InitDebugUtilsCallback() {
// We're intentionally skipping VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT and
// VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT, just too spammy.
int bits = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
VkDebugUtilsMessengerCreateInfoEXT callback1{VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
callback1.messageSeverity = bits;
callback1.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
callback1.pfnUserCallback = &VulkanDebugUtilsCallback;
callback1.pUserData = (void *)&g_LogOptions;
VkDebugUtilsMessengerEXT messenger;
VkResult res = vkCreateDebugUtilsMessengerEXT(instance_, &callback1, nullptr, &messenger);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "Failed to register debug callback with vkCreateDebugUtilsMessengerEXT");
// Do error handling for VK_ERROR_OUT_OF_MEMORY
} else {
INFO_LOG(Log::G3D, "Debug callback registered with vkCreateDebugUtilsMessengerEXT.");
utils_callbacks.push_back(messenger);
}
return res;
}
bool VulkanContext::CreateInstanceAndDevice(const CreateInfo &info) {
VkResult res = CreateInstance(info);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "Failed to create vulkan context: %s", InitError().c_str());
VulkanSetAvailable(false);
return false;
}
int physicalDevice = GetBestPhysicalDevice();
if (physicalDevice < 0) {
ERROR_LOG(Log::G3D, "No usable Vulkan device found.");
DestroyInstance();
return false;
}
INFO_LOG(Log::G3D, "Creating Vulkan device (flags: %08x)", info.flags);
if (CreateDevice(physicalDevice) != VK_SUCCESS) {
INFO_LOG(Log::G3D, "Failed to create vulkan device: %s", InitError().c_str());
DestroyInstance();
return false;
}
return true;
}
void VulkanContext::SetDebugNameImpl(uint64_t handle, VkObjectType type, const char *name) {
VkDebugUtilsObjectNameInfoEXT info{ VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT };
info.pObjectName = name;
info.objectHandle = handle;
info.objectType = type;
vkSetDebugUtilsObjectNameEXT(device_, &info);
}
VkResult VulkanContext::InitSurface(WindowSystem winsys, void *data1, void *data2) {
winsys_ = winsys;
winsysData1_ = data1;
winsysData2_ = data2;
return ReinitSurface();
}
VkResult VulkanContext::ReinitSurface() {
if (surface_ != VK_NULL_HANDLE) {
INFO_LOG(Log::G3D, "Destroying Vulkan surface (%d, %d)", swapChainExtent_.width, swapChainExtent_.height);
vkDestroySurfaceKHR(instance_, surface_, nullptr);
surface_ = VK_NULL_HANDLE;
}
INFO_LOG(Log::G3D, "Creating Vulkan surface for window (data1=%p data2=%p)", winsysData1_, winsysData2_);
VkResult retval = VK_SUCCESS;
switch (winsys_) {
#ifdef _WIN32
case WINDOWSYSTEM_WIN32:
{
VkWin32SurfaceCreateInfoKHR win32{ VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR };
win32.flags = 0;
win32.hwnd = (HWND)winsysData2_;
win32.hinstance = (HINSTANCE)winsysData1_;
retval = vkCreateWin32SurfaceKHR(instance_, &win32, nullptr, &surface_);
break;
}
#endif
#if defined(__ANDROID__)
case WINDOWSYSTEM_ANDROID:
{
ANativeWindow *wnd = (ANativeWindow *)winsysData1_;
VkAndroidSurfaceCreateInfoKHR android{ VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR };
android.flags = 0;
android.window = wnd;
retval = vkCreateAndroidSurfaceKHR(instance_, &android, nullptr, &surface_);
break;
}
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
case WINDOWSYSTEM_METAL_EXT:
{
VkMetalSurfaceCreateInfoEXT metal{ VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT };
metal.flags = 0;
metal.pLayer = winsysData1_;
metal.pNext = winsysData2_;
retval = vkCreateMetalSurfaceEXT(instance_, &metal, nullptr, &surface_);
break;
}
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
case WINDOWSYSTEM_XLIB:
{
VkXlibSurfaceCreateInfoKHR xlib{ VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR };
xlib.flags = 0;
xlib.dpy = (Display *)winsysData1_;
xlib.window = (Window)winsysData2_;
retval = vkCreateXlibSurfaceKHR(instance_, &xlib, nullptr, &surface_);
break;
}
#endif
#if defined(VK_USE_PLATFORM_XCB_KHR)
case WINDOWSYSTEM_XCB:
{
VkXCBSurfaceCreateInfoKHR xcb{ VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR };
xcb.flags = 0;
xcb.connection = (Connection *)winsysData1_;
xcb.window = (Window)(uintptr_t)winsysData2_;
retval = vkCreateXcbSurfaceKHR(instance_, &xcb, nullptr, &surface_);
break;
}
#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
case WINDOWSYSTEM_WAYLAND:
{
VkWaylandSurfaceCreateInfoKHR wayland{ VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR };
wayland.flags = 0;
wayland.display = (wl_display *)winsysData1_;
wayland.surface = (wl_surface *)winsysData2_;
retval = vkCreateWaylandSurfaceKHR(instance_, &wayland, nullptr, &surface_);
break;
}
#endif
#if defined(VK_USE_PLATFORM_DISPLAY_KHR)
case WINDOWSYSTEM_DISPLAY:
{
VkDisplaySurfaceCreateInfoKHR display{ VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR };
#if !defined(__LIBRETRO__)
/*
And when not to use libretro need VkDisplaySurfaceCreateInfoKHR this extension,
then you need to use dlopen to read vulkan loader in VulkanLoader.cpp.
huangzihan China
*/
if(!vkGetPhysicalDeviceDisplayPropertiesKHR ||
!vkGetPhysicalDeviceDisplayPlanePropertiesKHR ||
!vkGetDisplayModePropertiesKHR ||
!vkGetDisplayPlaneSupportedDisplaysKHR ||
!vkGetDisplayPlaneCapabilitiesKHR ) {
_assert_msg_(false, "DISPLAY Vulkan cannot find any vulkan function symbols.");
return VK_ERROR_INITIALIZATION_FAILED;
}
//The following code is for reference:
// https://github.com/vanfanel/ppsspp
// When using the VK_KHR_display extension and not using LIBRETRO, a complete
// VkDisplaySurfaceCreateInfoKHR is needed.
uint32_t display_count;
uint32_t plane_count;
VkDisplayPropertiesKHR *display_props = NULL;
VkDisplayPlanePropertiesKHR *plane_props = NULL;
VkDisplayModePropertiesKHR* mode_props = NULL;
VkExtent2D image_size;
// This is the chosen physical_device, it has been chosen elsewhere.
VkPhysicalDevice phys_device = physical_devices_[physical_device_];
VkDisplayModeKHR display_mode = VK_NULL_HANDLE;
VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
uint32_t plane = UINT32_MAX;
// For now, use the first available (connected) display.
int display_index = 0;
VkResult result;
bool ret = false;
bool mode_found = false;
int i, j;
// 1 physical device can have N displays connected.
// Vulkan only counts the connected displays.
// Get a list of displays on the physical device.
display_count = 0;
vkGetPhysicalDeviceDisplayPropertiesKHR(phys_device, &display_count, NULL);
if (display_count == 0) {
_assert_msg_(false, "DISPLAY Vulkan couldn't find any displays.");
return VK_ERROR_INITIALIZATION_FAILED;
}
display_props = new VkDisplayPropertiesKHR[display_count];
vkGetPhysicalDeviceDisplayPropertiesKHR(phys_device, &display_count, display_props);
// Get a list of display planes on the physical device.
plane_count = 0;
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(phys_device, &plane_count, NULL);
if (plane_count == 0) {
_assert_msg_(false, "DISPLAY Vulkan couldn't find any planes on the physical device");
return VK_ERROR_INITIALIZATION_FAILED;
}
plane_props = new VkDisplayPlanePropertiesKHR[plane_count];
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(phys_device, &plane_count, plane_props);
// Get the Vulkan display we are going to use.
VkDisplayKHR myDisplay = display_props[display_index].display;
// Get the list of display modes of the display
uint32_t mode_count = 0;
vkGetDisplayModePropertiesKHR(phys_device, myDisplay, &mode_count, NULL);
if (mode_count == 0) {
_assert_msg_(false, "DISPLAY Vulkan couldn't find any video modes on the display");
return VK_ERROR_INITIALIZATION_FAILED;
}
mode_props = new VkDisplayModePropertiesKHR[mode_count];
vkGetDisplayModePropertiesKHR(phys_device, myDisplay, &mode_count, mode_props);
// See if there's an appropiate mode available on the display
display_mode = VK_NULL_HANDLE;
for (i = 0; i < mode_count; ++i)
{
const VkDisplayModePropertiesKHR* mode = &mode_props[i];
if (mode->parameters.visibleRegion.width == g_display.pixel_xres &&
mode->parameters.visibleRegion.height == g_display.pixel_yres)
{
display_mode = mode->displayMode;
mode_found = true;
break;
}
}
// Free the mode list now.
delete [] mode_props;
// If there are no useable modes found on the display, error out
if (display_mode == VK_NULL_HANDLE)
{
_assert_msg_(false, "DISPLAY Vulkan couldn't find any video modes on the display");
return VK_ERROR_INITIALIZATION_FAILED;
}
/* Iterate on the list of planes of the physical device
to find a plane that matches these criteria:
-It must be compatible with the chosen display + mode.
-It isn't currently bound to another display.
-It supports per-pixel alpha, if possible. */
for (i = 0; i < plane_count; i++) {
uint32_t supported_displays_count = 0;
VkDisplayKHR* supported_displays;
VkDisplayPlaneCapabilitiesKHR plane_caps;
/* See if the plane is compatible with the current display. */
vkGetDisplayPlaneSupportedDisplaysKHR(phys_device, i, &supported_displays_count, NULL);
if (supported_displays_count == 0) {
/* This plane doesn't support any displays. Continue to the next plane. */
continue;
}
/* Get the list of displays supported by this plane. */
supported_displays = new VkDisplayKHR[supported_displays_count];
vkGetDisplayPlaneSupportedDisplaysKHR(phys_device, i,
&supported_displays_count, supported_displays);
/* The plane must be bound to the chosen display, or not in use.
If none of these is true, iterate to another plane. */
if ( !( (plane_props[i].currentDisplay == myDisplay) ||
(plane_props[i].currentDisplay == VK_NULL_HANDLE)))
continue;
/* Iterate the list of displays supported by this plane
in order to find out if the chosen display is among them. */
bool plane_supports_display = false;
for (j = 0; j < supported_displays_count; j++) {
if (supported_displays[j] == myDisplay) {
plane_supports_display = true;
break;
}
}
/* Free the list of displays supported by this plane. */
delete [] supported_displays;
/* If the display is not supported by this plane, iterate to the next plane. */
if (!plane_supports_display)
continue;
/* Want a plane that supports the alpha mode we have chosen. */
vkGetDisplayPlaneCapabilitiesKHR(phys_device, display_mode, i, &plane_caps);
if (plane_caps.supportedAlpha & alpha_mode) {
/* Yep, this plane is alright. */
plane = i;
break;
}
}
/* If we couldn't find an appropiate plane, error out. */
if (plane == UINT32_MAX) {
_assert_msg_(false, "DISPLAY Vulkan couldn't find an appropiate plane");
return VK_ERROR_INITIALIZATION_FAILED;
}
// Finally, create the vulkan surface.
image_size.width = g_display.pixel_xres;
image_size.height = g_display.pixel_yres;
display.displayMode = display_mode;
display.imageExtent = image_size;
display.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
display.alphaMode = alpha_mode;
display.globalAlpha = 1.0f;
display.planeIndex = plane;
display.planeStackIndex = plane_props[plane].currentStackIndex;
display.pNext = nullptr;
delete [] display_props;
delete [] plane_props;
#endif
display.flags = 0;
retval = vkCreateDisplayPlaneSurfaceKHR(instance_, &display, nullptr, &surface_);
break;
}
#endif
default:
_assert_msg_(false, "Vulkan support for chosen window system not implemented");
return VK_ERROR_INITIALIZATION_FAILED;
}
if (retval != VK_SUCCESS) {
return retval;
}
if (!ChooseQueue()) {
return VK_ERROR_INITIALIZATION_FAILED;
}
for (int i = 0; i < ARRAY_SIZE(frame_); i++) {
frame_[i].profiler.Init(this);
}
return VK_SUCCESS;
}
bool VulkanContext::ChooseQueue() {
// Iterate over each queue to learn whether it supports presenting:
VkBool32 *supportsPresent = new VkBool32[queue_count];
for (uint32_t i = 0; i < queue_count; i++) {
vkGetPhysicalDeviceSurfaceSupportKHR(physical_devices_[physical_device_], i, surface_, &supportsPresent[i]);
}
// Search for a graphics queue and a present queue in the array of queue
// families, try to find one that supports both
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
uint32_t presentQueueNodeIndex = UINT32_MAX;
for (uint32_t i = 0; i < queue_count; i++) {
if ((queueFamilyProperties_[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
if (graphicsQueueNodeIndex == UINT32_MAX) {
graphicsQueueNodeIndex = i;
}
if (supportsPresent[i] == VK_TRUE) {
graphicsQueueNodeIndex = i;
presentQueueNodeIndex = i;
break;
}
}
}
if (presentQueueNodeIndex == UINT32_MAX) {
// If didn't find a queue that supports both graphics and present, then
// find a separate present queue. NOTE: We don't actually currently support this arrangement!
for (uint32_t i = 0; i < queue_count; ++i) {
if (supportsPresent[i] == VK_TRUE) {
presentQueueNodeIndex = i;
break;
}
}
}
delete[] supportsPresent;
// Generate error if could not find both a graphics and a present queue
if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) {
ERROR_LOG(Log::G3D, "Could not find a graphics and a present queue");
return false;
}
graphics_queue_family_index_ = graphicsQueueNodeIndex;
// Get the list of VkFormats that are supported:
uint32_t formatCount = 0;
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, nullptr);
_assert_msg_(res == VK_SUCCESS, "Failed to get formats for device %d: %d", physical_device_, (int)res);
if (res != VK_SUCCESS) {
return false;
}
surfFormats_.resize(formatCount);
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_devices_[physical_device_], surface_, &formatCount, surfFormats_.data());
_dbg_assert_(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
return false;
}
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
// the surface has no preferred format. Otherwise, at least one
// supported format will be returned.
if (formatCount == 0 || (formatCount == 1 && surfFormats_[0].format == VK_FORMAT_UNDEFINED)) {
INFO_LOG(Log::G3D, "swapchain_format: Falling back to B8G8R8A8_UNORM");
swapchainFormat_ = VK_FORMAT_B8G8R8A8_UNORM;
} else {
swapchainFormat_ = VK_FORMAT_UNDEFINED;
for (uint32_t i = 0; i < formatCount; ++i) {
if (surfFormats_[i].colorSpace != VK_COLORSPACE_SRGB_NONLINEAR_KHR) {
continue;
}
if (surfFormats_[i].format == VK_FORMAT_B8G8R8A8_UNORM || surfFormats_[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
swapchainFormat_ = surfFormats_[i].format;
break;
}
}
if (swapchainFormat_ == VK_FORMAT_UNDEFINED) {
// Okay, take the first one then.
swapchainFormat_ = surfFormats_[0].format;
}
INFO_LOG(Log::G3D, "swapchain_format: %d (/%d)", swapchainFormat_, formatCount);
}
vkGetDeviceQueue(device_, graphics_queue_family_index_, 0, &gfx_queue_);
return true;
}
int clamp(int x, int a, int b) {
if (x < a)
return a;
if (x > b)
return b;
return x;
}
static std::string surface_transforms_to_string(VkSurfaceTransformFlagsKHR transformFlags) {
std::string str;
if (transformFlags & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) str += "IDENTITY ";
if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR) str += "ROTATE_90 ";
if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) str += "ROTATE_180 ";
if (transformFlags & VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) str += "ROTATE_270 ";
if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR) str += "HMIRROR ";
if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR) str += "HMIRROR_90 ";
if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR) str += "HMIRROR_180 ";
if (transformFlags & VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR) str += "HMIRROR_270 ";
if (transformFlags & VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR) str += "INHERIT ";
return str;
}
bool VulkanContext::InitSwapchain() {
_assert_(physical_device_ >= 0 && physical_device_ < physical_devices_.size());
if (!surface_) {
ERROR_LOG(Log::G3D, "VK: No surface, can't create swapchain");
return false;
}
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_);
if (res == VK_ERROR_SURFACE_LOST_KHR) {
// Not much to do.
ERROR_LOG(Log::G3D, "VK: Surface lost in InitSwapchain");
return false;
}
_dbg_assert_(res == VK_SUCCESS);
uint32_t presentModeCount;
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, nullptr);
_dbg_assert_(res == VK_SUCCESS);
VkPresentModeKHR *presentModes = new VkPresentModeKHR[presentModeCount];
_dbg_assert_(presentModes);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_devices_[physical_device_], surface_, &presentModeCount, presentModes);
_dbg_assert_(res == VK_SUCCESS);
VkExtent2D currentExtent { surfCapabilities_.currentExtent };
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSurfaceCapabilitiesKHR.html
// currentExtent is the current width and height of the surface, or the special value (0xFFFFFFFF, 0xFFFFFFFF) indicating that the surface size will be determined by the extent of a swapchain targeting the surface.
if (currentExtent.width == 0xFFFFFFFFu || currentExtent.height == 0xFFFFFFFFu
#if PPSSPP_PLATFORM(IOS)
|| currentExtent.width == 0 || currentExtent.height == 0
#endif
) {
_dbg_assert_((bool)cbGetDrawSize_)
if (cbGetDrawSize_) {
currentExtent = cbGetDrawSize_();
}
}
swapChainExtent_.width = clamp(currentExtent.width, surfCapabilities_.minImageExtent.width, surfCapabilities_.maxImageExtent.width);
swapChainExtent_.height = clamp(currentExtent.height, surfCapabilities_.minImageExtent.height, surfCapabilities_.maxImageExtent.height);
INFO_LOG(Log::G3D, "surfCapabilities_.current: %dx%d min: %dx%d max: %dx%d computed: %dx%d",
currentExtent.width, currentExtent.height,
surfCapabilities_.minImageExtent.width, surfCapabilities_.minImageExtent.height,
surfCapabilities_.maxImageExtent.width, surfCapabilities_.maxImageExtent.height,
swapChainExtent_.width, swapChainExtent_.height);
availablePresentModes_.clear();
// TODO: Find a better way to specify the prioritized present mode while being able
// to fall back in a sensible way.
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR;
std::string modes = "";
for (size_t i = 0; i < presentModeCount; i++) {
modes += VulkanPresentModeToString(presentModes[i]);
if (i != presentModeCount - 1) {
modes += ", ";
}
availablePresentModes_.push_back(presentModes[i]);
}
INFO_LOG(Log::G3D, "Supported present modes: %s", modes.c_str());
for (size_t i = 0; i < presentModeCount; i++) {
bool match = false;
match = match || ((flags_ & VULKAN_FLAG_PRESENT_MAILBOX) && presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR);
match = match || ((flags_ & VULKAN_FLAG_PRESENT_IMMEDIATE) && presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR);
match = match || ((flags_ & VULKAN_FLAG_PRESENT_FIFO_RELAXED) && presentModes[i] == VK_PRESENT_MODE_FIFO_RELAXED_KHR);
match = match || ((flags_ & VULKAN_FLAG_PRESENT_FIFO) && presentModes[i] == VK_PRESENT_MODE_FIFO_KHR);
// Default to the first present mode from the list.
if (match || swapchainPresentMode == VK_PRESENT_MODE_MAX_ENUM_KHR) {
swapchainPresentMode = presentModes[i];
}
if (match) {
break;
}
}
delete[] presentModes;
// Determine the number of VkImage's to use in the swap chain (we desire to
// own only 1 image at a time, besides the images being displayed and
// queued for display):
uint32_t desiredNumberOfSwapChainImages = surfCapabilities_.minImageCount + 1;
if ((surfCapabilities_.maxImageCount > 0) &&
(desiredNumberOfSwapChainImages > surfCapabilities_.maxImageCount))
{
// Application must settle for fewer images than desired:
desiredNumberOfSwapChainImages = surfCapabilities_.maxImageCount;
}
INFO_LOG(Log::G3D, "Chosen present mode: %d (%s). numSwapChainImages: %d/%d",
swapchainPresentMode, VulkanPresentModeToString(swapchainPresentMode),
desiredNumberOfSwapChainImages, surfCapabilities_.maxImageCount);
// We mostly follow the practices from
// https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/surface_rotation/surface_rotation_tutorial.html
//
VkSurfaceTransformFlagBitsKHR preTransform;
std::string supportedTransforms = surface_transforms_to_string(surfCapabilities_.supportedTransforms);
std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform);
g_display.rotation = DisplayRotation::ROTATE_0;
g_display.rot_matrix.setIdentity();
uint32_t allowedRotations = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
// Hack: Don't allow 270 degrees pretransform (inverse landscape), it creates bizarre issues on some devices (see #15773).
allowedRotations &= ~VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
if (surfCapabilities_.currentTransform & (VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR)) {
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else if (surfCapabilities_.currentTransform & allowedRotations) {
// Normal, sensible rotations. Let's handle it.
preTransform = surfCapabilities_.currentTransform;
g_display.rot_matrix.setIdentity();
switch (surfCapabilities_.currentTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
g_display.rotation = DisplayRotation::ROTATE_90;
g_display.rot_matrix.setRotationZ90();
std::swap(swapChainExtent_.width, swapChainExtent_.height);
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
g_display.rotation = DisplayRotation::ROTATE_180;
g_display.rot_matrix.setRotationZ180();
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
g_display.rotation = DisplayRotation::ROTATE_270;
g_display.rot_matrix.setRotationZ270();
std::swap(swapChainExtent_.width, swapChainExtent_.height);
break;
default:
_dbg_assert_(false);
}
} else {
// Let the OS rotate the image (potentially slower on many Android devices)
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
std::string preTransformStr = surface_transforms_to_string(preTransform);
INFO_LOG(Log::G3D, "Transform supported: %s current: %s chosen: %s", supportedTransforms.c_str(), currentTransform.c_str(), preTransformStr.c_str());
if (physicalDeviceProperties_[physical_device_].properties.vendorID == VULKAN_VENDOR_IMGTEC) {
u32 driverVersion = physicalDeviceProperties_[physical_device_].properties.driverVersion;
// Cutoff the hack at driver version 1.386.1368 (0x00582558, see issue #15773).
if (driverVersion < 0x00582558) {
INFO_LOG(Log::G3D, "Applying PowerVR hack (rounding off the width!) driverVersion=%08x", driverVersion);
// Swap chain width hack to avoid issue #11743 (PowerVR driver bug).
// To keep the size consistent even with pretransform, do this after the swap. Should be fine.
// This is fixed in newer PowerVR drivers but I don't know the cutoff.
swapChainExtent_.width &= ~31;
// TODO: Also modify display_xres/display_yres appropriately for scissors to match.
// This will get a bit messy. Ideally we should remove that logic from app-android.cpp
// and move it here, but the OpenGL code still needs it.
} else {
INFO_LOG(Log::G3D, "PowerVR driver version new enough (%08x), not applying swapchain width hack", driverVersion);
}
}
VkSwapchainCreateInfoKHR swap_chain_info{ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
swap_chain_info.surface = surface_;
swap_chain_info.minImageCount = desiredNumberOfSwapChainImages;
swap_chain_info.imageFormat = swapchainFormat_;
swap_chain_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
swap_chain_info.imageExtent.width = swapChainExtent_.width;
swap_chain_info.imageExtent.height = swapChainExtent_.height;
swap_chain_info.preTransform = preTransform;
swap_chain_info.imageArrayLayers = 1;
swap_chain_info.presentMode = swapchainPresentMode;
swap_chain_info.oldSwapchain = VK_NULL_HANDLE;
swap_chain_info.clipped = true;
swap_chain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
presentMode_ = swapchainPresentMode;
// Don't ask for TRANSFER_DST for the swapchain image, we don't use that.
// if (surfCapabilities_.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)
// swap_chain_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
#ifndef ANDROID
// We don't support screenshots on Android
// Add more usage flags if they're supported.
if (surfCapabilities_.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)
swap_chain_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
#endif
swap_chain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swap_chain_info.queueFamilyIndexCount = 0;
swap_chain_info.pQueueFamilyIndices = NULL;
// OPAQUE is not supported everywhere.
if (surfCapabilities_.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
swap_chain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
} else {
// This should be supported anywhere, and is the only thing supported on the SHIELD TV, for example.
swap_chain_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
res = vkCreateSwapchainKHR(device_, &swap_chain_info, NULL, &swapchain_);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "vkCreateSwapchainKHR failed!");
return false;
}
INFO_LOG(Log::G3D, "Created swapchain: %dx%d", swap_chain_info.imageExtent.width, swap_chain_info.imageExtent.height);
return true;
}
void VulkanContext::SetCbGetDrawSize(std::function<VkExtent2D()> cb) {
cbGetDrawSize_ = cb;
}
VkFence VulkanContext::CreateFence(bool presignalled) {
VkFence fence;
VkFenceCreateInfo fenceInfo{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
fenceInfo.flags = presignalled ? VK_FENCE_CREATE_SIGNALED_BIT : 0;
vkCreateFence(device_, &fenceInfo, NULL, &fence);
return fence;
}
void VulkanContext::PerformPendingDeletes() {
for (int i = 0; i < ARRAY_SIZE(frame_); i++) {
frame_[i].deleteList.PerformDeletes(this, allocator_);
}
Delete().PerformDeletes(this, allocator_);
}
void VulkanContext::DestroyDevice() {
if (swapchain_) {
ERROR_LOG(Log::G3D, "DestroyDevice: Swapchain should have been destroyed.");
}
if (surface_) {
ERROR_LOG(Log::G3D, "DestroyDevice: Surface should have been destroyed.");
}
for (int i = 0; i < ARRAY_SIZE(frame_); i++) {
frame_[i].profiler.Shutdown();
}
INFO_LOG(Log::G3D, "VulkanContext::DestroyDevice (performing deletes)");
PerformPendingDeletes();
vmaDestroyAllocator(allocator_);
allocator_ = VK_NULL_HANDLE;
vkDestroyDevice(device_, nullptr);
device_ = nullptr;
}
bool VulkanContext::CreateShaderModule(const std::vector<uint32_t> &spirv, VkShaderModule *shaderModule, const char *tag) {
VkShaderModuleCreateInfo sm{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
sm.pCode = spirv.data();
sm.codeSize = spirv.size() * sizeof(uint32_t);
sm.flags = 0;
VkResult result = vkCreateShaderModule(device_, &sm, nullptr, shaderModule);
if (tag) {
SetDebugName(*shaderModule, VK_OBJECT_TYPE_SHADER_MODULE, tag);
}
if (result != VK_SUCCESS) {
return false;
} else {
return true;
}
}
EShLanguage FindLanguage(const VkShaderStageFlagBits shader_type) {
switch (shader_type) {
case VK_SHADER_STAGE_VERTEX_BIT:
return EShLangVertex;
case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
return EShLangTessControl;
case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
return EShLangTessEvaluation;
case VK_SHADER_STAGE_GEOMETRY_BIT:
return EShLangGeometry;
case VK_SHADER_STAGE_FRAGMENT_BIT:
return EShLangFragment;
case VK_SHADER_STAGE_COMPUTE_BIT:
return EShLangCompute;
default:
return EShLangVertex;
}
}
// Compile a given string containing GLSL into SPV for use by VK
// Return value of false means an error was encountered.
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant,
std::vector<unsigned int> &spirv, std::string *errorMessage) {
glslang::TProgram program;
const char *shaderStrings[1];
TBuiltInResource Resources{};
InitShaderResources(Resources);
int defaultVersion = 0;
EShMessages messages;
EProfile profile;
switch (variant) {
case GLSLVariant::VULKAN:
// Enable SPIR-V and Vulkan rules when parsing GLSL
messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);
defaultVersion = 450;
profile = ECoreProfile;
break;
case GLSLVariant::GL140:
messages = (EShMessages)(EShMsgDefault);
defaultVersion = 140;
profile = ECompatibilityProfile;
break;
case GLSLVariant::GLES300:
messages = (EShMessages)(EShMsgDefault);
defaultVersion = 300;
profile = EEsProfile;
break;
default:
return false;
}
EShLanguage stage = FindLanguage(shader_type);
glslang::TShader shader(stage);
shaderStrings[0] = sourceCode;
shader.setStrings(shaderStrings, 1);
if (!shader.parse(&Resources, defaultVersion, profile, false, true, messages)) {
puts(shader.getInfoLog());
puts(shader.getInfoDebugLog());
if (errorMessage) {
*errorMessage = shader.getInfoLog();
(*errorMessage) += shader.getInfoDebugLog();
}
return false; // something didn't work
}
// TODO: Propagate warnings into errorMessages even if we succeeded here.
// Note that program does not take ownership of &shader, so this is fine.
program.addShader(&shader);
if (!program.link(messages)) {
puts(shader.getInfoLog());
puts(shader.getInfoDebugLog());
if (errorMessage) {
*errorMessage = shader.getInfoLog();
(*errorMessage) += shader.getInfoDebugLog();
}
return false;
}
// Can't fail, parsing worked, "linking" worked.
glslang::SpvOptions options;
options.disableOptimizer = false;
options.optimizeSize = false;
options.generateDebugInfo = false;
glslang::GlslangToSpv(*program.getIntermediate(stage), spirv, &options);
return true;
}
void init_glslang() {
glslang::InitializeProcess();
}
void finalize_glslang() {
glslang::FinalizeProcess();
}
void VulkanDeleteList::Take(VulkanDeleteList &del) {
_dbg_assert_(cmdPools_.empty());
_dbg_assert_(descPools_.empty());
_dbg_assert_(modules_.empty());
_dbg_assert_(buffers_.empty());
_dbg_assert_(bufferViews_.empty());
_dbg_assert_(buffersWithAllocs_.empty());
_dbg_assert_(imageViews_.empty());
_dbg_assert_(imagesWithAllocs_.empty());
_dbg_assert_(deviceMemory_.empty());
_dbg_assert_(samplers_.empty());
_dbg_assert_(pipelines_.empty());
_dbg_assert_(pipelineCaches_.empty());
_dbg_assert_(renderPasses_.empty());
_dbg_assert_(framebuffers_.empty());
_dbg_assert_(pipelineLayouts_.empty());
_dbg_assert_(descSetLayouts_.empty());
_dbg_assert_(callbacks_.empty());
cmdPools_ = std::move(del.cmdPools_);
descPools_ = std::move(del.descPools_);
modules_ = std::move(del.modules_);
buffers_ = std::move(del.buffers_);
buffersWithAllocs_ = std::move(del.buffersWithAllocs_);
bufferViews_ = std::move(del.bufferViews_);
imageViews_ = std::move(del.imageViews_);
imagesWithAllocs_ = std::move(del.imagesWithAllocs_);
deviceMemory_ = std::move(del.deviceMemory_);
samplers_ = std::move(del.samplers_);
pipelines_ = std::move(del.pipelines_);
pipelineCaches_ = std::move(del.pipelineCaches_);
renderPasses_ = std::move(del.renderPasses_);
framebuffers_ = std::move(del.framebuffers_);
pipelineLayouts_ = std::move(del.pipelineLayouts_);
descSetLayouts_ = std::move(del.descSetLayouts_);
callbacks_ = std::move(del.callbacks_);
del.cmdPools_.clear();
del.descPools_.clear();
del.modules_.clear();
del.buffers_.clear();
del.buffersWithAllocs_.clear();
del.imageViews_.clear();
del.imagesWithAllocs_.clear();
del.deviceMemory_.clear();
del.samplers_.clear();
del.pipelines_.clear();
del.pipelineCaches_.clear();
del.renderPasses_.clear();
del.framebuffers_.clear();
del.pipelineLayouts_.clear();
del.descSetLayouts_.clear();
del.callbacks_.clear();
}
void VulkanDeleteList::PerformDeletes(VulkanContext *vulkan, VmaAllocator allocator) {
int deleteCount = 0;
for (auto &callback : callbacks_) {
callback.func(vulkan, callback.userdata);
deleteCount++;
}
callbacks_.clear();
VkDevice device = vulkan->GetDevice();
for (auto &cmdPool : cmdPools_) {
vkDestroyCommandPool(device, cmdPool, nullptr);
deleteCount++;
}
cmdPools_.clear();
for (auto &descPool : descPools_) {
vkDestroyDescriptorPool(device, descPool, nullptr);
deleteCount++;
}
descPools_.clear();
for (auto &module : modules_) {
vkDestroyShaderModule(device, module, nullptr);
deleteCount++;
}
modules_.clear();
for (auto &buf : buffers_) {
vkDestroyBuffer(device, buf, nullptr);
deleteCount++;
}
buffers_.clear();
for (auto &buf : buffersWithAllocs_) {
vmaDestroyBuffer(allocator, buf.buffer, buf.alloc);
deleteCount++;
}
buffersWithAllocs_.clear();
for (auto &bufView : bufferViews_) {
vkDestroyBufferView(device, bufView, nullptr);
deleteCount++;
}
bufferViews_.clear();
for (auto &imageWithAlloc : imagesWithAllocs_) {
vmaDestroyImage(allocator, imageWithAlloc.image, imageWithAlloc.alloc);
deleteCount++;
}
imagesWithAllocs_.clear();
for (auto &imageView : imageViews_) {
vkDestroyImageView(device, imageView, nullptr);
deleteCount++;
}
imageViews_.clear();
for (auto &mem : deviceMemory_) {
vkFreeMemory(device, mem, nullptr);
deleteCount++;
}
deviceMemory_.clear();
for (auto &sampler : samplers_) {
vkDestroySampler(device, sampler, nullptr);
deleteCount++;
}
samplers_.clear();
for (auto &pipeline : pipelines_) {
vkDestroyPipeline(device, pipeline, nullptr);
deleteCount++;
}
pipelines_.clear();
for (auto &pcache : pipelineCaches_) {
vkDestroyPipelineCache(device, pcache, nullptr);
deleteCount++;
}
pipelineCaches_.clear();
for (auto &renderPass : renderPasses_) {
vkDestroyRenderPass(device, renderPass, nullptr);
deleteCount++;
}
renderPasses_.clear();
for (auto &framebuffer : framebuffers_) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
deleteCount++;
}
framebuffers_.clear();
for (auto &pipeLayout : pipelineLayouts_) {
vkDestroyPipelineLayout(device, pipeLayout, nullptr);
deleteCount++;
}
pipelineLayouts_.clear();
for (auto &descSetLayout : descSetLayouts_) {
vkDestroyDescriptorSetLayout(device, descSetLayout, nullptr);
deleteCount++;
}
descSetLayouts_.clear();
for (auto &queryPool : queryPools_) {
vkDestroyQueryPool(device, queryPool, nullptr);
deleteCount++;
}
queryPools_.clear();
deleteCount_ = deleteCount;
}
void VulkanContext::GetImageMemoryRequirements(VkImage image, VkMemoryRequirements *mem_reqs, bool *dedicatedAllocation) {
if (Extensions().KHR_dedicated_allocation) {
VkImageMemoryRequirementsInfo2KHR memReqInfo2{VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR};
memReqInfo2.image = image;
VkMemoryRequirements2KHR memReq2 = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR};
VkMemoryDedicatedRequirementsKHR memDedicatedReq{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR};
memReq2.pNext = &memDedicatedReq;
vkGetImageMemoryRequirements2(GetDevice(), &memReqInfo2, &memReq2);
*mem_reqs = memReq2.memoryRequirements;
*dedicatedAllocation =
(memDedicatedReq.requiresDedicatedAllocation != VK_FALSE) ||
(memDedicatedReq.prefersDedicatedAllocation != VK_FALSE);
} else {
vkGetImageMemoryRequirements(GetDevice(), image, mem_reqs);
*dedicatedAllocation = false;
}
}
bool IsHashMaliDriverVersion(const VkPhysicalDeviceProperties &props) {
// ARM used to put a hash in place of the driver version.
// Now they only use major versions. We'll just make a bad heuristic.
uint32_t major = VK_VERSION_MAJOR(props.driverVersion);
uint32_t branch = VK_VERSION_PATCH(props.driverVersion);
if (branch > 0)
return true;
if (branch > 100 || major > 100)
return true;
return false;
}
// From Sascha's code
std::string FormatDriverVersion(const VkPhysicalDeviceProperties &props) {
if (props.vendorID == VULKAN_VENDOR_NVIDIA) {
// For whatever reason, NVIDIA has their own scheme.
// 10 bits = major version (up to r1023)
// 8 bits = minor version (up to 255)
// 8 bits = secondary branch version/build version (up to 255)
// 6 bits = tertiary branch/build version (up to 63)
uint32_t major = (props.driverVersion >> 22) & 0x3ff;
uint32_t minor = (props.driverVersion >> 14) & 0x0ff;
uint32_t secondaryBranch = (props.driverVersion >> 6) & 0x0ff;
uint32_t tertiaryBranch = (props.driverVersion) & 0x003f;
return StringFromFormat("%d.%d.%d.%d", major, minor, secondaryBranch, tertiaryBranch);
} else if (props.vendorID == VULKAN_VENDOR_ARM) {
// ARM used to just put a hash here. No point in splitting it up.
if (IsHashMaliDriverVersion(props)) {
return StringFromFormat("(hash) %08x", props.driverVersion);
}
}
// Qualcomm has an inscrutable versioning scheme. Let's just display it as normal.
// Standard scheme, use the standard macros.
uint32_t major = VK_VERSION_MAJOR(props.driverVersion);
uint32_t minor = VK_VERSION_MINOR(props.driverVersion);
uint32_t branch = VK_VERSION_PATCH(props.driverVersion);
return StringFromFormat("%d.%d.%d (%08x)", major, minor, branch, props.driverVersion);
}
// Mainly just the formats seen on gpuinfo.org for swapchains, as this function is only used for listing
// those in the UI. Also depth buffers that we used in one place.
// Might add more in the future if we find more uses for this.
const char *VulkanFormatToString(VkFormat format) {
switch (format) {
case VK_FORMAT_A1R5G5B5_UNORM_PACK16: return "A1R5G5B5_UNORM_PACK16";
case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return "A2B10G10R10_UNORM_PACK32";
case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return "A2R10G10B10_UNORM_PACK32";
case VK_FORMAT_A8B8G8R8_SNORM_PACK32: return "A8B8G8R8_SNORM_PACK32";
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: return "A8B8G8R8_SRGB_PACK32";
case VK_FORMAT_A8B8G8R8_UNORM_PACK32: return "A8B8G8R8_UNORM_PACK32";
case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return "B10G11R11_UFLOAT_PACK32";
case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return "B4G4R4A4_UNORM_PACK16";
case VK_FORMAT_B5G5R5A1_UNORM_PACK16: return "B5G5R5A1_UNORM_PACK16";
case VK_FORMAT_B5G6R5_UNORM_PACK16: return "B5G6R5_UNORM_PACK16";
case VK_FORMAT_B8G8R8A8_SNORM: return "B8G8R8A8_SNORM";
case VK_FORMAT_B8G8R8A8_SRGB: return "B8G8R8A8_SRGB";
case VK_FORMAT_B8G8R8A8_UNORM: return "B8G8R8A8_UNORM";
case VK_FORMAT_R16G16B16A16_SFLOAT: return "R16G16B16A16_SFLOAT";
case VK_FORMAT_R16G16B16A16_SNORM: return "R16G16B16A16_SNORM";
case VK_FORMAT_R16G16B16A16_UNORM: return "R16G16B16A16_UNORM";
case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return "R4G4B4A4_UNORM_PACK16";
case VK_FORMAT_R5G5B5A1_UNORM_PACK16: return "R5G5B5A1_UNORM_PACK16";
case VK_FORMAT_R5G6B5_UNORM_PACK16: return "R5G6B5_UNORM_PACK16";
case VK_FORMAT_R8G8B8A8_SNORM: return "R8G8B8A8_SNORM";
case VK_FORMAT_R8G8B8A8_SRGB: return "R8G8B8A8_SRGB";
case VK_FORMAT_R8G8B8A8_UNORM: return "R8G8B8A8_UNORM";
case VK_FORMAT_D24_UNORM_S8_UINT: return "D24S8";
case VK_FORMAT_D16_UNORM: return "D16";
case VK_FORMAT_D16_UNORM_S8_UINT: return "D16S8";
case VK_FORMAT_D32_SFLOAT: return "D32f";
case VK_FORMAT_D32_SFLOAT_S8_UINT: return "D32fS8";
case VK_FORMAT_S8_UINT: return "S8";
case VK_FORMAT_UNDEFINED: return "UNDEFINED (BAD!)";
default: return "(format not added to string list)";
}
}
// I miss Rust where this is automatic :(
const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace) {
switch (colorSpace) {
case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: return "SRGB_NONLINEAR";
case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT: return "DISPLAY_P3_NONLINEAR";
case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: return "EXTENDED_SRGB_LINEAR";
case VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT: return "DISPLAY_P3_LINEAR";
case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT: return "DCI_P3_NONLINEAR";
case VK_COLOR_SPACE_BT709_LINEAR_EXT: return "BT709_LINEAR";
case VK_COLOR_SPACE_BT709_NONLINEAR_EXT: return "BT709_NONLINEAR";
case VK_COLOR_SPACE_BT2020_LINEAR_EXT: return "BT2020_LINEAR";
case VK_COLOR_SPACE_HDR10_ST2084_EXT: return "HDR10_ST2084";
case VK_COLOR_SPACE_DOLBYVISION_EXT: return "DOLBYVISION";
case VK_COLOR_SPACE_HDR10_HLG_EXT: return "HDR10_HLG";
case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT: return "ADOBERGB_LINEAR";
case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT: return "ADOBERGB_NONLINEAR";
case VK_COLOR_SPACE_PASS_THROUGH_EXT: return "PASS_THROUGH";
case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT: return "EXTENDED_SRGB_NONLINEAR";
case VK_COLOR_SPACE_DISPLAY_NATIVE_AMD: return "DISPLAY_NATIVE_AMD";
default: return "(unknown)";
}
}