context.cpp 20.7 KB
Newer Older
1
2
#include <SDL_vulkan.h> // has to be includes before vulkan
#include <sf2/sf2.hpp>  // has to be first so he sf2_struct define is set
3
4
5
6
7
8
9
10
11
12
13
14

#include <mirrage/graphic/context.hpp>
#include <mirrage/graphic/device.hpp>
#include <mirrage/graphic/window.hpp>

#include <mirrage/asset/asset_manager.hpp>
#include <mirrage/utils/log.hpp>
#include <mirrage/utils/template_utils.hpp>

#include <SDL2/SDL.h>
#include <gsl/gsl>

15
#include <cstdio>
16
17
18
19
20
#include <iostream>
#include <sstream>


extern "C" {
21
22
23
24
VkResult vkCreateDebugReportCallbackEXT(VkInstance                                instance,
                                        const VkDebugReportCallbackCreateInfoEXT* pCreateInfo,
                                        const VkAllocationCallbacks*              pAllocator,
                                        VkDebugReportCallbackEXT*                 pCallback) {
Florian Oetke's avatar
Florian Oetke committed
25
26
	auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(
	        instance, "vkCreateDebugReportCallbackEXT");
27
28
29
30
	if(func != nullptr) {
		return func(instance, pCreateInfo, pAllocator, pCallback);
	} else {
		return VK_ERROR_EXTENSION_NOT_PRESENT;
31
	}
32
}
33

34
35
36
37
38
39
40
void vkDestroyDebugReportCallbackEXT(VkInstance                   instance,
                                     VkDebugReportCallbackEXT     callback,
                                     const VkAllocationCallbacks* pAllocator) {
	auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(
	        instance, "vkDestroyDebugReportCallbackEXT");
	if(func != nullptr) {
		func(instance, callback, pAllocator);
41
42
	}
}
43
}
44

45
namespace mirrage::graphic {
46
47
48
49
50

	using namespace util::unit_literals;

	namespace {
		void sdl_error_check() {
51
			const char* err = SDL_GetError();
52
53
54
			if(*err != '\0') {
				std::string errorStr(err);
				SDL_ClearError();
55
				MIRRAGE_FAIL("SDL: " << errorStr);
56
57
58
59
			}
		}

		void add_present_extensions(std::vector<const char*>& extensions) {
60
			auto present_extensions      = std::vector<const char*>(4);
61
			auto present_extensions_size = static_cast<unsigned int>(present_extensions.size());
Florian Oetke's avatar
Florian Oetke committed
62
63
			if(!SDL_GetVulkanInstanceExtensions(&present_extensions_size,
			                                    present_extensions.data())) {
64
				MIRRAGE_FAIL("Unable to determine present extensions: " << SDL_GetError());
65
66
			}

67
68
69
70
			if(present_extensions_size > 0) {
				extensions.insert(extensions.end(),
				                  present_extensions.begin(),
				                  present_extensions.begin() + present_extensions_size);
71
72
73
74
			}
		}

		auto check_extensions(const std::vector<const char*>& required,
Florian Oetke's avatar
Florian Oetke committed
75
76
		                      const std::vector<const char*>& optional)
		        -> std::vector<const char*> {
77
78
79
80
81
82
83
84
			auto extensions = std::vector<const char*>();
			extensions.reserve(required.size() + optional.size());

			auto supported_extensions = vk::enumerateInstanceExtensionProperties();

			auto support_confirmed = std::vector<bool>(required.size());

			for(auto& e : supported_extensions) {
85
				for(auto i = 0u; i < required.size(); i++) {
86
87
88
89
90
91
92
					if(!strcmp(e.extensionName, required[i])) {
						support_confirmed[i] = true;
						extensions.push_back(required[i]);
						break;
					}
				}

93
				for(auto i = 0u; i < optional.size(); i++) {
94
95
96
97
98
99
100
101
					if(!strcmp(e.extensionName, optional[i])) {
						extensions.push_back(optional[i]);
						break;
					}
				}
			}

			bool all_supported = true;
102
			for(auto i = 0u; i < support_confirmed.size(); i++) {
103
				if(!support_confirmed[i]) {
104
					MIRRAGE_WARN("Unsupported extension \"" << required[i] << "\"!");
105
106
107
108
109
					all_supported = false;
				}
			}

			if(!all_supported) {
Florian Oetke's avatar
Florian Oetke committed
110
111
				MIRRAGE_FAIL(
				        "At least one required extension is not supported (see log for details)!");
112
113
114
115
116
117
118
119
120
121
122
123
124
			}

			return extensions;
		}

