From ce278affb7867d7210c73341cdf28fd63000f8b6 Mon Sep 17 00:00:00 2001 From: Egor Yusov Date: Sat, 17 Mar 2018 12:07:06 -0700 Subject: Added VulkanInstance helper class --- Graphics/GraphicsEngineVulkan/CMakeLists.txt | 15 +- .../include/VulkanUtilities/VulkanInstance.h | 54 ++++++ .../src/RenderDeviceFactoryVk.cpp | 101 +++------- .../src/VulkanUtilities/VulkanDebug.cpp | 5 +- .../src/VulkanUtilities/VulkanInstance.cpp | 213 +++++++++++++++++++++ 5 files changed, 309 insertions(+), 79 deletions(-) create mode 100644 Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanInstance.h create mode 100644 Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanInstance.cpp (limited to 'Graphics/GraphicsEngineVulkan') diff --git a/Graphics/GraphicsEngineVulkan/CMakeLists.txt b/Graphics/GraphicsEngineVulkan/CMakeLists.txt index 5c9658d7..91875ac1 100644 --- a/Graphics/GraphicsEngineVulkan/CMakeLists.txt +++ b/Graphics/GraphicsEngineVulkan/CMakeLists.txt @@ -30,9 +30,14 @@ set(INCLUDE include/SwapChainVkImpl.h include/TextureVkImpl.h include/TextureViewVkImpl.h +) + +set(VULKAN_UTILS_INCLUDE include/VulkanUtilities/VulkanDebug.h + include/VulkanUtilities/VulkanInstance.h ) + set(INTERFACE interface/BufferVk.h interface/BufferViewVk.h @@ -76,7 +81,11 @@ set(SRC src/SwapChainVkImpl.cpp src/TextureVkImpl.cpp src/TextureViewVkImpl.cpp +) + +set(VULKAN_UTILS_SRC src/VulkanUtilities/VulkanDebug.cpp + src/VulkanUtilities/VulkanInstance.cpp ) #set(SHADERS @@ -110,13 +119,13 @@ INTERFACE ) add_library(GraphicsEngineVk-static STATIC - ${SRC} ${INTERFACE} ${INCLUDE} ${SHADERS} + ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${SHADERS} readme.md #shaders/GenerateMips/GenerateMipsCS.hlsli ) add_library(GraphicsEngineVk-shared SHARED - ${SRC} ${INTERFACE} ${INCLUDE} ${SHADERS} + ${SRC} ${VULKAN_UTILS_SRC} ${INTERFACE} ${INCLUDE} ${VULKAN_UTILS_INCLUDE} ${SHADERS} src/DLLMain.cpp src/GraphicsEngineVk.def readme.md @@ -172,6 +181,7 @@ set_common_target_properties(GraphicsEngineVk-shared) set_common_target_properties(GraphicsEngineVk-static) source_group("src" FILES ${SRC}) +source_group("src\\vulkan utils" FILES ${VULKAN_UTILS_SRC}) source_group("dll" FILES src/DLLMain.cpp @@ -180,6 +190,7 @@ source_group("dll" FILES source_group("include" FILES ${INCLUDE}) source_group("interface" FILES ${INTERFACE}) +source_group("include\\vulkan utils" FILES ${VULKAN_UTILS_INCLUDE}) #source_group("shaders" FILES # ${SHADERS} # shaders/GenerateMips/GenerateMipsCS.hlsli diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanInstance.h b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanInstance.h new file mode 100644 index 00000000..9937d613 --- /dev/null +++ b/Graphics/GraphicsEngineVulkan/include/VulkanUtilities/VulkanInstance.h @@ -0,0 +1,54 @@ +/* Copyright 2015-2018 Egor Yusov +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. +* +* In no event and under no legal theory, whether in tort (including negligence), +* contract, or otherwise, unless required by applicable law (such as deliberate +* and grossly negligent acts) or agreed to in writing, shall any Contributor be +* liable for any damages, including any direct, indirect, special, incidental, +* or consequential damages of any character arising as a result of this License or +* out of the use or inability to use the software (including but not limited to damages +* for loss of goodwill, work stoppage, computer failure or malfunction, or any and +* all other commercial damages or losses), even if such Contributor has been advised +* of the possibility of such damages. +*/ + +#pragma once + +#include +#include "vulkan.h" + +namespace VulkanUtilities +{ + class VulkanInstance + { + public: + VulkanInstance(bool EnableValidation, + uint32_t GlobalExtensionCount, + const char* const* ppGlobalExtensionNames, + VkAllocationCallbacks* pVkAllocator); + ~VulkanInstance(); + + bool IsLayerAvailable(const char *LayerName)const; + bool IsExtensionAvailable(const char *ExtensionName)const; + VkPhysicalDevice SelectPhysicalDevice(); + + private: + + bool m_ValidationEnabled = false; + VkAllocationCallbacks* const m_pVkAllocator; + VkInstance m_VkInstance = VK_NULL_HANDLE; + std::vector m_Layers; + std::vector m_Extensions; + std::vector m_PhysicalDevices; + VkPhysicalDevice m_SelectedPhysicalDevice = VK_NULL_HANDLE; + }; +} diff --git a/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp b/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp index eb18298b..0f49494d 100644 --- a/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp +++ b/Graphics/GraphicsEngineVulkan/src/RenderDeviceFactoryVk.cpp @@ -25,6 +25,8 @@ /// Routines that initialize Vulkan-based engine implementation #include "pch.h" +#include + #include "RenderDeviceFactoryVk.h" #include "RenderDeviceVkImpl.h" #include "DeviceContextVkImpl.h" @@ -33,7 +35,7 @@ #include "StringTools.h" #include "EngineMemory.h" #include "CommandQueueVkImpl.h" -#include "VulkanUtilities/VulkanDebug.h" +#include "VulkanUtilities/VulkanInstance.h" namespace Diligent { @@ -66,9 +68,6 @@ public: const SwapChainDesc& SwapChainDesc, void* pNativeWndHandle, ISwapChain **ppSwapChain )override final; -private: - void CreateVulkanInstance(const EngineVkAttribs& CreationAttribs, VkInstance &instance); - }; #if 0 @@ -102,66 +101,6 @@ void GetHardwareAdapter(IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter) } #endif -void EngineFactoryVkImpl::CreateVulkanInstance(const EngineVkAttribs& CreationAttribs, VkInstance &instance) -{ - bool EnableValidation = CreationAttribs.EnableValidation; -#ifdef _DEBUG - EnableValidation = true; -#endif - - VkApplicationInfo appInfo = {}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pNext = nullptr; // Pointer to an extension-specific structure. - appInfo.pApplicationName = nullptr; - appInfo.applicationVersion = 0; // Developer-supplied version number of the application - appInfo.pEngineName = "Diligent Engine"; - appInfo.engineVersion = 0;// Developer-supplied version number of the engine used to create the application. - appInfo.apiVersion = VK_API_VERSION_1_0; - - std::vector GlobalExtensions = { VK_KHR_SURFACE_EXTENSION_NAME }; - - // Enable surface extensions depending on os -#if defined(VK_USE_PLATFORM_WIN32_KHR) - GlobalExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); -#elif defined(VK_USE_PLATFORM_ANDROID_KHR) - GlobalExtensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); -#elif defined(_DIRECT2DISPLAY) - GlobalExtensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME); -#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) - GlobalExtensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); -#elif defined(VK_USE_PLATFORM_XCB_KHR) - GlobalExtensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); -#elif defined(VK_USE_PLATFORM_IOS_MVK) - GlobalExtensions.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME); -#elif defined(VK_USE_PLATFORM_MACOS_MVK) - GlobalExtensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); -#endif - - if (EnableValidation) - { - GlobalExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); - } - - for(Uint32 ext = 0; ext < CreationAttribs.GlobalExtensionCount; ++ext) - GlobalExtensions.push_back(CreationAttribs.ppGlobalExtensionNames[ext]); - - VkInstanceCreateInfo InstanceCreateInfo = {}; - InstanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - InstanceCreateInfo.pNext = NULL; // Pointer to an extension-specific structure. - InstanceCreateInfo.flags = 0; // Reserved for future use. - InstanceCreateInfo.pApplicationInfo = &appInfo; - InstanceCreateInfo.enabledExtensionCount = static_cast(GlobalExtensions.size()); - InstanceCreateInfo.ppEnabledExtensionNames = GlobalExtensions.empty() ? nullptr : GlobalExtensions.data(); - - if (EnableValidation) - { - InstanceCreateInfo.enabledLayerCount = static_cast(_countof(VulkanUtilities::ValidationLayerNames)); - InstanceCreateInfo.ppEnabledLayerNames = VulkanUtilities::ValidationLayerNames; - } - auto res = vkCreateInstance(&InstanceCreateInfo, reinterpret_cast(CreationAttribs.pVkAllocator), &instance); - CHECK_VK_ERROR_AND_THROW(res, "Failed to create Vulkan instance"); -} - /// Creates render device and device contexts for Vulkan backend @@ -177,19 +116,29 @@ void EngineFactoryVkImpl::CreateVulkanInstance(const EngineVkAttribs& CreationAt /// contexts are written to ppContexts array starting /// at position 1 void EngineFactoryVkImpl::CreateDeviceAndContextsVk( const EngineVkAttribs& CreationAttribs, - IRenderDevice **ppDevice, - IDeviceContext **ppContexts, - Uint32 NumDeferredContexts) + IRenderDevice **ppDevice, + IDeviceContext **ppContexts, + Uint32 NumDeferredContexts) { VERIFY( ppDevice && ppContexts, "Null pointer provided" ); if( !ppDevice || !ppContexts ) return; - VkInstance instance; - CreateVulkanInstance(CreationAttribs, instance); + std::shared_ptr Instance; + try + { + Instance = std::make_shared( + CreationAttribs.EnableValidation, + CreationAttribs.GlobalExtensionCount, + CreationAttribs.ppGlobalExtensionNames, + reinterpret_cast(CreationAttribs.pVkAllocator)); + } + catch(std::runtime_error& ) + { + return; + } - // Vulkan instance - //err = createInstance(settings.validation); + Instance->SelectPhysicalDevice(); #if 0 for(Uint32 Type=Vk_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; Type < Vk_DESCRIPTOR_HEAP_TYPE_NUM_TYPES; ++Type) @@ -332,11 +281,11 @@ void EngineFactoryVkImpl::CreateDeviceAndContextsVk( const EngineVkAttribs& Crea /// contexts are written to ppContexts array starting /// at position 1 void EngineFactoryVkImpl::AttachToVkDevice(void *pVkNativeDevice, - ICommandQueueVk *pCommandQueue, - const EngineVkAttribs& EngineAttribs, - IRenderDevice **ppDevice, - IDeviceContext **ppContexts, - Uint32 NumDeferredContexts) + ICommandQueueVk *pCommandQueue, + const EngineVkAttribs& EngineAttribs, + IRenderDevice **ppDevice, + IDeviceContext **ppContexts, + Uint32 NumDeferredContexts) { VERIFY( pVkNativeDevice && pCommandQueue && ppDevice && ppContexts, "Null pointer provided" ); if( !pVkNativeDevice || !pCommandQueue || !ppDevice || !ppContexts ) diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp index 42fa7d6f..4a3cd749 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanDebug.cpp @@ -80,8 +80,11 @@ namespace VulkanUtilities void SetupDebugging(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportCallbackEXT callBack) { auto CreateDebugReportCallback = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); + VERIFY_EXPR(CreateDebugReportCallback != VK_NULL_HANDLE); //auto dbgBreakCallback = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDebugReportMessageEXT")); + DestroyDebugReportCallback = reinterpret_cast(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); + VERIFY_EXPR(DestroyDebugReportCallback != VK_NULL_HANDLE); VkDebugReportCallbackCreateInfoEXT dbgCreateInfo = {}; dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; @@ -93,7 +96,7 @@ namespace VulkanUtilities &dbgCreateInfo, nullptr, (callBack != VK_NULL_HANDLE) ? &callBack : &msgCallback); - VERIFY_EXPR(err); + VERIFY_EXPR(err == VK_SUCCESS); } void FreeDebugCallback(VkInstance instance) diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanInstance.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanInstance.cpp new file mode 100644 index 00000000..561d990f --- /dev/null +++ b/Graphics/GraphicsEngineVulkan/src/VulkanUtilities/VulkanInstance.cpp @@ -0,0 +1,213 @@ +/* Copyright 2015-2018 Egor Yusov +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. +* +* In no event and under no legal theory, whether in tort (including negligence), +* contract, or otherwise, unless required by applicable law (such as deliberate +* and grossly negligent acts) or agreed to in writing, shall any Contributor be +* liable for any damages, including any direct, indirect, special, incidental, +* or consequential damages of any character arising as a result of this License or +* out of the use or inability to use the software (including but not limited to damages +* for loss of goodwill, work stoppage, computer failure or malfunction, or any and +* all other commercial damages or losses), even if such Contributor has been advised +* of the possibility of such damages. +*/ + +#include +#include "pch.h" +#include "VulkanUtilities/VulkanInstance.h" +#include "VulkanUtilities/VulkanDebug.h" + +namespace VulkanUtilities +{ + bool VulkanInstance::IsLayerAvailable(const char *LayerName)const + { + for(const auto& Layer : m_Layers) + if(strcmp(Layer.layerName, LayerName) == 0) + return true; + + return false; + } + + bool VulkanInstance::IsExtensionAvailable(const char *ExtensionName)const + { + for (const auto& Extension : m_Extensions) + if (strcmp(Extension.extensionName, ExtensionName) == 0) + return true; + + return false; + } + + VulkanInstance::VulkanInstance(bool EnableValidation, + uint32_t GlobalExtensionCount, + const char* const* ppGlobalExtensionNames, + VkAllocationCallbacks* pVkAllocator) : + m_pVkAllocator(pVkAllocator) + { +#ifdef _DEBUG + EnableValidation = true; +#endif + + { + // Enumerate available layers + uint32_t LayerCount = 0; + auto res = vkEnumerateInstanceLayerProperties(&LayerCount, NULL); + CHECK_VK_ERROR_AND_THROW(res, "Failed to query layer count"); + m_Layers.resize(LayerCount); + vkEnumerateInstanceLayerProperties(&LayerCount, m_Layers.data()); + CHECK_VK_ERROR_AND_THROW(res, "Failed to enumerate extensions"); + VERIFY_EXPR(LayerCount == m_Layers.size()); + } + + { + // Enumerate available extensions + uint32_t ExtensionCount = 0; + auto res = vkEnumerateInstanceExtensionProperties(NULL, &ExtensionCount, NULL); + CHECK_VK_ERROR_AND_THROW(res, "Failed to query extension count"); + m_Extensions.resize(ExtensionCount); + vkEnumerateInstanceExtensionProperties(NULL, &ExtensionCount, m_Extensions.data()); + CHECK_VK_ERROR_AND_THROW(res, "Failed to enumerate extensions"); + VERIFY_EXPR(ExtensionCount == m_Extensions.size()); + } + + std::vector GlobalExtensions = + { + VK_KHR_SURFACE_EXTENSION_NAME, + + // Enable surface extensions depending on OS +#if defined(VK_USE_PLATFORM_WIN32_KHR) + VK_KHR_WIN32_SURFACE_EXTENSION_NAME +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME +#elif defined(VK_USE_PLATFORM_XCB_KHR) + VK_KHR_XCB_SURFACE_EXTENSION_NAME +#elif defined(VK_USE_PLATFORM_IOS_MVK) + VK_MVK_IOS_SURFACE_EXTENSION_NAME +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + VK_MVK_MACOS_SURFACE_EXTENSION_NAME +#endif + }; + + if (EnableValidation) + { + GlobalExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } + + for (uint32_t ext = 0; ext < GlobalExtensionCount; ++ext) + GlobalExtensions.push_back(ppGlobalExtensionNames[ext]); + + for(const auto* ExtName : GlobalExtensions) + { + if(!IsExtensionAvailable(ExtName)) + LOG_ERROR_AND_THROW("Requested extension ", ExtName, " is not available"); + } + + VkApplicationInfo appInfo = {}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pNext = nullptr; // Pointer to an extension-specific structure. + appInfo.pApplicationName = nullptr; + appInfo.applicationVersion = 0; // Developer-supplied version number of the application + appInfo.pEngineName = "Diligent Engine"; + appInfo.engineVersion = 0;// Developer-supplied version number of the engine used to create the application. + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo InstanceCreateInfo = {}; + InstanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + InstanceCreateInfo.pNext = NULL; // Pointer to an extension-specific structure. + InstanceCreateInfo.flags = 0; // Reserved for future use. + InstanceCreateInfo.pApplicationInfo = &appInfo; + InstanceCreateInfo.enabledExtensionCount = static_cast(GlobalExtensions.size()); + InstanceCreateInfo.ppEnabledExtensionNames = GlobalExtensions.empty() ? nullptr : GlobalExtensions.data(); + + if (EnableValidation) + { + bool ValidationLayersPresent = true; + for (size_t l = 0; l < _countof(VulkanUtilities::ValidationLayerNames); ++l) + { + auto *pLayerName = VulkanUtilities::ValidationLayerNames[l]; + if (!IsLayerAvailable(pLayerName)) + { + ValidationLayersPresent = false; + LOG_ERROR_MESSAGE("Failed to find ", pLayerName, " layer. Validation will be disabled"); + } + } + if (ValidationLayersPresent) + { + InstanceCreateInfo.enabledLayerCount = static_cast(_countof(VulkanUtilities::ValidationLayerNames)); + InstanceCreateInfo.ppEnabledLayerNames = VulkanUtilities::ValidationLayerNames; + m_ValidationEnabled = true; + } + } + auto res = vkCreateInstance(&InstanceCreateInfo, m_pVkAllocator, &m_VkInstance); + CHECK_VK_ERROR_AND_THROW(res, "Failed to create Vulkan instance"); + + // If requested, we enable the default validation layers for debugging + if (m_ValidationEnabled) + { + // The report flags determine what type of messages for the layers will be displayed + // For validating (debugging) an appplication the error and warning bits should suffice + VkDebugReportFlagsEXT debugReportFlags = + VK_DEBUG_REPORT_ERROR_BIT_EXT | + VK_DEBUG_REPORT_WARNING_BIT_EXT | + VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; + // Additional flags include performance info, loader and layer debug messages, etc. + VulkanUtilities::SetupDebugging(m_VkInstance, debugReportFlags, VK_NULL_HANDLE); + } + + // Enumerate physical devices + { + // Physical device + uint32_t PhysicalDeviceCount = 0; + // Get the number of available physical devices + auto err = vkEnumeratePhysicalDevices(m_VkInstance, &PhysicalDeviceCount, nullptr); + CHECK_VK_ERROR(err, "Failed to get physical device count"); + if (PhysicalDeviceCount == 0) + LOG_ERROR_AND_THROW("No physical devices found on the system"); + + // Enumerate devices + m_PhysicalDevices.resize(PhysicalDeviceCount); + err = vkEnumeratePhysicalDevices(m_VkInstance, &PhysicalDeviceCount, m_PhysicalDevices.data()); + CHECK_VK_ERROR(err, "Failed to enumerate physical devices"); + VERIFY_EXPR(m_PhysicalDevices.size() == PhysicalDeviceCount); + } + } + + VulkanInstance::~VulkanInstance() + { + if(m_ValidationEnabled) + { + VulkanUtilities::FreeDebugCallback(m_VkInstance); + } + vkDestroyInstance(m_VkInstance, m_pVkAllocator); + } + + VkPhysicalDevice VulkanInstance::SelectPhysicalDevice() + { + for (auto Device : m_PhysicalDevices) + { + VkPhysicalDeviceProperties DeviceProps; + vkGetPhysicalDeviceProperties(Device, &DeviceProps); + if (DeviceProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + { + m_SelectedPhysicalDevice = Device; + LOG_INFO_MESSAGE("Selected discrete GPU ", DeviceProps.deviceName); + break; + } + } + + if(m_SelectedPhysicalDevice == VK_NULL_HANDLE) + LOG_ERROR_MESSAGE("Failed to find suitable physical device"); + + return m_SelectedPhysicalDevice; + } +} -- cgit v1.2.3