1/*
2 * Copyright (c) 2021-2023 The Khronos Group Inc.
3 * Copyright (c) 2021-2023 Valve Corporation
4 * Copyright (c) 2021-2023 LunarG, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and/or associated documentation files (the "Materials"), to
8 * deal in the Materials without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Materials, and to permit persons to whom the Materials are
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice(s) and this permission notice shall be included in
14 * all copies or substantial portions of the Materials.
15 *
16 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 *
20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
23 * USE OR OTHER DEALINGS IN THE MATERIALS.
24 *
25 * Author: Charles Giessen <charles@lunarg.com>
26 */
27
28#include "test_util.h"
29
30#if defined(WIN32)
31#include <strsafe.h>
32const char* win_api_error_str(LSTATUS status) {
33    if (status == ERROR_INVALID_FUNCTION) return "ERROR_INVALID_FUNCTION";
34    if (status == ERROR_FILE_NOT_FOUND) return "ERROR_FILE_NOT_FOUND";
35    if (status == ERROR_PATH_NOT_FOUND) return "ERROR_PATH_NOT_FOUND";
36    if (status == ERROR_TOO_MANY_OPEN_FILES) return "ERROR_TOO_MANY_OPEN_FILES";
37    if (status == ERROR_ACCESS_DENIED) return "ERROR_ACCESS_DENIED";
38    if (status == ERROR_INVALID_HANDLE) return "ERROR_INVALID_HANDLE";
39    if (status == ERROR_ENVVAR_NOT_FOUND) return "ERROR_ENVVAR_NOT_FOUND";
40    if (status == ERROR_SETENV_FAILED) return "ERROR_SETENV_FAILED";
41    return "UNKNOWN ERROR";
42}
43
44void print_error_message(LSTATUS status, const char* function_name, std::string optional_message) {
45    LPVOID lpMsgBuf;
46    DWORD dw = GetLastError();
47
48    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw,
49                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, nullptr);
50
51    std::cerr << function_name << " failed with " << win_api_error_str(status) << ": "
52              << std::string(reinterpret_cast<LPTSTR>(lpMsgBuf));
53    if (optional_message != "") {
54        std::cerr << " | " << optional_message;
55    }
56    std::cerr << "\n";
57    LocalFree(lpMsgBuf);
58}
59
60void EnvVarWrapper::set_env_var() {
61    BOOL ret = SetEnvironmentVariableW(widen(name).c_str(), widen(cur_value).c_str());
62    if (ret == 0) {
63        print_error_message(ERROR_SETENV_FAILED, "SetEnvironmentVariableW");
64    }
65}
66void EnvVarWrapper::remove_env_var() const { SetEnvironmentVariableW(widen(name).c_str(), nullptr); }
67std::string get_env_var(std::string const& name, bool report_failure) {
68    std::wstring name_utf16 = widen(name);
69    DWORD value_size = GetEnvironmentVariableW(name_utf16.c_str(), nullptr, 0);
70    if (0 == value_size) {
71        if (report_failure) print_error_message(ERROR_ENVVAR_NOT_FOUND, "GetEnvironmentVariableW");
72        return {};
73    }
74    std::wstring value(value_size, L'\0');
75    if (GetEnvironmentVariableW(name_utf16.c_str(), &value[0], value_size) != value_size - 1) {
76        return {};
77    }
78    return narrow(value);
79}
80#elif COMMON_UNIX_PLATFORMS
81
82void EnvVarWrapper::set_env_var() { setenv(name.c_str(), cur_value.c_str(), 1); }
83void EnvVarWrapper::remove_env_var() const { unsetenv(name.c_str()); }
84std::string get_env_var(std::string const& name, bool report_failure) {
85    char* ret = getenv(name.c_str());
86    if (ret == nullptr) {
87        if (report_failure) std::cerr << "Failed to get environment variable:" << name << "\n";
88        return std::string();
89    }
90    return ret;
91}
92#endif
93
94template <typename T>
95void print_object_of_t(JsonWriter& writer, const char* object_name, std::vector<T> const& vec) {
96    if (vec.size() == 0) return;
97    writer.StartKeyedObject(object_name);
98    for (auto& element : vec) {
99        element.get_manifest_str(writer);
100    }
101    writer.EndObject();
102}
103
104template <typename T>
105void print_array_of_t(JsonWriter& writer, const char* object_name, std::vector<T> const& vec) {
106    if (vec.size() == 0) return;
107    writer.StartKeyedArray(object_name);
108    for (auto& element : vec) {
109        element.get_manifest_str(writer);
110    }
111    writer.EndArray();
112}
113void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector<std::string> const& strings) {
114    if (strings.size() == 0) return;
115    writer.StartKeyedArray(object_name);
116    for (auto& str : strings) {
117        writer.AddString(fs::fixup_backslashes_in_path(str));
118    }
119    writer.EndArray();
120}
121
122std::string to_text(bool b) { return b ? std::string("true") : std::string("false"); }
123
124std::string ManifestICD::get_manifest_str() const {
125    JsonWriter writer;
126    writer.StartObject();
127    writer.AddKeyedString("file_format_version", file_format_version.get_version_str());
128    writer.StartKeyedObject("ICD");
129    writer.AddKeyedString("library_path", fs::fixup_backslashes_in_path(lib_path).str());
130    writer.AddKeyedString("api_version", version_to_string(api_version));
131    writer.AddKeyedBool("is_portability_driver", is_portability_driver);
132    if (!library_arch.empty()) writer.AddKeyedString("library_arch", library_arch);
133    writer.EndObject();
134    writer.EndObject();
135    return writer.output;
136}
137
138void ManifestLayer::LayerDescription::Extension::get_manifest_str(JsonWriter& writer) const {
139    writer.StartObject();
140    writer.AddKeyedString("name", name);
141    writer.AddKeyedString("spec_version", std::to_string(spec_version));
142    writer.AddKeyedString("spec_version", std::to_string(spec_version));
143    print_vector_of_strings(writer, "entrypoints", entrypoints);
144    writer.EndObject();
145}
146
147void ManifestLayer::LayerDescription::get_manifest_str(JsonWriter& writer) const {
148    writer.AddKeyedString("name", name);
149    writer.AddKeyedString("type", get_type_str(type));
150    if (!lib_path.str().empty()) {
151        writer.AddKeyedString("library_path", fs::fixup_backslashes_in_path(lib_path.str()));
152    }
153    writer.AddKeyedString("api_version", version_to_string(api_version));
154    writer.AddKeyedString("implementation_version", std::to_string(implementation_version));
155    writer.AddKeyedString("description", description);
156    print_object_of_t(writer, "functions", functions);
157    print_array_of_t(writer, "instance_extensions", instance_extensions);
158    print_array_of_t(writer, "device_extensions", device_extensions);
159    if (!enable_environment.empty()) {
160        writer.StartKeyedObject("enable_environment");
161        writer.AddKeyedString(enable_environment, "1");
162        writer.EndObject();
163    }
164    if (!disable_environment.empty()) {
165        writer.StartKeyedObject("disable_environment");
166        writer.AddKeyedString(disable_environment, "1");
167        writer.EndObject();
168    }
169    print_vector_of_strings(writer, "component_layers", component_layers);
170    print_vector_of_strings(writer, "blacklisted_layers", blacklisted_layers);
171    print_vector_of_strings(writer, "override_paths", override_paths);
172    print_vector_of_strings(writer, "app_keys", app_keys);
173    print_object_of_t(writer, "pre_instance_functions", pre_instance_functions);
174    if (!library_arch.empty()) {
175        writer.AddKeyedString("library_arch", library_arch);
176    }
177}
178
179VkLayerProperties ManifestLayer::LayerDescription::get_layer_properties() const {
180    VkLayerProperties properties{};
181    copy_string_to_char_array(name, properties.layerName, VK_MAX_EXTENSION_NAME_SIZE);
182    copy_string_to_char_array(description, properties.description, VK_MAX_EXTENSION_NAME_SIZE);
183    properties.implementationVersion = implementation_version;
184    properties.specVersion = api_version;
185    return properties;
186}
187
188std::string ManifestLayer::get_manifest_str() const {
189    JsonWriter writer;
190    writer.StartObject();
191    writer.AddKeyedString("file_format_version", file_format_version.get_version_str());
192    if (layers.size() == 1) {
193        writer.StartKeyedObject("layer");
194        layers.at(0).get_manifest_str(writer);
195        writer.EndObject();
196    } else {
197        writer.StartKeyedArray("layers");
198        for (size_t i = 0; i < layers.size(); i++) {
199            writer.StartObject();
200            layers.at(i).get_manifest_str(writer);
201            writer.EndObject();
202        }
203        writer.EndArray();
204    }
205    writer.EndObject();
206    return writer.output;
207}
208
209namespace fs {
210std::string make_native(std::string const& in_path) {
211    std::string out;
212#if defined(WIN32)
213    for (auto& c : in_path) {
214        if (c == '/')
215            out += "\\";
216        else
217            out += c;
218    }
219#elif COMMON_UNIX_PLATFORMS
220    for (size_t i = 0; i < in_path.size(); i++) {
221        if (i + 1 < in_path.size() && in_path[i] == '\\' && in_path[i + 1] == '\\') {
222            out += '/';
223            i++;
224        } else
225            out += in_path[i];
226    }
227#endif
228    return out;
229}
230
231// Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings
232std::string fixup_backslashes_in_path(std::string const& in_path) {
233    std::string out;
234    for (auto& c : in_path) {
235        if (c == '\\')
236            out += "\\\\";
237        else
238            out += c;
239    }
240    return out;
241}
242fs::path fixup_backslashes_in_path(fs::path const& in_path) { return fixup_backslashes_in_path(in_path.str()); }
243
244path& path::operator+=(path const& in) {
245    contents += in.contents;
246    return *this;
247}
248path& path::operator+=(std::string const& in) {
249    contents += in;
250    return *this;
251}
252path& path::operator+=(const char* in) {
253    contents += std::string{in};
254    return *this;
255}
256path& path::operator/=(path const& in) {
257    if (contents.back() != path_separator && in.contents.front() != path_separator) contents += path_separator;
258    contents += in.contents;
259    return *this;
260}
261path& path::operator/=(std::string const& in) {
262    if (contents.back() != path_separator && in.front() != path_separator) contents += path_separator;
263    contents += in;
264    return *this;
265}
266path& path::operator/=(const char* in) {
267    std::string in_str{in};
268    if (contents.back() != path_separator && in_str.front() != path_separator) contents += path_separator;
269    contents += in_str;
270    return *this;
271}
272path path::operator+(path const& in) const {
273    path new_path = contents;
274    new_path += in;
275    return new_path;
276}
277path path::operator+(std::string const& in) const {
278    path new_path = contents;
279    new_path += in;
280    return new_path;
281}
282path path::operator+(const char* in) const {
283    path new_path(contents);
284    new_path += in;
285    return new_path;
286}
287
288path path::operator/(path const& in) const {
289    path new_path = contents;
290    new_path /= in;
291    return new_path;
292}
293path path::operator/(std::string const& in) const {
294    path new_path = contents;
295    new_path /= in;
296    return new_path;
297}
298path path::operator/(const char* in) const {
299    path new_path(contents);
300    new_path /= in;
301    return new_path;
302}
303
304path path::parent_path() const {
305    auto last_div = contents.rfind(path_separator);
306    if (last_div == std::string::npos) return "";
307    return path(contents.substr(0, last_div));
308}
309bool path::has_parent_path() const {
310    auto last_div = contents.rfind(path_separator);
311    return last_div != std::string::npos;
312}
313path path::filename() const {
314    auto last_div = contents.rfind(path_separator);
315    return path(contents.substr(last_div + 1, contents.size() - last_div + 1));
316}
317
318path path::extension() const {
319    auto last_div = contents.rfind(path_separator);
320    auto ext_div = contents.rfind('.');
321    // Make sure to not get the special `.` and `..`, as well as any filename that being with a dot, like .profile
322    if (last_div + 1 == ext_div || (last_div + 2 == ext_div && contents[last_div + 1] == '.')) return path("");
323    path temp = path(contents.substr(ext_div, contents.size() - ext_div + 1));
324
325    return path(contents.substr(ext_div, contents.size() - ext_div + 1));
326}
327
328path path::stem() const {
329    auto last_div = contents.rfind(path_separator);
330    auto ext_div = contents.rfind('.');
331    if (last_div + 1 == ext_div || (last_div + 2 == ext_div && contents[last_div + 1] == '.')) {
332        return path(contents.substr(last_div + 1, contents.size() - last_div + 1));
333    }
334    return path(contents.substr(last_div + 1, ext_div - last_div - 1));
335}
336
337path& path::replace_filename(path const& replacement) {
338    *this = parent_path() / replacement.str();
339    return *this;
340}
341
342// internal implementation helper for per-platform creating & destroying folders
343int create_folder(path const& path) {
344#if defined(WIN32)
345    return _wmkdir(widen(path.str()).c_str());
346#else
347    mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
348    return 0;
349#endif
350}
351
352int delete_folder_contents(path const& folder) {
353#if defined(WIN32)
354    std::wstring folder_utf16 = widen(folder.str());
355    if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(folder_utf16.c_str()) && GetLastError() == ERROR_FILE_NOT_FOUND) {
356        // nothing to delete
357        return 0;
358    }
359    std::wstring search_path = folder_utf16 + L"/*.*";
360    std::string s_p = folder.str() + "/";
361    WIN32_FIND_DATAW fd;
362    HANDLE hFind = ::FindFirstFileW(search_path.c_str(), &fd);
363    if (hFind != INVALID_HANDLE_VALUE) {
364        do {
365            std::string file_name_utf8 = narrow(fd.cFileName);
366            if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
367                if (!string_eq(file_name_utf8.c_str(), ".") && !string_eq(file_name_utf8.c_str(), "..")) {
368                    delete_folder(s_p + file_name_utf8);
369                }
370            } else {
371                std::string child_name = s_p + file_name_utf8;
372                DeleteFileW(widen(child_name).c_str());
373            }
374        } while (::FindNextFileW(hFind, &fd));
375        ::FindClose(hFind);
376    }
377    return 0;
378#else
379    DIR* dir = opendir(folder.c_str());
380    if (!dir) {
381        return 0;
382    }
383    int ret = 0;
384    dirent* file;
385    while (!ret && (file = readdir(dir))) {
386        int ret2 = -1;
387
388        /* Skip the names "." and ".." as we don't want to recurse on them. */
389        if (string_eq(file->d_name, ".") || string_eq(file->d_name, "..")) continue;
390
391        path file_path = folder / file->d_name;
392        struct stat statbuf;
393        if (!stat(file_path.c_str(), &statbuf)) {
394            if (S_ISDIR(statbuf.st_mode))
395                ret2 = delete_folder(file_path);
396            else
397                ret2 = unlink(file_path.c_str());
398        }
399
400        ret = ret2;
401    }
402    closedir(dir);
403    return ret;
404#endif
405}
406
407int delete_folder(path const& folder) {
408    int ret = delete_folder_contents(folder);
409    if (ret != 0) return ret;
410#if defined(WIN32)
411    _wrmdir(widen(folder.str()).c_str());
412    return 0;
413#else
414    return rmdir(folder.c_str());
415#endif
416}
417
418#if defined(WIN32)
419std::wstring native_path(const std::string& utf8) { return widen(utf8); }
420#else
421const std::string& native_path(const std::string& utf8) { return utf8; }
422#endif
423
424FolderManager::FolderManager(path root_path, std::string name) noexcept : folder(root_path / name) {
425    delete_folder_contents(folder);
426    create_folder(folder);
427}
428FolderManager::~FolderManager() noexcept {
429    if (folder.str().empty()) return;
430    auto list_of_files_to_delete = files;
431    // remove(file) modifies the files variable, copy the list before deleting it
432    // Note: the allocation tests currently leak the loaded driver handles because in an OOM scenario the loader doesn't bother
433    // removing those. Since this is in an OOM situation, it is a low priority to fix. It does have the effect that Windows will
434    // be unable to delete the binaries that were leaked.
435    for (auto& file : list_of_files_to_delete) {
436        remove(file);
437    }
438    delete_folder(folder);
439}
440FolderManager::FolderManager(FolderManager&& other) noexcept : folder(other.folder), files(other.files) {
441    other.folder.str().clear();
442}
443FolderManager& FolderManager::operator=(FolderManager&& other) noexcept {
444    folder = other.folder;
445    files = other.files;
446    other.folder.str().clear();
447    return *this;
448}
449
450path FolderManager::write_manifest(std::string const& name, std::string const& contents) {
451    path out_path = folder / name;
452    auto found = std::find(files.begin(), files.end(), name);
453    if (found != files.end()) {
454        std::cout << "Overwriting manifest " << name << ". Was this intended?\n";
455    } else {
456        files.emplace_back(name);
457    }
458    auto file = std::ofstream(native_path(out_path.str()), std::ios_base::trunc | std::ios_base::out);
459    if (!file) {
460        std::cerr << "Failed to create manifest " << name << " at " << out_path.str() << "\n";
461        return out_path;
462    }
463    file << contents << std::endl;
464    return out_path;
465}
466void FolderManager::add_existing_file(std::string const& file_name) { files.emplace_back(file_name); }
467
468// close file handle, delete file, remove `name` from managed file list.
469void FolderManager::remove(std::string const& name) {
470    path out_path = folder / name;
471    auto found = std::find(files.begin(), files.end(), name);
472    if (found != files.end()) {
473        int rc = std::remove(out_path.c_str());
474        if (rc != 0) {
475            std::cerr << "Failed to remove file " << name << " at " << out_path.str() << "\n";
476        }
477
478        files.erase(found);
479
480    } else {
481        std::cout << "Couldn't remove file " << name << " at " << out_path.str() << ".\n";
482    }
483}
484
485// copy file into this folder
486path FolderManager::copy_file(path const& file, std::string const& new_name) {
487    auto new_filepath = folder / new_name;
488    auto found = std::find(files.begin(), files.end(), new_name);
489    if (found != files.end()) {
490        std::cout << "File location already contains" << new_name << ". Is this a bug?\n";
491    } else if (file.str() == new_filepath.str()) {
492        std::cout << "Trying to copy " << new_name << " into itself. Is this a bug?\n";
493    } else {
494        files.emplace_back(new_name);
495    }
496    std::ifstream src(native_path(file.str()), std::ios::binary);
497    if (!src) {
498        std::cerr << "Failed to create file " << file.str() << " for copying from\n";
499        return new_filepath;
500    }
501    std::ofstream dst(native_path(new_filepath.str()), std::ios::binary);
502    if (!dst) {
503        std::cerr << "Failed to create file " << new_filepath.str() << " for copying to\n";
504        return new_filepath;
505    }
506    dst << src.rdbuf();
507    return new_filepath;
508}
509}  // namespace fs
510
511const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection) {
512#if defined(VK_USE_PLATFORM_ANDROID_KHR)
513    return "VK_KHR_android_surface";
514#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
515    return "VK_EXT_directfb_surface";
516#elif defined(VK_USE_PLATFORM_FUCHSIA)
517    return "VK_FUCHSIA_imagepipe_surface";
518#elif defined(VK_USE_PLATFORM_GGP)
519    return "VK_GGP_stream_descriptor_surface";
520#elif defined(VK_USE_PLATFORM_IOS_MVK)
521    return "VK_MVK_ios_surface";
522#elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)
523#if defined(VK_USE_PLATFORM_MACOS_MVK)
524    if (string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return "VK_MVK_macos_surface";
525#endif
526#if defined(VK_USE_PLATFORM_METAL_EXT)
527    if (string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT")) return "VK_EXT_metal_surface";
528    return "VK_EXT_metal_surface";
529#endif
530#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
531    return "VK_QNX_screen_surface";
532#elif defined(VK_USE_PLATFORM_VI_NN)
533    return "VK_NN_vi_surface";
534#elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR)
535#if defined(VK_USE_PLATFORM_XCB_KHR)
536    if (string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return "VK_KHR_xcb_surface";
537#endif
538#if defined(VK_USE_PLATFORM_XLIB_KHR)
539    if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return "VK_KHR_xlib_surface";
540#endif
541#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
542    if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return "VK_KHR_wayland_surface";
543#endif
544#if defined(VK_USE_PLATFORM_XCB_KHR)
545    return "VK_KHR_xcb_surface";
546#endif
547#elif defined(VK_USE_PLATFORM_WIN32_KHR)
548    return "VK_KHR_win32_surface";
549#else
550    return "VK_KHR_display";
551#endif
552}
553
554bool string_eq(const char* a, const char* b) noexcept { return a && b && strcmp(a, b) == 0; }
555bool string_eq(const char* a, const char* b, size_t len) noexcept { return a && b && strncmp(a, b, len) == 0; }
556
557InstanceCreateInfo::InstanceCreateInfo() {
558    instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
559    application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
560}
561
562VkInstanceCreateInfo* InstanceCreateInfo::get() noexcept {
563    if (fill_in_application_info) {
564        application_info.pApplicationName = app_name.c_str();
565        application_info.pEngineName = engine_name.c_str();
566        application_info.applicationVersion = app_version;
567        application_info.engineVersion = engine_version;
568        application_info.apiVersion = api_version;
569        instance_info.pApplicationInfo = &application_info;
570    }
571    instance_info.flags = flags;
572    instance_info.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size());
573    instance_info.ppEnabledLayerNames = enabled_layers.data();
574    instance_info.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
575    instance_info.ppEnabledExtensionNames = enabled_extensions.data();
576    return &instance_info;
577}
578InstanceCreateInfo& InstanceCreateInfo::set_api_version(uint32_t major, uint32_t minor, uint32_t patch) {
579    this->api_version = VK_MAKE_API_VERSION(0, major, minor, patch);
580    return *this;
581}
582InstanceCreateInfo& InstanceCreateInfo::setup_WSI(const char* api_selection) {
583    add_extensions({"VK_KHR_surface", get_platform_wsi_extension(api_selection)});
584    return *this;
585}
586
587DeviceQueueCreateInfo::DeviceQueueCreateInfo() { queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; }
588DeviceQueueCreateInfo::DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info) {
589    queue_create_info = *create_info;
590    for (uint32_t i = 0; i < create_info->queueCount; i++) {
591        priorities.push_back(create_info->pQueuePriorities[i]);
592    }
593}
594
595VkDeviceQueueCreateInfo DeviceQueueCreateInfo::get() noexcept {
596    queue_create_info.pQueuePriorities = priorities.data();
597    queue_create_info.queueCount = 1;
598    queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
599    return queue_create_info;
600}
601
602DeviceCreateInfo::DeviceCreateInfo(const VkDeviceCreateInfo* create_info) {
603    dev = *create_info;
604    for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) {
605        enabled_extensions.push_back(create_info->ppEnabledExtensionNames[i]);
606    }
607    for (uint32_t i = 0; i < create_info->enabledLayerCount; i++) {
608        enabled_layers.push_back(create_info->ppEnabledLayerNames[i]);
609    }
610    for (uint32_t i = 0; i < create_info->queueCreateInfoCount; i++) {
611        device_queue_infos.push_back(create_info->pQueueCreateInfos[i]);
612    }
613}
614
615VkDeviceCreateInfo* DeviceCreateInfo::get() noexcept {
616    dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
617    dev.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size());
618    dev.ppEnabledLayerNames = enabled_layers.data();
619    dev.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size());
620    dev.ppEnabledExtensionNames = enabled_extensions.data();
621    uint32_t index = 0;
622    for (auto& queue : queue_info_details) {
623        queue.queue_create_info.queueFamilyIndex = index++;
624        queue.queue_create_info.queueCount = 1;
625        device_queue_infos.push_back(queue.get());
626    }
627
628    dev.queueCreateInfoCount = static_cast<uint32_t>(device_queue_infos.size());
629    dev.pQueueCreateInfos = device_queue_infos.data();
630    return &dev;
631}
632
633#if defined(WIN32)
634std::string narrow(const std::wstring& utf16) {
635    if (utf16.empty()) {
636        return {};
637    }
638    int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.size()), nullptr, 0, nullptr, nullptr);
639    if (size <= 0) {
640        return {};
641    }
642    std::string utf8(size, '\0');
643    if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.size()), &utf8[0], size, nullptr, nullptr) != size) {
644        return {};
645    }
646    return utf8;
647}
648
649std::wstring widen(const std::string& utf8) {
650    if (utf8.empty()) {
651        return {};
652    }
653    int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast<int>(utf8.size()), nullptr, 0);
654    if (size <= 0) {
655        return {};
656    }
657    std::wstring utf16(size, L'\0');
658    if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast<int>(utf8.size()), &utf16[0], size) != size) {
659        return {};
660    }
661    return utf16;
662}
663#endif
664