		auto check_layers(const std::vector<const char*>& requested) -> std::vector<const char*> {
			auto validation_layers = std::vector<const char*>();
			validation_layers.reserve(requested.size());

			auto supported_layers = vk::enumerateInstanceLayerProperties();
			for(auto& l : supported_layers) {
				bool layer_requested = false;
				for(auto& req_layer : requested) {
125
					if(0 == strcmp(l.layerName, req_layer)) {
126
127
128
129
130
131
132
						validation_layers.push_back(req_layer);
						layer_requested = true;
						break;
					}
				}

				if(!layer_requested) {
Florian Oetke's avatar
Florian Oetke committed
133
134
135
					MIRRAGE_DEBUG(
					        "Additional validation layer is available, that hasn't been requested: "
					        << l.layerName);
136
137
138
139
				}
			}

			if(validation_layers.size() != requested.size()) {
140
				auto log = util::error(__func__, __FILE__, __LINE__);
141
142
				log << "Some requested validation layers are not supported: \n";
				for(auto& l : validation_layers) {
143
					log << "  - " << l << "\n";
144
145
146
147
148
149
150
				}
				log << std::endl;
			}

			return validation_layers;
		}

151
152
153
154
155
156
157
158
		VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT      flags,
		                                             VkDebugReportObjectTypeEXT objType,
		                                             uint64_t                   obj,
		                                             size_t                     location,
		                                             int32_t                    code,
		                                             const char*                layerPrefix,
		                                             const char*                msg,
		                                             void*                      userData) {
159
160
161
162

			// silences: DescriptorSet 0x3f previously bound as set #1 is incompatible with set
			//             0x1cc99c0 newly bound as set #1 so set #2 and any subsequent sets were
			//             disturbed by newly bound pipelineLayout (0x4f)
163
			if(objType == VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT)
164
165
166
167
				return VK_FALSE;


			if(flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
168
				MIRRAGE_ERROR("[VK | " << layerPrefix << "] " << msg);
169
170
			} else if(flags & VK_DEBUG_REPORT_WARNING_BIT_EXT
			          || flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
171
				MIRRAGE_WARN("[VK | " << layerPrefix << "] " << msg);
172
			} else {
173
				MIRRAGE_INFO("[VK | " << layerPrefix << "] " << msg);
174
175
176
177
			}

			return VK_FALSE;
		}
178
	} // namespace
179
180


