Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/Vulkan/VulkanContext.h
5693 views
1
#pragma once
2
3
#include <cstdint>
4
#include <cstring>
5
#include <string>
6
#include <string_view>
7
#include <vector>
8
#include <utility>
9
#include <functional>
10
11
#include "Common/Common.h"
12
#include "Common/Log.h"
13
#include "Common/GPU/Vulkan/VulkanLoader.h"
14
#include "Common/GPU/Vulkan/VulkanDebug.h"
15
#include "Common/GPU/Vulkan/VulkanAlloc.h"
16
#include "Common/GPU/Vulkan/VulkanProfiler.h"
17
18
// Enable or disable a simple logging profiler for Vulkan.
19
// Mostly useful for profiling texture uploads currently, but could be useful for
20
// other things as well. We also have a nice integrated render pass profiler in the queue
21
// runner, but this one is more convenient for transient events.
22
23
#define VK_PROFILE_BEGIN(vulkan, cmd, stage, ...) vulkan->GetProfiler()->Begin(cmd, stage, __VA_ARGS__);
24
#define VK_PROFILE_END(vulkan, cmd, stage) vulkan->GetProfiler()->End(cmd, stage);
25
26
enum class VulkanInitFlags : uint32_t {
27
VALIDATE = (1 << 0),
28
DISABLE_IMPLICIT_LAYERS = (1 << 5),
29
};
30
ENUM_CLASS_BITOPS(VulkanInitFlags);
31
32
enum {
33
VULKAN_VENDOR_NVIDIA = 0x000010de,
34
VULKAN_VENDOR_INTEL = 0x00008086, // Haha!
35
VULKAN_VENDOR_AMD = 0x00001002,
36
VULKAN_VENDOR_ARM = 0x000013B5, // Mali
37
VULKAN_VENDOR_QUALCOMM = 0x00005143,
38
VULKAN_VENDOR_IMGTEC = 0x00001010, // PowerVR
39
VULKAN_VENDOR_APPLE = 0x0000106b, // Apple through MoltenVK
40
VULKAN_VENDOR_MESA = 0x00010005, // lavapipe
41
};
42
43
VK_DEFINE_HANDLE(VmaAllocator);
44
VK_DEFINE_HANDLE(VmaAllocation);
45
46
std::string VulkanVendorString(uint32_t vendorId);
47
48
template<class R, class T> inline void ChainStruct(R &root, T *newStruct) {
49
newStruct->pNext = root.pNext;
50
root.pNext = newStruct;
51
}
52
53
// Not all will be usable on all platforms, of course...
54
enum WindowSystem {
55
WINDOWSYSTEM_UNINITIALIZED,
56
#ifdef _WIN32
57
WINDOWSYSTEM_WIN32,
58
#endif
59
#ifdef __ANDROID__
60
WINDOWSYSTEM_ANDROID,
61
#endif
62
#ifdef VK_USE_PLATFORM_METAL_EXT
63
WINDOWSYSTEM_METAL_EXT,
64
#endif
65
#ifdef VK_USE_PLATFORM_XLIB_KHR
66
WINDOWSYSTEM_XLIB,
67
#endif
68
#ifdef VK_USE_PLATFORM_XCB_KHR
69
WINDOWSYSTEM_XCB,
70
#endif
71
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
72
WINDOWSYSTEM_WAYLAND,
73
#endif
74
#ifdef VK_USE_PLATFORM_DISPLAY_KHR
75
WINDOWSYSTEM_DISPLAY,
76
#endif
77
};
78
79
struct VulkanPhysicalDeviceInfo {
80
VkFormat preferredDepthStencilFormat;
81
bool canBlitToPreferredDepthStencilFormat;
82
};
83
84
class VulkanProfiler;
85
class VulkanContext;
86
87
// Extremely rough split of capabilities.
88
enum class PerfClass {
89
SLOW,
90
FAST,
91
};
92
93
// This is a bit repetitive...
94
class VulkanDeleteList {
95
struct BufferWithAlloc {
96
VkBuffer buffer;
97
VmaAllocation alloc;
98
};
99
struct ImageWithAlloc {
100
VkImage image;
101
VmaAllocation alloc;
102
};
103
104
struct Callback {
105
explicit Callback(void(*f)(VulkanContext *vulkan, void *userdata), void *u)
106
: func(f), userdata(u) {
107
}
108
109
void (*func)(VulkanContext *vulkan, void *userdata);
110
void *userdata;
111
};
112
113
public:
114
// NOTE: These all take reference handles so they can zero the input value.
115
void QueueDeleteCommandPool(VkCommandPool &pool) { _dbg_assert_(pool != VK_NULL_HANDLE); cmdPools_.push_back(pool); pool = VK_NULL_HANDLE; }
116
void QueueDeleteDescriptorPool(VkDescriptorPool &pool) { _dbg_assert_(pool != VK_NULL_HANDLE); descPools_.push_back(pool); pool = VK_NULL_HANDLE; }
117
void QueueDeleteShaderModule(VkShaderModule &module) { _dbg_assert_(module != VK_NULL_HANDLE); modules_.push_back(module); module = VK_NULL_HANDLE; }
118
void QueueDeleteBuffer(VkBuffer &buffer) { _dbg_assert_(buffer != VK_NULL_HANDLE); buffers_.push_back(buffer); buffer = VK_NULL_HANDLE; }
119
void QueueDeleteBufferView(VkBufferView &bufferView) { _dbg_assert_(bufferView != VK_NULL_HANDLE); bufferViews_.push_back(bufferView); bufferView = VK_NULL_HANDLE; }
120
void QueueDeleteImageView(VkImageView &imageView) { _dbg_assert_(imageView != VK_NULL_HANDLE); imageViews_.push_back(imageView); imageView = VK_NULL_HANDLE; }
121
void QueueDeleteDeviceMemory(VkDeviceMemory &deviceMemory) { _dbg_assert_(deviceMemory != VK_NULL_HANDLE); deviceMemory_.push_back(deviceMemory); deviceMemory = VK_NULL_HANDLE; }
122
void QueueDeleteSampler(VkSampler &sampler) { _dbg_assert_(sampler != VK_NULL_HANDLE); samplers_.push_back(sampler); sampler = VK_NULL_HANDLE; }
123
void QueueDeletePipeline(VkPipeline &pipeline) { _dbg_assert_(pipeline != VK_NULL_HANDLE); pipelines_.push_back(pipeline); pipeline = VK_NULL_HANDLE; }
124
void QueueDeletePipelineCache(VkPipelineCache &pipelineCache) { _dbg_assert_(pipelineCache != VK_NULL_HANDLE); pipelineCaches_.push_back(pipelineCache); pipelineCache = VK_NULL_HANDLE; }
125
void QueueDeleteRenderPass(VkRenderPass &renderPass) { _dbg_assert_(renderPass != VK_NULL_HANDLE); renderPasses_.push_back(renderPass); renderPass = VK_NULL_HANDLE; }
126
void QueueDeleteFramebuffer(VkFramebuffer &framebuffer) { _dbg_assert_(framebuffer != VK_NULL_HANDLE); framebuffers_.push_back(framebuffer); framebuffer = VK_NULL_HANDLE; }
127
void QueueDeletePipelineLayout(VkPipelineLayout &pipelineLayout) { _dbg_assert_(pipelineLayout != VK_NULL_HANDLE); pipelineLayouts_.push_back(pipelineLayout); pipelineLayout = VK_NULL_HANDLE; }
128
void QueueDeleteDescriptorSetLayout(VkDescriptorSetLayout &descSetLayout) { _dbg_assert_(descSetLayout != VK_NULL_HANDLE); descSetLayouts_.push_back(descSetLayout); descSetLayout = VK_NULL_HANDLE; }
129
void QueueDeleteQueryPool(VkQueryPool &queryPool) { _dbg_assert_(queryPool != VK_NULL_HANDLE); queryPools_.push_back(queryPool); queryPool = VK_NULL_HANDLE; }
130
void QueueCallback(void (*func)(VulkanContext *vulkan, void *userdata), void *userdata) { callbacks_.push_back(Callback(func, userdata)); }
131
132
void QueueDeleteBufferAllocation(VkBuffer &buffer, VmaAllocation &alloc) {
133
_dbg_assert_(buffer != VK_NULL_HANDLE);
134
buffersWithAllocs_.push_back(BufferWithAlloc{ buffer, alloc });
135
buffer = VK_NULL_HANDLE;
136
alloc = VK_NULL_HANDLE;
137
}
138
void QueueDeleteImageAllocation(VkImage &image, VmaAllocation &alloc) {
139
_dbg_assert_(image != VK_NULL_HANDLE && alloc != VK_NULL_HANDLE);
140
imagesWithAllocs_.push_back(ImageWithAlloc{ image, alloc });
141
image = VK_NULL_HANDLE;
142
alloc = VK_NULL_HANDLE;
143
}
144
145
void Take(VulkanDeleteList &del);
146
void PerformDeletes(VulkanContext *vulkan, VmaAllocator allocator);
147
148
int GetLastDeleteCount() const {
149
return deleteCount_;
150
}
151
152
private:
153
std::vector<VkCommandPool> cmdPools_;
154
std::vector<VkDescriptorPool> descPools_;
155
std::vector<VkShaderModule> modules_;
156
std::vector<VkBuffer> buffers_;
157
std::vector<BufferWithAlloc> buffersWithAllocs_;
158
std::vector<VkBufferView> bufferViews_;
159
std::vector<ImageWithAlloc> imagesWithAllocs_;
160
std::vector<VkImageView> imageViews_;
161
std::vector<VkDeviceMemory> deviceMemory_;
162
std::vector<VkSampler> samplers_;
163
std::vector<VkPipeline> pipelines_;
164
std::vector<VkPipelineCache> pipelineCaches_;
165
std::vector<VkRenderPass> renderPasses_;
166
std::vector<VkFramebuffer> framebuffers_;
167
std::vector<VkPipelineLayout> pipelineLayouts_;
168
std::vector<VkDescriptorSetLayout> descSetLayouts_;
169
std::vector<VkQueryPool> queryPools_;
170
std::vector<Callback> callbacks_;
171
int deleteCount_ = 0;
172
};
173
174
// VulkanContext manages the device and swapchain, and deferred deletion of objects.
175
class VulkanContext {
176
public:
177
VulkanContext();
178
~VulkanContext();
179
180
struct CreateInfo {
181
const char *app_name;
182
int app_ver;
183
VulkanInitFlags flags;
184
std::string customDriver;
185
};
186
187
VkResult CreateInstance(const CreateInfo &info);
188
void DestroyInstance();
189
190
int GetBestPhysicalDevice() const;
191
int GetPhysicalDeviceByName(std::string_view name) const;
192
193
// Convenience method to avoid code duplication.
194
// If it returns false, delete the context.
195
bool CreateInstanceAndDevice(const CreateInfo &info);
196
197
// The coreVersion is to avoid enabling extensions that are merged into core Vulkan from a certain version.
198
bool EnableInstanceExtension(const char *extension, uint32_t coreVersion);
199
bool EnableDeviceExtension(const char *extension, uint32_t coreVersion);
200
201
// Was previously two functions, ChooseDevice and CreateDevice.
202
VkResult CreateDevice(int physical_device);
203
204
const std::string &InitError() const { return init_error_; }
205
206
VkDevice GetDevice() const { return device_; }
207
VkInstance GetInstance() const { return instance_; }
208
VulkanInitFlags GetInitFlags() const { return createInfo_.flags; }
209
210
// Of course, this won't update things that can only change on first init.
211
void UpdateCreateInfo(const VulkanContext::CreateInfo &info) { createInfo_ = info; }
212
213
VulkanDeleteList &Delete() { return globalDeleteList_; }
214
215
// The parameters are whatever the chosen window system wants.
216
// The extents will be automatically determined.
217
VkResult InitSurface(WindowSystem winsys, void *data1, void *data2);
218
VkResult ReinitSurface();
219
220
// If the present mode is not available, will fall back to the first available (which is almost always FIFO).
221
bool InitSwapchain(VkPresentModeKHR desiredPresentMode);
222
void SetCbGetDrawSize(std::function<VkExtent2D()>);
223
224
void DestroySwapchain();
225
void DestroySurface();
226
227
void DestroyDevice();
228
229
void PerformPendingDeletes();
230
void WaitUntilQueueIdle();
231
232
// Utility functions for shorter code
233
VkFence CreateFence(bool presignalled);
234
bool CreateShaderModule(const std::vector<uint32_t> &spirv, VkShaderModule *shaderModule, const char *tag);
235
236
void BeginFrame(VkCommandBuffer firstCommandBuffer);
237
void EndFrame();
238
239
VulkanProfiler *GetProfiler() {
240
return &frame_[curFrame_].profiler;
241
}
242
243
// Simple workaround for the casting warning.
244
template <class T>
245
void SetDebugName(T handle, VkObjectType type, const char *name) {
246
if (extensionsLookup_.EXT_debug_utils && handle != VK_NULL_HANDLE) {
247
_dbg_assert_(handle != VK_NULL_HANDLE);
248
SetDebugNameImpl((uint64_t)handle, type, name);
249
}
250
}
251
bool DebugLayerEnabled() const {
252
return extensionsLookup_.EXT_debug_utils;
253
}
254
255
bool MemoryTypeFromProperties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex);
256
257
VkPhysicalDevice GetPhysicalDevice(int n) const {
258
return physical_devices_[n];
259
}
260
VkPhysicalDevice GetCurrentPhysicalDevice() const {
261
return physical_devices_[physical_device_];
262
}
263
int GetCurrentPhysicalDeviceIndex() const {
264
return physical_device_;
265
}
266
int GetNumPhysicalDevices() const {
267
return (int)physical_devices_.size();
268
}
269
270
VkQueue GetGraphicsQueue() const {
271
return gfx_queue_;
272
}
273
274
int GetGraphicsQueueFamilyIndex() const {
275
return graphics_queue_family_index_;
276
}
277
278
struct PhysicalDeviceProps {
279
VkPhysicalDeviceProperties properties;
280
VkPhysicalDevicePushDescriptorPropertiesKHR pushDescriptorProperties;
281
VkPhysicalDeviceExternalMemoryHostPropertiesEXT externalMemoryHostProperties;
282
VkPhysicalDeviceDepthStencilResolveProperties depthStencilResolve;
283
};
284
285
struct AllPhysicalDeviceFeatures {
286
VkPhysicalDeviceFeatures standard;
287
VkPhysicalDeviceMultiviewFeatures multiview;
288
VkPhysicalDevicePresentWaitFeaturesKHR presentWait;
289
VkPhysicalDevicePresentIdFeaturesKHR presentId;
290
VkPhysicalDeviceProvokingVertexFeaturesEXT provokingVertex;
291
VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR presentModeFifoProps;
292
};
293
294
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {
295
if (i < 0)
296
i = GetCurrentPhysicalDeviceIndex();
297
return physicalDeviceProperties_[i];
298
}
299
300
const VkQueueFamilyProperties &GetQueueFamilyProperties(int family) const {
301
return queueFamilyProperties_[family];
302
}
303
304
VkResult GetInstanceLayerExtensionList(const char *layerName, std::vector<VkExtensionProperties> &extensions);
305
VkResult GetInstanceLayerProperties();
306
307
VkResult GetDeviceLayerExtensionList(const char *layerName, std::vector<VkExtensionProperties> &extensions);
308
VkResult GetDeviceLayerProperties();
309
310
const std::vector<VkExtensionProperties> &GetDeviceExtensionsAvailable() const {
311
return device_extension_properties_;
312
}
313
const std::vector<const char *> &GetDeviceExtensionsEnabled() const {
314
return device_extensions_enabled_;
315
}
316
317
const std::vector<VkExtensionProperties> &GetInstanceExtensionsAvailable() const {
318
return instance_extension_properties_;
319
}
320
const std::vector<const char *> &GetInstanceExtensionsEnabled() const {
321
return instance_extensions_enabled_;
322
}
323
324
const VkPhysicalDeviceMemoryProperties &GetMemoryProperties() const {
325
return memory_properties_;
326
}
327
328
struct PhysicalDeviceFeatures {
329
AllPhysicalDeviceFeatures available{};
330
AllPhysicalDeviceFeatures enabled{};
331
};
332
333
const PhysicalDeviceFeatures &GetDeviceFeatures() const { return deviceFeatures_; }
334
const VulkanPhysicalDeviceInfo &GetDeviceInfo() const { return deviceInfo_; }
335
const VkSurfaceCapabilitiesKHR &GetSurfaceCapabilities() const { return surfCapabilities_; }
336
337
bool IsInstanceExtensionAvailable(const char *extensionName) const {
338
for (const auto &iter : instance_extension_properties_) {
339
if (!strcmp(extensionName, iter.extensionName))
340
return true;
341
}
342
343
// Also search through the layers, one of them might carry the extension (especially DEBUG_utils)
344
for (const auto &iter : instance_layer_properties_) {
345
for (const auto &ext : iter.extensions) {
346
if (!strcmp(extensionName, ext.extensionName)) {
347
return true;
348
}
349
}
350
}
351
352
return false;
353
}
354
355
bool IsDeviceExtensionAvailable(const char *name) const {
356
for (auto &iter : device_extension_properties_) {
357
if (!strcmp(name, iter.extensionName))
358
return true;
359
}
360
return false;
361
}
362
363
int GetInflightFrames() const {
364
// out of MAX_INFLIGHT_FRAMES.
365
return inflightFrames_;
366
}
367
368
// Don't call while a frame is in progress.
369
void UpdateInflightFrames(int n);
370
371
int GetCurFrame() const {
372
return curFrame_;
373
}
374
375
VkSwapchainKHR GetSwapchain() const { return swapchain_; }
376
VkFormat GetSwapchainFormat() const { return swapchainFormat_; }
377
bool IsSwapchainInited() const { return swapchainInited_; }
378
bool HasRealSwapchain() const { return swapChainExtent_.width > 0; }
379
380
int GetBackbufferWidth() { return (int)swapChainExtent_.width; }
381
int GetBackbufferHeight() { return (int)swapChainExtent_.height; }
382
383
void SetProfilerEnabledPtr(bool *enabled) {
384
for (auto &frame : frame_) {
385
frame.profiler.SetEnabledPtr(enabled);
386
}
387
}
388
389
// 1 for no frame overlap and thus minimal latency but worst performance.
390
// 2 is an OK compromise, while 3 performs best but risks slightly higher latency.
391
enum {
392
MAX_INFLIGHT_FRAMES = 3,
393
};
394
395
const VulkanExtensions &Extensions() { return extensionsLookup_; }
396
397
PerfClass DevicePerfClass() const {
398
return devicePerfClass_;
399
}
400
401
void GetImageMemoryRequirements(VkImage image, VkMemoryRequirements *mem_reqs, bool *dedicatedAllocation);
402
403
VmaAllocator Allocator() const {
404
return allocator_;
405
}
406
407
const std::vector<VkSurfaceFormatKHR> &SurfaceFormats() {
408
return surfFormats_;
409
}
410
411
VkPresentModeKHR GetPresentMode() const {
412
return presentMode_;
413
}
414
415
std::vector<VkPresentModeKHR> GetAvailablePresentModes() const {
416
return availablePresentModes_;
417
}
418
419
bool PresentModeSupported(VkPresentModeKHR mode) const {
420
for (const auto &m : availablePresentModes_) {
421
if (m == mode) {
422
return true;
423
}
424
}
425
return false;
426
}
427
428
int GetLastDeleteCount() const {
429
return frame_[curFrame_].deleteList.GetLastDeleteCount();
430
}
431
432
u32 InstanceApiVersion() const {
433
return vulkanInstanceApiVersion_;
434
}
435
436
u32 DeviceApiVersion() const {
437
return vulkanDeviceApiVersion_;
438
}
439
440
private:
441
bool ChooseQueue();
442
443
void SetDebugNameImpl(uint64_t handle, VkObjectType type, const char *name);
444
445
VkResult InitDebugUtilsCallback();
446
447
// A layer can expose extensions, keep track of those extensions here.
448
struct LayerProperties {
449
VkLayerProperties properties;
450
std::vector<VkExtensionProperties> extensions;
451
};
452
453
bool CheckLayers(const std::vector<LayerProperties> &layer_props, const std::vector<const char *> &layer_names) const;
454
455
WindowSystem winsys_ = WINDOWSYSTEM_UNINITIALIZED;
456
457
// Don't use the real types here to avoid having to include platform-specific stuff
458
// that we really don't want in everything that uses VulkanContext.
459
void *winsysData1_ = nullptr;
460
void *winsysData2_ = nullptr;
461
std::function<VkExtent2D()> cbGetDrawSize_;
462
463
VkInstance instance_ = VK_NULL_HANDLE;
464
VkDevice device_ = VK_NULL_HANDLE;
465
VkQueue gfx_queue_ = VK_NULL_HANDLE;
466
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
467
u32 vulkanInstanceApiVersion_ = 0;
468
u32 vulkanDeviceApiVersion_ = 0;
469
470
std::string init_error_;
471
std::vector<const char *> instance_layer_names_;
472
std::vector<LayerProperties> instance_layer_properties_;
473
474
std::vector<const char *> instance_extensions_enabled_;
475
std::vector<VkExtensionProperties> instance_extension_properties_;
476
477
std::vector<const char *> device_layer_names_;
478
std::vector<LayerProperties> device_layer_properties_;
479
480
std::vector<const char *> device_extensions_enabled_;
481
std::vector<VkExtensionProperties> device_extension_properties_;
482
VulkanExtensions extensionsLookup_{};
483
484
std::vector<VkPhysicalDevice> physical_devices_;
485
486
int physical_device_ = -1;
487
488
uint32_t graphics_queue_family_index_ = -1;
489
std::vector<PhysicalDeviceProps> physicalDeviceProperties_;
490
std::vector<VkQueueFamilyProperties> queueFamilyProperties_;
491
492
VkPhysicalDeviceMemoryProperties memory_properties_{};
493
494
// Custom collection of things that are good to know
495
VulkanPhysicalDeviceInfo deviceInfo_{};
496
497
// Swap chain extent
498
VkExtent2D swapChainExtent_{};
499
500
VulkanContext::CreateInfo createInfo_{};
501
502
PerfClass devicePerfClass_ = PerfClass::SLOW;
503
504
int inflightFrames_ = MAX_INFLIGHT_FRAMES;
505
506
struct FrameData {
507
FrameData() {}
508
VulkanDeleteList deleteList;
509
VulkanProfiler profiler;
510
};
511
FrameData frame_[MAX_INFLIGHT_FRAMES];
512
int curFrame_ = 0;
513
514
// At the end of the frame, this is copied into the frame's delete list, so it can be processed
515
// the next time the frame comes around again.
516
VulkanDeleteList globalDeleteList_;
517
518
std::vector<VkDebugUtilsMessengerEXT> utils_callbacks;
519
520
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
521
VkFormat swapchainFormat_ = VK_FORMAT_UNDEFINED;
522
523
uint32_t queue_count = 0;
524
bool swapchainInited_ = false;
525
526
PhysicalDeviceFeatures deviceFeatures_;
527
528
VkSurfaceCapabilitiesKHR surfCapabilities_{};
529
std::vector<VkSurfaceFormatKHR> surfFormats_{};
530
531
VkPresentModeKHR presentMode_ = VK_PRESENT_MODE_FIFO_KHR;
532
std::vector<VkPresentModeKHR> availablePresentModes_;
533
534
std::vector<VkCommandBuffer> cmdQueue_;
535
536
VmaAllocator allocator_ = VK_NULL_HANDLE;
537
};
538
539
// GLSL compiler
540
void init_glslang();
541
void finalize_glslang();
542
543
enum class GLSLVariant {
544
VULKAN,
545
GL140,
546
GLES300,
547
};
548
549
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode, GLSLVariant variant, std::vector<uint32_t> &spirv, std::string *errorMessage);
550
551
const char *VulkanColorSpaceToString(VkColorSpaceKHR colorSpace);
552
const char *VulkanFormatToString(VkFormat format);
553
const char *VulkanPresentModeToString(VkPresentModeKHR presentMode);
554
const char *VulkanImageLayoutToString(VkImageLayout imageLayout);
555
556
std::string FormatDriverVersion(const VkPhysicalDeviceProperties &props);
557
std::string FormatAPIVersion(u32 version);
558
559
// Simple heuristic.
560
bool IsHashMaliDriverVersion(const VkPhysicalDeviceProperties &props);
561
562
extern VulkanLogOptions g_LogOptions;
563
564