1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoBackends/Vulkan/VideoBackend.h"
#include "Common/Logging/LogManager.h"
#include "Common/MsgHandler.h"
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/ObjectCache.h"
#include "VideoBackends/Vulkan/StateTracker.h"
#include "VideoBackends/Vulkan/VKBoundingBox.h"
#include "VideoBackends/Vulkan/VKGfx.h"
#include "VideoBackends/Vulkan/VKPerfQuery.h"
#include "VideoBackends/Vulkan/VKSwapChain.h"
#include "VideoBackends/Vulkan/VKVertexManager.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/VideoConfig.h"
#if defined(VK_USE_PLATFORM_METAL_EXT)
#include <objc/message.h>
#endif
namespace Vulkan
{
void VideoBackend::InitBackendInfo(const WindowSystemInfo& wsi)
{
VulkanContext::PopulateBackendInfo(&g_backend_info);
if (LoadVulkanLibrary())
{
u32 vk_api_version = 0;
VkInstance temp_instance = VulkanContext::CreateVulkanInstance(WindowSystemType::Headless,
false, false, &vk_api_version);
if (temp_instance)
{
if (LoadVulkanInstanceFunctions(temp_instance))
{
VulkanContext::GPUList gpu_list = VulkanContext::EnumerateGPUs(temp_instance);
VulkanContext::PopulateBackendInfoAdapters(&g_backend_info, 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];
VulkanContext::PhysicalDeviceInfo properties(gpu);
VulkanContext::PopulateBackendInfoFeatures(&g_backend_info, gpu, properties);
VulkanContext::PopulateBackendInfoMultisampleModes(&g_backend_info, gpu, properties);
}
}
vkDestroyInstance(temp_instance, nullptr);
}
else
{
PanicAlertFmt("Failed to create Vulkan instance.");
}
UnloadVulkanLibrary();
}
else
{
PanicAlertFmt("Failed to load Vulkan library.");
}
}
// Helper method to check whether the Host GPU logging category is enabled.
static bool IsHostGPULoggingEnabled()
{
return Common::Log::LogManager::GetInstance()->IsEnabled(Common::Log::LogType::HOST_GPU,
Common::Log::LogLevel::LERROR);
}
// Helper method to determine whether to enable the debug utils extension.
static bool ShouldEnableDebugUtils(bool enable_validation_layers)
{
// Enable debug utils 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 utils on every instance, when most
// users will never check the Host GPU logging category.
return enable_validation_layers || IsHostGPULoggingEnabled();
}
bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
{
if (!LoadVulkanLibrary())
{
PanicAlertFmt("Failed to load Vulkan library.");
return false;
}
// Check for presence of the validation layers before trying to enable it
bool enable_validation_layer = g_Config.bEnableValidationLayer;
if (enable_validation_layer && !VulkanContext::CheckValidationLayerAvailablility())
{
WARN_LOG_FMT(VIDEO, "Validation layer requested but not available, disabling.");
enable_validation_layer = false;
}
// 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.
bool enable_surface = wsi.type != WindowSystemType::Headless;
bool enable_debug_utils = ShouldEnableDebugUtils(enable_validation_layer);
u32 vk_api_version = 0;
VkInstance instance = VulkanContext::CreateVulkanInstance(
wsi.type, enable_debug_utils, enable_validation_layer, &vk_api_version);
if (instance == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to create Vulkan instance.");
UnloadVulkanLibrary();
return false;
}
// Load instance function pointers.
if (!LoadVulkanInstanceFunctions(instance))
{
PanicAlertFmt("Failed to load Vulkan instance functions.");
vkDestroyInstance(instance, nullptr);
UnloadVulkanLibrary();
return false;
}
// 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())
{
PanicAlertFmt("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_backend_info);
VulkanContext::PopulateBackendInfoAdapters(&g_backend_info, gpu_list);
// We need the surface before we can create a device, as some parameters depend on it.
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (enable_surface)
{
surface = SwapChain::CreateVulkanSurface(instance, wsi);
if (surface == VK_NULL_HANDLE)
{
PanicAlertFmt("Failed to create Vulkan surface.");
vkDestroyInstance(instance, nullptr);
UnloadVulkanLibrary();
return false;
}
}
// Since we haven't called InitializeShared yet, iAdapter may be out of range,
// so we have to check it ourselves.
size_t selected_adapter_index = static_cast<size_t>(g_Config.iAdapter);
if (selected_adapter_index >= gpu_list.size())
{
WARN_LOG_FMT(VIDEO, "Vulkan adapter index out of range, selecting first adapter.");
selected_adapter_index = 0;
}
// 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_utils,
enable_validation_layer, vk_api_version);
if (!g_vulkan_context)
{
PanicAlertFmt("Failed to create Vulkan device");
UnloadVulkanLibrary();
return false;
}
// 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_backend_info, g_vulkan_context->GetPhysicalDevice(),
g_vulkan_context->GetDeviceInfo());
VulkanContext::PopulateBackendInfoMultisampleModes(
&g_backend_info, g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetDeviceInfo());
g_backend_info.bSupportsExclusiveFullscreen =
enable_surface && g_vulkan_context->SupportsExclusiveFullscreen(wsi, surface);
UpdateActiveConfig();
// Remaining classes are also dependent on object cache.
g_object_cache = std::make_unique<ObjectCache>();
if (!g_object_cache->Initialize())
{
PanicAlertFmt("Failed to initialize Vulkan object cache.");
Shutdown();
return false;
}
// 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)
{
swap_chain = SwapChain::Create(wsi, surface, g_ActiveConfig.bVSyncActive);
if (!swap_chain)
{
PanicAlertFmt("Failed to create Vulkan swap chain.");
Shutdown();
return false;
}
}
// 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);
size_t swapchain_image_count =
surface != VK_NULL_HANDLE ? swap_chain->GetSwapChainImageCount() : 0;
if (!g_command_buffer_mgr->Initialize(swapchain_image_count))
{
PanicAlertFmt("Failed to create Vulkan command buffers");
Shutdown();
return false;
}
if (!StateTracker::CreateInstance())
{
PanicAlertFmt("Failed to create state tracker");
Shutdown();
return false;
}
auto gfx = std::make_unique<VKGfx>(std::move(swap_chain), wsi.render_surface_scale);
auto vertex_manager = std::make_unique<VertexManager>();
auto perf_query = std::make_unique<PerfQuery>();
auto bounding_box = std::make_unique<VKBoundingBox>();
return InitializeShared(std::move(gfx), std::move(vertex_manager), std::move(perf_query),
std::move(bounding_box));
}
void VideoBackend::Shutdown()
{
if (g_vulkan_context)
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
if (g_object_cache)
g_object_cache->Shutdown();
ShutdownShared();
g_object_cache.reset();
StateTracker::DestroyInstance();
g_command_buffer_mgr.reset();
g_vulkan_context.reset();
UnloadVulkanLibrary();
}
void VideoBackend::PrepareWindow(WindowSystemInfo& wsi)
{
#if defined(VK_USE_PLATFORM_METAL_EXT)
// We only need to manually create the CAMetalLayer on macOS.
if (wsi.type != WindowSystemType::MacOS)
return;
// This is kinda messy, but it avoids having to write Objective C++ just to create a metal layer.
id view = reinterpret_cast<id>(wsi.render_surface);
Class clsCAMetalLayer = objc_getClass("CAMetalLayer");
if (!clsCAMetalLayer)
{
ERROR_LOG_FMT(VIDEO, "Failed to get CAMetalLayer class.");
return;
}
// [CAMetalLayer layer]
id layer = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("CAMetalLayer"),
sel_getUid("layer"));
if (!layer)
{
ERROR_LOG_FMT(VIDEO, "Failed to create Metal layer.");
return;
}
// [view setWantsLayer:YES]
reinterpret_cast<void (*)(id, SEL, BOOL)>(objc_msgSend)(view, sel_getUid("setWantsLayer:"), YES);
// [view setLayer:layer]
reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend)(view, sel_getUid("setLayer:"), layer);
// NSScreen* screen = [NSScreen mainScreen]
id screen = reinterpret_cast<id (*)(Class, SEL)>(objc_msgSend)(objc_getClass("NSScreen"),
sel_getUid("mainScreen"));
// CGFloat factor = [screen backingScaleFactor]
double factor =
reinterpret_cast<double (*)(id, SEL)>(objc_msgSend)(screen, sel_getUid("backingScaleFactor"));
// layer.contentsScale = factor
reinterpret_cast<void (*)(id, SEL, double)>(objc_msgSend)(layer, sel_getUid("setContentsScale:"),
factor);
// Store the layer pointer, that way MoltenVK doesn't call [NSView layer] outside the main thread.
wsi.render_surface = layer;
#endif
}
} // namespace Vulkan
|