181
182
183
184
185
186
187
	Context::Context(const std::string&    appName,
	                 uint32_t              appVersion,
	                 const std::string&    engineName,
	                 uint32_t              engineVersion,
	                 bool                  debug,
	                 asset::Asset_manager& assets)
	  : _assets(assets), _name(appName) {
188
189
190
191

		auto maybe_settings = assets.load_maybe<Graphics_settings>("cfg:graphics"_aid);
		if(maybe_settings.is_nothing()) {
			if(!settings(default_settings())) {
192
				MIRRAGE_FAIL("Invalid graphics settings");
193
194
195
196
197
198
			}
		} else {
			_settings = maybe_settings.get_or_throw();
		}

		if(!settings(*_settings)) { //< apply actual size/settings
199
			MIRRAGE_FAIL("Couldn't apply graphics settings");
200
201
202
203
204
		}

		sdl_error_check();

		auto instanceCreateInfo = vk::InstanceCreateInfo{};
205
		auto appInfo            = vk::ApplicationInfo{
206
                appName.c_str(), appVersion, engineName.c_str(), engineVersion, VK_API_VERSION_1_0};
207

208
		auto required_extensions = std::vector<const char*>{VK_KHR_SURFACE_EXTENSION_NAME};
209
		add_present_extensions(required_extensions);
210
		auto optional_extensions = std::vector<const char*>{};
211
212
213


		if(debug) {
214
			_enabled_layers = check_layers({"VK_LAYER_LUNARG_standard_validation"});
215
216
217
218
219
220

			required_extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
		}

		auto extensions = check_extensions(required_extensions, optional_extensions);

221
		auto log = util::info(__func__, __FILE__, __LINE__);
222
223
224
		log << "Initializing vulkan...\n";
		log << "Enabled extensions:\n";
		for(auto e : extensions) {
225
			log << "  - " << e << "\n";
226
227
228
		}
		log << "Enabled validation layers:\n";
		for(auto l : _enabled_layers) {
229
			log << "  - " << l << "\n";
230
231
232
233
234
235
236
237
238
239
240
		}
		log << std::endl;

		instanceCreateInfo.setPApplicationInfo(&appInfo);
		instanceCreateInfo.setEnabledExtensionCount(gsl::narrow<uint32_t>(extensions.size()));
		instanceCreateInfo.setPpEnabledExtensionNames(extensions.data());
		instanceCreateInfo.setEnabledLayerCount(gsl::narrow<uint32_t>(_enabled_layers.size()));
		instanceCreateInfo.setPpEnabledLayerNames(_enabled_layers.data());
		_instance = vk::createInstanceUnique(instanceCreateInfo);

		if(debug) {
241
242
			_debug_callback = _instance->createDebugReportCallbackEXTUnique(
			        {/*	vk::DebugReportFlagBitsEXT::eDebug | vk::DebugReportFlagBitsEXT::eInformation | */
Florian Oetke's avatar
Florian Oetke committed
243
244
			         vk::DebugReportFlagBitsEXT::eError
			                 | vk::DebugReportFlagBitsEXT::ePerformanceWarning
245
246
			                 | vk::DebugReportFlagBitsEXT::eWarning,
			         debugCallback});
247
248
249
250
251
252
253
254
255
		}
	}
	Context::~Context() = default;

	bool Context::settings(Graphics_settings new_settings) {
		// TODO: update windows
		/*
		for(auto device : children()) {
			if(!device->settings_changed(new_settings)) {
256
				MIRRAGE_WARN("New graphics settings are not supported");
257
258
259
260
261
262
263
264
265
266
267
				return false;
			}
		}*/

		_assets.save<Graphics_settings>("cfg:graphics"_aid, new_settings);
		_settings = _assets.load<Graphics_settings>("cfg:graphics"_aid);

		return true;
	}

	auto Context::create_window(std::string name, int width, int height) -> Window_ptr {
268
269
270
271
272
273
274
275
276
		auto settings = _find_window_settings(name, width, height);

		return std::make_unique<Window>(*this,
		                                name,
		                                app_name() + " | " + name,
		                                settings.display,
		                                settings.width,
		                                settings.height,
		                                settings.fullscreen);
277
	}
