| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  | // Copyright 2016 Dolphin Emulator Project
 | 
					
						
							|  |  |  | // Licensed under GPLv2+
 | 
					
						
							|  |  |  | // Refer to the license.txt file included.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <vector>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-04 19:36:48 +10:00
										 |  |  | #include "Common/Logging/LogManager.h"
 | 
					
						
							| 
									
										
										
										
											2017-01-31 23:29:29 -05:00
										 |  |  | #include "Common/MsgHandler.h"
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/CommandBufferManager.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/Constants.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/FramebufferManager.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/ObjectCache.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/PerfQuery.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/Renderer.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/StateTracker.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/SwapChain.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/TextureCache.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/VertexManager.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/VideoBackend.h"
 | 
					
						
							|  |  |  | #include "VideoBackends/Vulkan/VulkanContext.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "VideoCommon/OnScreenDisplay.h"
 | 
					
						
							|  |  |  | #include "VideoCommon/VideoBackendBase.h"
 | 
					
						
							|  |  |  | #include "VideoCommon/VideoConfig.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Vulkan | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | void VideoBackend::InitBackendInfo() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   VulkanContext::PopulateBackendInfo(&g_Config); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (LoadVulkanLibrary()) | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2016-12-04 19:36:48 +10:00
										 |  |  |     VkInstance temp_instance = VulkanContext::CreateVulkanInstance(false, false, false); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     if (temp_instance) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       if (LoadVulkanInstanceFunctions(temp_instance)) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(temp_instance); | 
					
						
							|  |  |  |         VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!gpu_list.empty()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           // Use the selected adapter, or the first to fill features.
 | 
					
						
							|  |  |  |           size_t device_index = static_cast<size_t>(g_Config.iAdapter); | 
					
						
							|  |  |  |           if (device_index >= gpu_list.size()) | 
					
						
							|  |  |  |             device_index = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           VkPhysicalDevice gpu = gpu_list[device_index]; | 
					
						
							|  |  |  |           VkPhysicalDeviceProperties properties; | 
					
						
							|  |  |  |           vkGetPhysicalDeviceProperties(gpu, &properties); | 
					
						
							|  |  |  |           VkPhysicalDeviceFeatures features; | 
					
						
							|  |  |  |           vkGetPhysicalDeviceFeatures(gpu, &features); | 
					
						
							|  |  |  |           VulkanContext::PopulateBackendInfoFeatures(&g_Config, gpu, features); | 
					
						
							|  |  |  |           VulkanContext::PopulateBackendInfoMultisampleModes(&g_Config, gpu, properties); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       vkDestroyInstance(temp_instance, nullptr); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       PanicAlert("Failed to create Vulkan instance."); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     UnloadVulkanLibrary(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to load Vulkan library."); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-04 19:36:48 +10:00
										 |  |  | // Helper method to check whether the Host GPU logging category is enabled.
 | 
					
						
							|  |  |  | static bool IsHostGPULoggingEnabled() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   return LogManager::GetInstance()->IsEnabled(LogTypes::HOST_GPU, LogTypes::LERROR); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helper method to determine whether to enable the debug report extension.
 | 
					
						
							|  |  |  | static bool ShouldEnableDebugReports(bool enable_validation_layers) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   // Enable debug reports if the Host GPU log option is checked, or validation layers are enabled.
 | 
					
						
							|  |  |  |   // The only issue here is that if Host GPU is not checked when the instance is created, the debug
 | 
					
						
							|  |  |  |   // report extension will not be enabled, requiring the game to be restarted before any reports
 | 
					
						
							|  |  |  |   // will be logged. Otherwise, we'd have to enable debug reports on every instance, when most
 | 
					
						
							|  |  |  |   // users will never check the Host GPU logging category.
 | 
					
						
							|  |  |  |   return enable_validation_layers || IsHostGPULoggingEnabled(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  | bool VideoBackend::Initialize(void* window_handle) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (!LoadVulkanLibrary()) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to load Vulkan library."); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-04 19:36:48 +10:00
										 |  |  |   // Check for presence of the validation layers before trying to enable it
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   bool enable_validation_layer = g_Config.bEnableValidationLayer; | 
					
						
							|  |  |  |   if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility()) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     WARN_LOG(VIDEO, "Validation layer requested but not available, disabling."); | 
					
						
							|  |  |  |     enable_validation_layer = false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Create Vulkan instance, needed before we can create a surface, or enumerate devices.
 | 
					
						
							|  |  |  |   // We use this instance to fill in backend info, then re-use it for the actual device.
 | 
					
						
							| 
									
										
										
										
											2016-12-04 19:36:48 +10:00
										 |  |  |   bool enable_surface = window_handle != nullptr; | 
					
						
							|  |  |  |   bool enable_debug_reports = ShouldEnableDebugReports(enable_validation_layer); | 
					
						
							|  |  |  |   VkInstance instance = VulkanContext::CreateVulkanInstance(enable_surface, enable_debug_reports, | 
					
						
							|  |  |  |                                                             enable_validation_layer); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   if (instance == VK_NULL_HANDLE) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to create Vulkan instance."); | 
					
						
							|  |  |  |     UnloadVulkanLibrary(); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Load instance function pointers.
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   if (!LoadVulkanInstanceFunctions(instance)) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to load Vulkan instance functions."); | 
					
						
							|  |  |  |     vkDestroyInstance(instance, nullptr); | 
					
						
							|  |  |  |     UnloadVulkanLibrary(); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Obtain a list of physical devices (GPUs) from the instance.
 | 
					
						
							|  |  |  |   // We'll re-use this list later when creating the device.
 | 
					
						
							|  |  |  |   VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(instance); | 
					
						
							|  |  |  |   if (gpu_list.empty()) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("No Vulkan physical devices available."); | 
					
						
							|  |  |  |     vkDestroyInstance(instance, nullptr); | 
					
						
							|  |  |  |     UnloadVulkanLibrary(); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Populate BackendInfo with as much information as we can at this point.
 | 
					
						
							|  |  |  |   VulkanContext::PopulateBackendInfo(&g_Config); | 
					
						
							|  |  |  |   VulkanContext::PopulateBackendInfoAdapters(&g_Config, gpu_list); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // We need the surface before we can create a device, as some parameters depend on it.
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   VkSurfaceKHR surface = VK_NULL_HANDLE; | 
					
						
							|  |  |  |   if (enable_surface) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     surface = SwapChain::CreateVulkanSurface(instance, window_handle); | 
					
						
							|  |  |  |     if (surface == VK_NULL_HANDLE) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       PanicAlert("Failed to create Vulkan surface."); | 
					
						
							|  |  |  |       vkDestroyInstance(instance, nullptr); | 
					
						
							|  |  |  |       UnloadVulkanLibrary(); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Since we haven't called InitializeShared yet, iAdapter may be out of range,
 | 
					
						
							|  |  |  |   // so we have to check it ourselves.
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter); | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   if (selected_adapter_index >= gpu_list.size()) | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   { | 
					
						
							|  |  |  |     WARN_LOG(VIDEO, "Vulkan adapter index out of range, selecting first adapter."); | 
					
						
							|  |  |  |     selected_adapter_index = 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Now we can create the Vulkan device. VulkanContext takes ownership of the instance and surface.
 | 
					
						
							|  |  |  |   g_vulkan_context = VulkanContext::Create(instance, gpu_list[selected_adapter_index], surface, | 
					
						
							|  |  |  |                                            enable_debug_reports, enable_validation_layer); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   if (!g_vulkan_context) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to create Vulkan device"); | 
					
						
							|  |  |  |     UnloadVulkanLibrary(); | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |   // Since VulkanContext maintains a copy of the device features and properties, we can use this
 | 
					
						
							|  |  |  |   // to initialize the backend information, so that we don't need to enumerate everything again.
 | 
					
						
							|  |  |  |   VulkanContext::PopulateBackendInfoFeatures(&g_Config, g_vulkan_context->GetPhysicalDevice(), | 
					
						
							|  |  |  |                                              g_vulkan_context->GetDeviceFeatures()); | 
					
						
							|  |  |  |   VulkanContext::PopulateBackendInfoMultisampleModes( | 
					
						
							|  |  |  |       &g_Config, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceProperties()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // With the backend information populated, we can now initialize videocommon.
 | 
					
						
							|  |  |  |   InitializeShared(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-02 21:37:24 +10:00
										 |  |  |   // Create swap chain. This has to be done early so that the target size is correct for auto-scale.
 | 
					
						
							|  |  |  |   std::unique_ptr<SwapChain> swap_chain; | 
					
						
							|  |  |  |   if (surface != VK_NULL_HANDLE) | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2016-10-02 22:09:19 +10:00
										 |  |  |     swap_chain = SwapChain::Create(window_handle, surface, g_Config.IsVSync()); | 
					
						
							| 
									
										
										
										
											2016-10-02 21:37:24 +10:00
										 |  |  |     if (!swap_chain) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       PanicAlert("Failed to create Vulkan swap chain."); | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |       g_vulkan_context.reset(); | 
					
						
							|  |  |  |       ShutdownShared(); | 
					
						
							|  |  |  |       UnloadVulkanLibrary(); | 
					
						
							| 
									
										
										
										
											2016-10-02 21:37:24 +10:00
										 |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   // Create command buffers. We do this separately because the other classes depend on it.
 | 
					
						
							|  |  |  |   g_command_buffer_mgr = std::make_unique<CommandBufferManager>(g_Config.bBackendMultithreading); | 
					
						
							|  |  |  |   if (!g_command_buffer_mgr->Initialize()) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to create Vulkan command buffers"); | 
					
						
							|  |  |  |     g_command_buffer_mgr.reset(); | 
					
						
							|  |  |  |     g_vulkan_context.reset(); | 
					
						
							|  |  |  |     ShutdownShared(); | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |     UnloadVulkanLibrary(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Create main wrapper instances.
 | 
					
						
							|  |  |  |   g_object_cache = std::make_unique<ObjectCache>(); | 
					
						
							|  |  |  |   g_framebuffer_manager = std::make_unique<FramebufferManager>(); | 
					
						
							| 
									
										
										
										
											2016-10-02 21:37:24 +10:00
										 |  |  |   g_renderer = std::make_unique<Renderer>(std::move(swap_chain)); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Invoke init methods on main wrapper classes.
 | 
					
						
							|  |  |  |   // These have to be done before the others because the destructors
 | 
					
						
							|  |  |  |   // for the remaining classes may call methods on these.
 | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |   if (!g_object_cache->Initialize() || !FramebufferManager::GetInstance()->Initialize() || | 
					
						
							|  |  |  |       !StateTracker::CreateInstance() || !Renderer::GetInstance()->Initialize()) | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to initialize Vulkan classes."); | 
					
						
							|  |  |  |     g_renderer.reset(); | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |     StateTracker::DestroyInstance(); | 
					
						
							|  |  |  |     g_framebuffer_manager.reset(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     g_object_cache.reset(); | 
					
						
							|  |  |  |     g_command_buffer_mgr.reset(); | 
					
						
							|  |  |  |     g_vulkan_context.reset(); | 
					
						
							|  |  |  |     ShutdownShared(); | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |     UnloadVulkanLibrary(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Create remaining wrapper instances.
 | 
					
						
							|  |  |  |   g_vertex_manager = std::make_unique<VertexManager>(); | 
					
						
							|  |  |  |   g_texture_cache = std::make_unique<TextureCache>(); | 
					
						
							|  |  |  |   g_perf_query = std::make_unique<PerfQuery>(); | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |   if (!VertexManager::GetInstance()->Initialize() || !TextureCache::GetInstance()->Initialize() || | 
					
						
							|  |  |  |       !PerfQuery::GetInstance()->Initialize()) | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   { | 
					
						
							|  |  |  |     PanicAlert("Failed to initialize Vulkan classes."); | 
					
						
							|  |  |  |     g_perf_query.reset(); | 
					
						
							|  |  |  |     g_texture_cache.reset(); | 
					
						
							|  |  |  |     g_vertex_manager.reset(); | 
					
						
							|  |  |  |     g_renderer.reset(); | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |     StateTracker::DestroyInstance(); | 
					
						
							|  |  |  |     g_framebuffer_manager.reset(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     g_object_cache.reset(); | 
					
						
							|  |  |  |     g_command_buffer_mgr.reset(); | 
					
						
							|  |  |  |     g_vulkan_context.reset(); | 
					
						
							|  |  |  |     ShutdownShared(); | 
					
						
							| 
									
										
										
										
											2017-01-29 22:14:09 +10:00
										 |  |  |     UnloadVulkanLibrary(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |     return false; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This is called after Initialize() from the Core
 | 
					
						
							|  |  |  | // Run from the graphics thread
 | 
					
						
							|  |  |  | void VideoBackend::Video_Prepare() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   // Display the name so the user knows which device was actually created
 | 
					
						
							|  |  |  |   OSD::AddMessage(StringFromFormat("Using physical adapter %s", | 
					
						
							|  |  |  |                                    g_vulkan_context->GetDeviceProperties().deviceName) | 
					
						
							|  |  |  |                       .c_str(), | 
					
						
							|  |  |  |                   5000); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void VideoBackend::Shutdown() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   g_command_buffer_mgr->WaitForGPUIdle(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_object_cache.reset(); | 
					
						
							|  |  |  |   g_command_buffer_mgr.reset(); | 
					
						
							|  |  |  |   g_vulkan_context.reset(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   UnloadVulkanLibrary(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ShutdownShared(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void VideoBackend::Video_Cleanup() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   g_command_buffer_mgr->WaitForGPUIdle(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Save all cached pipelines out to disk for next time.
 | 
					
						
							|  |  |  |   g_object_cache->SavePipelineCache(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   g_perf_query.reset(); | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |   g_texture_cache.reset(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  |   g_vertex_manager.reset(); | 
					
						
							|  |  |  |   g_framebuffer_manager.reset(); | 
					
						
							| 
									
										
										
										
											2016-10-22 20:50:36 +10:00
										 |  |  |   StateTracker::DestroyInstance(); | 
					
						
							|  |  |  |   g_renderer.reset(); | 
					
						
							| 
									
										
										
										
											2016-08-13 22:57:50 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |   CleanupShared(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | } |