Vulkan PhysicalDevice + Device Initialization + Report Accurate Version

Vulkan Device initialization is handled now, it supports required extensions but support for optional extensions/features/properties will come in later when we require those. In addition, we now correctly report the version of Skyline to Vulkan which can be accessed from debugging tools. 

There's also a minor change regarding the search pattern for `SkylineLibraries` which now only searches in headers of libraries and it also explicitly excludes the redundant `vulkan.hpp` from the `Vulkan-Headers` repository.
This commit is contained in:
PixelyIon 2021-03-28 20:05:13 +05:30 committed by ◱ Mark
parent 8ceed74371
commit 2143b43068
8 changed files with 105 additions and 20 deletions

View File

@ -1,3 +1,3 @@
<component name="DependencyValidationManager">
<scope name="SkylineLibraries" pattern="file[skyline.app]:libraries//*" />
<scope name="SkylineLibraries" pattern="(file[skyline.app]:libraries/fmt/include//*||file[skyline.app]:libraries/frozen/include//*||file[skyline.app]:libraries/lz4/lib//*||file[skyline.app]:libraries/oboe/include//*||file[skyline.app]:libraries/perfetto/include//*||file:libraries/pugixml/src/pugixml.hpp||file[skyline.app]:libraries/tzcode/include/*||file[skyline.app]:libraries/vkhpp/vulkan//*||file[skyline.app]:libraries/vkhpp/Vulkan-Headers/include//*)&amp;&amp;!file:libraries/vkhpp/Vulkan-Headers/include/vulkan/vulkan.hpp" />
</component>

View File