Florian Oetke's avatar
Florian Oetke committed
278
279
	auto Context::_find_window_settings(const std::string& name, int width, int height)
	        -> Window_settings {
280
281
		auto& cfg = settings();

Florian Oetke's avatar
Florian Oetke committed
282
283
284
		auto win_cfg = std::find_if(std::begin(cfg.windows), std::end(cfg.windows), [&](auto& w) {
			return w.first == name;
		});
285

286
		if(win_cfg == std::end(cfg.windows)) { // no config create new
287
288
			auto new_settings = cfg;
			auto win_settings = default_window_settings(0);
289
290
291
			if(width > 0)
				win_settings.width = width;
			if(height > 0)
292
				win_settings.height = height;
293
294
295
296
297
298
299
300
301
302
303
304
305
			new_settings.windows[name] = win_settings;

			settings(new_settings);

			return win_settings;
		}

		return win_cfg->second;
	}

	namespace {
		bool supports_present(vk::PhysicalDevice& gpu, const std::vector<Window*>& can_present_to) {
			auto supported_extensions = gpu.enumerateDeviceExtensionProperties();
Florian Oetke's avatar
Florian Oetke committed
306
307
308
309
			auto sc_ext               = std::find_if(
                    supported_extensions.begin(), supported_extensions.end(), [](auto& e) {
                        return std::strcmp(e.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0;
                    });
310

311
			if(sc_ext == supported_extensions.end()) {
312
313
314
315
316
317
318
319
320
321
322
323
324
				return false;
			}

			for(auto window : can_present_to) {
				auto formats       = gpu.getSurfaceFormatsKHR(window->surface());
				auto present_modes = gpu.getSurfacePresentModesKHR(window->surface());

				if(formats.empty() || present_modes.empty())
					return false;
			}

			return true;
		}
325

Florian Oetke's avatar
Florian Oetke committed
326
327
		auto find_graphics_queue(vk::PhysicalDevice&         gpu,
		                         const std::vector<Window*>& can_present_to)
328
329
		        -> util::maybe<std::uint32_t> {
			auto i = 0;
330
331

			for(auto& queue_family : gpu.getQueueFamilyProperties()) {
332
333
				auto can_present =
				        can_present_to.empty()
Florian Oetke's avatar
Florian Oetke committed
334
335
336
337
				        || std::all_of(
				                   can_present_to.begin(), can_present_to.end(), [&](auto window) {
					                   return gpu.getSurfaceSupportKHR(i, window->surface());
				                   });
338
339
340
341

				if(queue_family.queueCount > 0 && can_present
				   && queue_family.timestampValidBits
				              >= 32 // only accept queues with >=32bit timer support, for now
342
343
344
345
346
347
348
349
350
351
352
353
				   && (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
					return i;
				}

				i++;
			}

			return util::nothing;
		}

		auto find_transfer_queue(vk::PhysicalDevice& gpu) -> std::uint32_t {
			auto families = gpu.getQueueFamilyProperties();
354

355
			auto i = 0;
356

357
358
			// check for transfer-only queue
			for(auto& queue_family : families) {
Florian Oetke's avatar
Florian Oetke committed
359
360
				if(queue_family.queueCount > 0
				   && (queue_family.queueFlags & vk::QueueFlagBits::eTransfer)
361
				   && !(queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
362
363
364
365
366
					return i;
				}

				i++;
			}
367
368

			i = 0;
369
			for(auto& queue_family : families) {
370
				if(queue_family.queueCount > 0
371
				   && ((queue_family.queueFlags & vk::QueueFlagBits::eTransfer)
372
373
				       || (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)
				       || (queue_family.queueFlags & vk::QueueFlagBits::eCompute))) {
374
375
376
377
378
					return i;
				}

				i++;
			}
379

380
			MIRRAGE_FAIL("No queue found, that supports transfer operations!");
381
382
		}

383
384
385
386
		auto find_surface_format(vk::PhysicalDevice& gpu,
		                         Window&             window,
		                         vk::Format          target_format,
		                         vk::ColorSpaceKHR   target_space) -> vk::SurfaceFormatKHR {
387
388

			auto formats = gpu.getSurfaceFormatsKHR(window.surface());
389
			if(formats.size() == 1 && formats.front().format == vk::Format::eUndefined) {
390
391
392
393
				return {target_format, target_space};
			}

			auto opt_found = std::find_if(formats.begin(), formats.end(), [&](auto& f) {
394
				return f.format == target_format && f.colorSpace == target_space;
395
			});
396
			if(opt_found != formats.end()) {
397
398
399
				return {target_format, target_space};
			}

Florian Oetke's avatar
Florian Oetke committed
400
401
402
			opt_found = std::find_if(formats.begin(), formats.end(), [&](auto& f) {
				return f.format == target_format;
			});
403
			if(opt_found != formats.end()) {
404
405
406
407
				return *opt_found;
			}

			if(!formats.empty()) {
408
				MIRRAGE_WARN(
Florian Oetke's avatar
Florian Oetke committed
409
410
				        "Requested format is not supported by the device, fallback to first "
				        "reported "
411
				        "format");
412
413
				return formats.front();
			} else {
414
				MIRRAGE_FAIL("The device doesn't support any surface formats!");
415
416
417
			}
		}

418
419
		auto create_swapchain_create_info(vk::PhysicalDevice          gpu,
		                                  bool                        srgb,
420
421
		                                  const std::vector<Window*>& can_present_to) {
			auto swapchains = Swapchain_create_infos();
422

423
424
			for(auto window : can_present_to) {
				auto capabilities = gpu.getSurfaceCapabilitiesKHR(window->surface());
425
426


427
428
429
430
431
432
433
434
				auto sc_info = vk::SwapchainCreateInfoKHR{};
				sc_info.setSurface(window->surface());
				sc_info.setClipped(true);
				sc_info.setPreTransform(capabilities.currentTransform);
				sc_info.setImageSharingMode(vk::SharingMode::eExclusive);
				sc_info.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment);
				sc_info.setImageArrayLayers(1);
				sc_info.setMinImageCount(std::max(2u, capabilities.minImageCount));
Florian Oetke's avatar
Florian Oetke committed
435
436
				if(capabilities.maxImageCount > 0
				   && capabilities.maxImageCount < sc_info.minImageCount) {
437
438
					sc_info.setMinImageCount(capabilities.maxImageCount);
				}
439

Florian Oetke's avatar
Florian Oetke committed
440
441
442
443
444
				auto present_modes     = gpu.getSurfacePresentModesKHR(window->surface());
				auto mailbox_supported = std::find(present_modes.begin(),
				                                   present_modes.end(),
				                                   vk::PresentModeKHR::eMailbox)
				                         != present_modes.end();
445
446
447
448
449
				if(mailbox_supported) {
					sc_info.setPresentMode(vk::PresentModeKHR::eMailbox);
				} else {
					sc_info.setPresentMode(vk::PresentModeKHR::eFifo);
				}
450

451
				if(capabilities.currentExtent.width == std::numeric_limits<uint32_t>::max()) {
452
453
454
455
456
457
458
459
460
					capabilities.currentExtent.width =
					        std::min(std::max(gsl::narrow<std::uint32_t>(window->width()),
					                          capabilities.minImageExtent.width),
					                 capabilities.maxImageExtent.width);

					capabilities.currentExtent.height =
					        std::min(std::max(gsl::narrow<std::uint32_t>(window->height()),
					                          capabilities.minImageExtent.height),
					                 capabilities.maxImageExtent.height);
461
				}
462

463
				sc_info.setImageExtent(capabilities.currentExtent);
464

Florian Oetke's avatar
Florian Oetke committed
465
466
467
468
469
				auto format = find_surface_format(gpu,
				                                  *window,
				                                  srgb ? vk::Format::eB8G8R8A8Srgb
				                                       : vk::Format::eB8G8R8A8Unorm,
				                                  vk::ColorSpaceKHR::eSrgbNonlinear);
470

471
472
				sc_info.setImageFormat(format.format);
				sc_info.setImageColorSpace(format.colorSpace);
473

474
475
				swapchains.emplace(window->name(), std::make_tuple(window, sc_info));
			}
476

477
478
			return swapchains;
		}
479
	} // namespace
480
481
482
483
	auto Context::instantiate_device(Device_selector             selector,
	                                 Device_factory              factory,
	                                 const std::vector<Window*>& can_present_to,
	                                 bool                        srgb) -> Device_ptr {
484
485
486
487
488
489
490
		auto top_score = std::numeric_limits<int>::min();
		auto top_gpu   = vk::PhysicalDevice{};

		for(auto& gpu : _instance->enumeratePhysicalDevices()) {
			if(!can_present_to.empty() && !supports_present(gpu, can_present_to)) {
				continue;
			}
491

492
493
494
			auto graphics_queue = find_graphics_queue(gpu, can_present_to);

			auto score = selector(gpu, graphics_queue);
Florian Oetke's avatar
Florian Oetke committed
495
496
497
498
499
500
501
502

			auto gpu_name = std::string(gpu.getProperties().deviceName);
			MIRRAGE_INFO("Detected GPU: " << gpu_name);

			if(!_settings->gpu_preference.empty() && _settings->gpu_preference == gpu_name) {
				score = std::numeric_limits<int>::max();
			}

503
			if(score > top_score) {
504
				top_score = score;
505
				top_gpu   = gpu;
506
507
508
509
			}
		}

		if(!top_gpu) {
510
			MIRRAGE_FAIL("Couldn't find a GPU that supports vulkan and all required features.");
511
512
		}

Florian Oetke's avatar
Florian Oetke committed
513
514
		MIRRAGE_INFO("Selected GPU: " << top_gpu.getProperties().deviceName);

515
516
517
518
		auto cfg = vk::DeviceCreateInfo{};
		cfg.setEnabledLayerCount(gsl::narrow<uint32_t>(_enabled_layers.size()));
		cfg.setPpEnabledLayerNames(_enabled_layers.data());

519
		auto extensions           = std::vector<const char*>();
520
		auto supported_extensions = top_gpu.enumerateDeviceExtensionProperties();
521
		auto extension_supported  = [&](const char* e) {
522
            return supported_extensions.end()
Florian Oetke's avatar
Florian Oetke committed
523
524
525
                   != std::find_if(supported_extensions.begin(),
                                   supported_extensions.end(),
                                   [&](auto& se) { return !strcmp(se.extensionName, e); });
526
		};
527

528
		auto dedicated_alloc_supported = false;
529

530
531
532
533
534
535
#ifdef VK_NV_dedicated_allocation
		if(extension_supported(VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME)) {
			extensions.emplace_back(VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME);
			dedicated_alloc_supported = true;
		}
#endif
536

537
538
539
		if(!can_present_to.empty()) {
			extensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
		}
540

541
542
		cfg.setEnabledExtensionCount(extensions.size());
		cfg.setPpEnabledExtensionNames(extensions.data());
543

544
545
546
		auto create_info = factory(top_gpu, find_graphics_queue(top_gpu, can_present_to));

		cfg.setPEnabledFeatures(&create_info.features);
547

548
		// familyId => (count, priorities)
549
550
551
		auto queue_families =
		        std::unordered_map<std::uint32_t, std::tuple<std::uint32_t, std::vector<float>>>();

552
		auto available_families = top_gpu.getQueueFamilyProperties();
553

554
555
556
		auto queue_mapping = Queue_family_mapping();

		auto transfer_queue_tag = "_transfer"_strid;
557
558
		create_info.queue_families.emplace(transfer_queue_tag,
		                                   Queue_create_info{find_transfer_queue(top_gpu), 0.2f});
559
560

		for(auto& qf : create_info.queue_families) {
561
562
			auto tag      = qf.first;
			auto family   = qf.second.family_id;
563
			auto priority = qf.second.priority;
564

565
			auto& entry = queue_families[family];
566

567
568
569
570
571
			if(available_families[family].queueCount > 0) {
				available_families[family].queueCount--;
				std::get<0>(entry) += 1;
				std::get<1>(entry).emplace_back(priority);
			} else {
572
573
				MIRRAGE_WARN("More queues requested than are availbalbe from family "
				             << family << ". Collapsed with previous queue!");
574
575
			}

576
			queue_mapping.emplace(tag, std::make_tuple(family, std::get<1>(entry).size() - 1));
577
578
579
580
581
		}

		auto used_queues = std::vector<vk::DeviceQueueCreateInfo>{};
		used_queues.reserve(queue_families.size());
		for(auto& qf : queue_families) {
Florian Oetke's avatar
Florian Oetke committed
582
583
			MIRRAGE_INVARIANT(std::get<1>(qf.second).size() == std::get<0>(qf.second),
			                  "Size mismatch");
584

585
586
587
588
589
			used_queues.emplace_back(vk::DeviceQueueCreateFlags(),
			                         qf.first,
			                         std::get<0>(qf.second),
			                         std::get<1>(qf.second).data());
		}
590

591
592
		cfg.setQueueCreateInfoCount(gsl::narrow<std::uint32_t>(used_queues.size()));
		cfg.setPQueueCreateInfos(used_queues.data());
593

594
595
		auto swapchains = create_swapchain_create_info(top_gpu, srgb, can_present_to);

596
597
598
599
600
601
602
603
604
		return std::make_unique<Device>(*this,
		                                _assets,
		                                top_gpu.createDeviceUnique(cfg),
		                                top_gpu,
		                                transfer_queue_tag,
		                                std::move(queue_mapping),
		                                std::move(swapchains),
		                                dedicated_alloc_supported);
	}
605
} // namespace mirrage::graphic