@ -29,6 +29,7 @@ set(LZ4_BUILD_LEGACY_LZ4C OFF CACHE BOOL "Build lz4c progam with legacy argument
add_subdirectory("libraries/lz4/build/cmake")
include_directories("libraries/lz4/lib")
add_compile_definitions(VK_USE_PLATFORM_ANDROID_KHR) # We want all the Android-specific structures to be defined
add_compile_definitions(VULKAN_HPP_NO_SPACESHIP_OPERATOR) # libcxx doesn't implement operator<=> for std::array which breaks this
add_compile_definitions(VULKAN_HPP_NO_STRUCT_CONSTRUCTORS) # We want to use designated initializers in Vulkan-Hpp
add_compile_definitions(VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1) # We use the dynamic loader rather than the static one to avoid an additional level of indirection

View File

@ -15,7 +15,7 @@ android {
targetSdkVersion 30
versionCode 3
versionName "0.3"
versionName "0.0.3"
ndk {
abiFilters "arm64-v8a"

View File

@ -1,22 +1,22 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <jvm.h>
#include "gpu.h"
namespace skyline::gpu {
vk::raii::Instance GPU::CreateInstance(const DeviceState &state, const vk::raii::Context &context) {
vk::ApplicationInfo applicationInfo{
.pApplicationName = "Skyline",
.applicationVersion = VK_MAKE_VERSION('S', 'K', 'Y'), // "SKY" magic as the application version
.pEngineName = "GPU",
.engineVersion = VK_MAKE_VERSION('G', 'P', 'U'), // "GPU" magic as engine version
.applicationVersion = state.jvm->GetVersionCode(), // Get the application version from JNI
.pEngineName = "FTX1", // "Fast Tegra X1"
.apiVersion = VK_API_VERSION_1_1,
};
#ifdef NDEBUG
std::array<const char *, 0> requiredLayers{};
constexpr std::array<const char *, 0> requiredLayers{};
#else
std::array<const char *, 1> requiredLayers{
constexpr std::array<const char *, 1> requiredLayers{
"VK_LAYER_KHRONOS_validation"
};
#endif
@ -25,7 +25,7 @@ namespace skyline::gpu {
if (state.logger->configLevel >= Logger::LogLevel::Debug) {
std::string layers;
for (const auto &instanceLayer : instanceLayers)
layers += fmt::format("\n* {} (Sv{}.{}.{}, Iv{}.{}.{}) - {}", instanceLayer.layerName, VK_VERSION_MAJOR(instanceLayer.specVersion), VK_VERSION_MINOR(instanceLayer.specVersion), VK_VERSION_PATCH(instanceLayer.specVersion), VK_VERSION_MAJOR(instanceLayer.implementationVersion), VK_VERSION_MINOR(instanceLayer.implementationVersion), VK_VERSION_PATCH(instanceLayer.implementationVersion), instanceLayer.description);
layers += util::Format("\n* {} (Sv{}.{}.{}, Iv{}.{}.{}) - {}", instanceLayer.layerName, VK_VERSION_MAJOR(instanceLayer.specVersion), VK_VERSION_MINOR(instanceLayer.specVersion), VK_VERSION_PATCH(instanceLayer.specVersion), VK_VERSION_MAJOR(instanceLayer.implementationVersion), VK_VERSION_MINOR(instanceLayer.implementationVersion), VK_VERSION_PATCH(instanceLayer.implementationVersion), instanceLayer.description);
state.logger->Debug("Vulkan Layers:{}", layers);
}
@ -40,9 +40,9 @@ namespace skyline::gpu {
}
#ifdef NDEBUG
std::array<const char*, 0> requiredInstanceExtensions{};
constexpr std::array<const char*, 0> requiredInstanceExtensions{};
#else
std::array<const char *, 1> requiredInstanceExtensions{
constexpr std::array<const char *, 1> requiredInstanceExtensions{
VK_EXT_DEBUG_REPORT_EXTENSION_NAME
};
#endif
@ -51,7 +51,7 @@ namespace skyline::gpu {
if (state.logger->configLevel >= Logger::LogLevel::Debug) {
std::string extensions;
for (const auto &instanceExtension : instanceExtensions)
extensions += fmt::format("\n* {} (v{}.{}.{})", instanceExtension.extensionName, VK_VERSION_MAJOR(instanceExtension.specVersion), VK_VERSION_MINOR(instanceExtension.specVersion), VK_VERSION_PATCH(instanceExtension.specVersion));
extensions += util::Format("\n* {} (v{}.{}.{})", instanceExtension.extensionName, VK_VERSION_MAJOR(instanceExtension.specVersion), VK_VERSION_MINOR(instanceExtension.specVersion), VK_VERSION_PATCH(instanceExtension.specVersion));
state.logger->Debug("Vulkan Instance Extensions:{}", extensions);
}
@ -82,8 +82,6 @@ namespace skyline::gpu {
});
}
GPU::GPU(const DeviceState &state) : presentation(state), instance(CreateInstance(state, context)), debugReportCallback(CreateDebugReportCallback(state, instance)) {}
VkBool32 GPU::DebugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char *layerPrefix, const char *message, Logger *logger) {
constexpr std::array<Logger::LogLevel, 5> severityLookup{
Logger::LogLevel::Info, // VK_DEBUG_REPORT_INFORMATION_BIT_EXT
@ -95,4 +93,63 @@ namespace skyline::gpu {
logger->Write(severityLookup.at(std::countr_zero(static_cast<u32>(flags))), util::Format("Vk{}:{}[0x{:X}]:I{}:L{}: {}", layerPrefix, vk::to_string(vk::DebugReportObjectTypeEXT(objectType)), object, messageCode, location, message));
return VK_FALSE;
}
vk::raii::PhysicalDevice GPU::CreatePhysicalDevice(const DeviceState &state, const vk::raii::Instance &instance) {
return std::move(vk::raii::PhysicalDevices(instance).front()); // We just select the first device as we aren't expecting multiple GPUs
}
vk::raii::Device GPU::CreateDevice(const DeviceState &state, const vk::raii::PhysicalDevice &physicalDevice) {
auto properties{physicalDevice.getProperties2().properties}; // We should check for required properties here, if/when we have them
// auto features{physicalDevice.getFeatures2().features}; // Same as above
constexpr std::array<const char *, 1> requiredDeviceExtensions{
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
auto deviceExtensions{physicalDevice.enumerateDeviceExtensionProperties()};
for (const auto &requiredExtension : requiredDeviceExtensions) {
if (![&] {
for (const auto &deviceExtension : deviceExtensions)
if (std::string_view(deviceExtension.extensionName) == std::string_view(requiredExtension))
return true;
return false;
}())
throw exception("Cannot find Vulkan device extension: \"{}\"", requiredExtension);
}
auto queueFamilies{physicalDevice.getQueueFamilyProperties2()};
if (auto family{queueFamilies.front().queueFamilyProperties}; !(family.queueFlags & vk::QueueFlagBits::eGraphics && family.queueFlags & vk::QueueFlagBits::eCompute))
// We only check the first queue family as essentially all mobile GPUs only have a single queue family which supports all operations
throw exception("The first queue family doesn't support both eGraphics and eCompute workloads");
float queuePriority{1.f}; //!< As we only have one queue, it's priority is set to the maximum of 1.0
vk::DeviceQueueCreateInfo queue{
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
if (state.logger->configLevel >= Logger::LogLevel::Error) {
std::string extensionString;
for (const auto &extension : deviceExtensions)
extensionString += util::Format("\n* {} (v{}.{}.{})", extension.extensionName, VK_VERSION_MAJOR(extension.specVersion), VK_VERSION_MINOR(extension.specVersion), VK_VERSION_PATCH(extension.specVersion));
std::string queueString;
for (const auto &queueFamily : queueFamilies) {
auto &family{queueFamily.queueFamilyProperties};
queueString += util::Format("\n* {}x{}{}{}{}{}: TSB{}, MIG({},{},{})", family.queueCount, family.queueFlags & vk::QueueFlagBits::eGraphics ? 'G' : '-', family.queueFlags & vk::QueueFlagBits::eCompute ? 'C' : '-', family.queueFlags & vk::QueueFlagBits::eTransfer ? 'T' : '-', family.queueFlags & vk::QueueFlagBits::eSparseBinding ? 'S' : '-', family.queueFlags & vk::QueueFlagBits::eProtected ? 'P' : '-', family.timestampValidBits, family.minImageTransferGranularity.width, family.minImageTransferGranularity.height, family.minImageTransferGranularity.depth);
}
state.logger->Error("Vulkan Device:\nName: {}\nType: {}\nVulkan Version: {}.{}.{}\nDriver Version: {}.{}.{}\nQueues:{}\nExtensions:{}", properties.deviceName, vk::to_string(properties.deviceType), VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion), VK_VERSION_PATCH(properties.apiVersion), VK_VERSION_MAJOR(properties.driverVersion), VK_VERSION_MINOR(properties.driverVersion), VK_VERSION_PATCH(properties.driverVersion), queueString, extensionString);
}
return vk::raii::Device(physicalDevice, vk::DeviceCreateInfo{
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue,
.enabledExtensionCount = requiredDeviceExtensions.size(),
.ppEnabledExtensionNames = requiredDeviceExtensions.data(),
});
}
GPU::GPU(const DeviceState &state) : vkInstance(CreateInstance(state, vkContext)), vkDebugReportCallback(CreateDebugReportCallback(state, vkInstance)), vkPhysicalDevice(CreatePhysicalDevice(state, vkInstance)), vkDevice(CreateDevice(state, vkPhysicalDevice)), vkQueue(vkDevice, 0, 0), presentation(state) {}
}

View File

@ -11,18 +11,24 @@ namespace skyline::gpu {
*/
class GPU {
private:
vk::raii::Context context;
vk::raii::Instance instance;
vk::raii::DebugReportCallbackEXT debugReportCallback;
vk::Device device;
static vk::raii::Instance CreateInstance(const DeviceState &state, const vk::raii::Context &context);
static vk::raii::DebugReportCallbackEXT CreateDebugReportCallback(const DeviceState &state, const vk::raii::Instance &instance);
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char *layerPrefix, const char *message, Logger *logger);
static vk::raii::PhysicalDevice CreatePhysicalDevice(const DeviceState &state, const vk::raii::Instance &instance);
static vk::raii::Device CreateDevice(const DeviceState &state, const vk::raii::PhysicalDevice &physicalDevice);
public:
vk::raii::Context vkContext; //!< An overarching context for Vulkan with
vk::raii::Instance vkInstance; //!< An instance of Vulkan with all application context
vk::raii::DebugReportCallbackEXT vkDebugReportCallback; //!< An RAII Vulkan debug report manager which calls into DebugCallback
vk::raii::PhysicalDevice vkPhysicalDevice; //!< The underlying physical Vulkan device from which we derieve our logical device
vk::raii::Device vkDevice; //!< The logical Vulkan device which we want to render using
vk::raii::Queue vkQueue; //!< A Vulkan Queue supporting graphics and compute operations
PresentationEngine presentation;
GPU(const DeviceState &state);

View File

@ -46,7 +46,7 @@ namespace skyline {
thread_local inline JniEnvironment env;
JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(environ->NewGlobalRef(instance)), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")), vibrateDeviceId(environ->GetMethodID(instanceClass, "vibrateDevice", "(I[J[I)V")), clearVibrationDeviceId(environ->GetMethodID(instanceClass, "clearVibrationDevice", "(I)V")) {
JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(environ->NewGlobalRef(instance)), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")), vibrateDeviceId(environ->GetMethodID(instanceClass, "vibrateDevice", "(I[J[I)V")), clearVibrationDeviceId(environ->GetMethodID(instanceClass, "clearVibrationDevice", "(I)V")), getVersionCodeId(environ->GetMethodID(instanceClass, "getVersionCode", "()I")) {
env.Initialize(environ);
}
@ -90,4 +90,8 @@ namespace skyline {
void JvmManager::ClearVibrationDevice(jint index) {
env->CallVoidMethod(instance, clearVibrationDeviceId, index);
}
u32 JvmManager::GetVersionCode() {
return env->CallIntMethod(instance, getVersionCodeId);
}
}

View File

@ -95,9 +95,16 @@ namespace skyline {
*/
void ClearVibrationDevice(jint index);
/**
* @brief A call to EmulationActivity.getVersionCode in Kotlin
* @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components
*/
u32 GetVersionCode();
private:
jmethodID initializeControllersId;
jmethodID vibrateDeviceId;
jmethodID clearVibrationDeviceId;
jmethodID getVersionCodeId;
};
}

View File

@ -454,4 +454,14 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
fun clearVibrationDevice(index : Int) {
vibrators[index]?.cancel()
}
/**
* @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components
*/
@ExperimentalUnsignedTypes
@Suppress("unused")
fun getVersionCode() : Int {
val (major, minor, patch) = BuildConfig.VERSION_NAME.split('.').map { it.toUInt() }
return ((major shl 22) or (minor shl 12) or (patch)).toInt()
}
}