Долго тема стояла пустая. Попробую расшевелить - может, что получится. Почему тема оставалась нетронутой - понятно: Vulkan - это кроссплатформенная библиотека, по сути, набор API-функций. Разработчик (в широком смысле) волен выбирать, на каком языке вызывать API-функции, и язык ассемблера для этого - не самый удобный инструмент, а идея кроссплатформенности теряется. За основу берём код с сайта vulkan-tutorial.com. И здесь сразу наблюдаем курьёз: на заглавной странице этого сайта честно говорится: А vulkan.org отправляет обратно: Вот этому руководству и будем следовать. Задача насколько амбициозная, настолько же и бессмысленная, но ассемблер - это дзен, а для самурая, как известно, путь важнее цели
Шаг 1. Создаём оконное приложение Исходный код на С++ показывает, насколько автор мыслит категориями ООП. Даже само приложение - это класс. Спойлер: Код С++ Код (C++): //https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Base_code #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <iostream> #include <stdexcept> #include <cstdlib> const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } private: GLFWwindow* window; void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); } void initVulkan() { } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } void cleanup() { glfwDestroyWindow(window); glfwTerminate(); } }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } ООП - это прекрасно. Но (в данном конкретном случае) для ассемблера этот подход не годится, т.к. содержит слишком много лишних переходов, что усложняет восприятие и в редакторе, и в дебаггере. Упрощаем: Спойлер: Код С Код (C): #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <iostream> #include <stdexcept> #include <cstdlib> GLFWwindow* window; int main() { //initWindow(); glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); //initVulkan(); //TODO //mainLoop(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } //cleanup(); glfwDestroyWindow(window); glfwTerminate(); return 0; } Вот теперь у нас есть стройная последовательность вызовов. Переводим: Спойлер: Код MASM Код (ASM): ;100_base_code.asm option casemap:none option prologue:none option epilogue:none ;Standard Libraries includelib D:\bin\dev\asm\ml64\VS2019\lib\user32.lib includelib D:\bin\dev\asm\ml64\VS2019\lib\kernel32.lib ;GLFW includelib D:\bin\dev\ogl\glfw-3.4.bin.WIN64\lib-vc2022\glfw3dll.lib ;WinMain extrn glfwInit:PROC extrn glfwWindowHint:PROC extrn glfwCreateWindow:PROC extrn glfwWindowShouldClose:PROC extrn glfwPollEvents:PROC extrn glfwDestroyWindow:PROC extrn glfwTerminate:PROC extrn ExitProcess:PROC .data hWnd dq 0 szAppTitle db "Vulkan", 0 .code WinMain proc ;Prologue: push rbp mov rbp,rsp and rsp,-16 ;Align the Stack sub rsp,100h ;Create the Buffer call glfwInit test rax,rax jz lbl_WinMain_Err mov rcx,22001h; GLFW_CLIENT_API xor rdx,rdx ;GLFW_NO_API = 0 call glfwWindowHint mov rcx,20003h ;GLFW_RESIZABLE xor rdx,rdx ;GLFW_FALSE = 0 call glfwWindowHint mov rcx,3C0h ;1920/2=960 mov rdx,21Ch ;1080/2=540 lea r8,szAppTitle xor r9,r9 mov qword ptr[rsp+20h],0 ;share = NULL call glfwCreateWindow mov hWnd,rax test rax,rax jz lbl_WinMain_Err lbl_WinMain_Loop: mov rcx,hWnd call glfwWindowShouldClose test rax,rax jnz lbl_WinMain_CleanUp call glfwPollEvents jmp lbl_WinMain_Loop lbl_WinMain_CleanUp: mov rcx,hWnd call glfwDestroyWindow call glfwTerminate xor rcx,rcx call ExitProcess lbl_WinMain_Err: call glfwTerminate mov rcx,1 call ExitProcess WinMain endp ;include procedures end Хорошо. Делаем батник: Спойлер: Код bat Код (Text): @echo off set AppName=100_base_code set BinPath=D:\bin\dev\asm\ml64\VS2019\bin if exist %AppName%.obj del %AppName%.obj if exist %AppName%.exe del %AppName%.exe %BinPath%\ml64 /c /Fo %AppName%.obj %AppName%.asm %BinPath%\link.exe %AppName%.obj /ENTRY:WinMain /SUBSYSTEM:WINDOWS del %AppName%.obj dir %AppName%.* pause Скачиваем актуальную библиотеку GLFW. Надо брать актуальные файлы glfw3dll.lib и glfw3.dll. Важный момент: библиотека glfw3.dll должна лежать в папке, где будет лежать наш исполняемый файл 100_base_code.exe, иначе приложение не запустится. Результат Шага 1 должен быть таким: Шаг 2. Собственно вулкан. Создаём экземпляр Vulkan (или как там переводится Vulkan Instance?) В класс приложения добавляется метод initVulkan(), содержащий пока одну функцию createInstance() следующего содержания: Спойлер: Код С++ Код (C++): void createInstance() { VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); createInfo.enabledExtensionCount = glfwExtensionCount; createInfo.ppEnabledExtensionNames = glfwExtensions; createInfo.enabledLayerCount = 0; if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } Для нас это означает, что надо объявить две структуры: VkApplicationInfo и VkInstanceCreateInfo, - добавить их экземпляры в секцию данных и заполнить в секции кода. Попутно надо вызвать функцию glfwGetRequiredInstanceExtensions, а по итогу вызвать vkCreateInstance и для этого подключить библиотеку vulkan-1.lib. Она находится в составе SDK, о котором говорил коллега выше. Скачивать SDK надо на сайте LunarG. Получится что-то вроде этого: Спойлер: Код MASM Код (ASM): ;createInstance.asm createInstance proc ;Prologue push rbp mov rbp,rsp and rsp,-16 ;Align the Stack sub rsp,100h ;Create the Buffer ;Fill the VkApplicationInfo Structure mov appInfo.sType,0 ;VK_STRUCTURE_TYPE_APPLICATION_INFO mov appInfo.pNext,0 ;nullptr lea rax,sz_AppName mov appInfo.pApplicationName,rax mov appInfo.applicationVersion,1 lea rax,sz_EngineName mov appInfo.pEngineName,rax mov appInfo.engineVersion,1 ;VK_API_VERSION_1_0 == ((((uint32_t)(0)) << 29U) | (((uint32_t)(1)) << 22U) | (((uint32_t)(0)) << 12U) | ((uint32_t)(0))); mov appInfo.apiVersion,400000h ;Fill the VkInstanceCreateInfo Structure mov createInfo.sType,1 ;VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO mov createInfo.pNext,0 ;nullptr mov createInfo.flags,0 lea rax,appInfo mov createInfo.pApplicationInfo,rax mov createInfo.enabledLayerCount,0 ;Temporarily 0 mov createInfo.ppEnabledLayerNames,0 ;Temporarily nullptr lea rcx,glfwExtensionCount ;Load Address call glfwGetRequiredInstanceExtensions ;mov glfwExtensions,rax mov createInfo.ppEnabledExtensionNames,rax ;glfwExtensions mov ecx,glfwExtensionCount ;Load Value mov createInfo.enabledExtensionCount,ecx lea rcx,createInfo xor rdx,rdx lea r8,ghVkInstance call vkCreateInstance test rax,rax jne lbl_createInstance_Err ;!= VK_SUCCESS jmp lbl_createInstance_Return0 lbl_createInstance_Err: xor rcx,rcx lea rdx,sz_createInstance_Err ;Failed to create a vk Instance! lea r8,sz_createInstance ;MessageBox Title mov r9,30h ;MB_OK|MB_ICONEXCLAMATION call MessageBoxA mov rax,-1 jmp lbl_createInstance_End lbl_createInstance_Return0: xor rax,rax lbl_createInstance_End: ;Epilogue leave ret createInstance endp
ml64, Обьясните плз что это и зачем нужно. Судя по коду это скрипт, генератор из г&п. Это виртуальная машина?, нет, тогда что?? Код (Text): call glfwWindowShouldClose test rax,rax jnz lbl_WinMain_CleanUp call glfwPollEvents Что вы курите ?
Курим исключительно мануалы. Автор мануала Alexander Overvoorde придерживается общей идеологии Kronos и LunarG, в которой реализованы как OpenGL, так и Vulkan, а именно: максимально возможной кроссплатформенности при низкоуровневом управлении аппаратной частью. Поэтому для создания оконного приложения он использует кроссплатформенную библиотеку GLFW, что не является обязательным. Можно создать окно без библиотек на голом API/ABI. Но автор выбрал GLFW, а я решил проверить, как его решение работает с MASM, поэтому окно рисуется четырьмя функциями: Код (C): glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); Основной цикл приложения - это две функции: Код (C): while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } И завершение приложения: Код (C): glfwDestroyWindow(window); glfwTerminate(); На втором шаге к этому приложению добавляется собственно библиотека Vulkan. Экземпляр контекста Vulkan создаётся в методе createInstance(): Код (C): vkCreateInstance(&createInfo, nullptr, &instance); На третьем шаге добавляется мессенджер VK. Серьёзно. Это делается так: Код (C++): void setupDebugMessenger() { VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) ; }
!!! Подход весьма поддерживаю, но исполнение... Код (ASM): .... call vkCreateInstance test rax,rax ;jne lbl_createInstance_Err ;!= VK_SUCCESS ;jmp lbl_createInstance_Return0 jz lbl_createInstance_End ;!= VK_SUCCESS ;lbl_createInstance_Err: xor rcx,rcx lea rdx,sz_createInstance_Err ;Failed to create a vk Instance! lea r8,sz_createInstance ;MessageBox Title mov r9,30h ;MB_OK|MB_ICONEXCLAMATION call MessageBoxA mov rax,-1 ;В x64 не знаю, ;а в x31? не: ;Mov eAx,-1 ;а: ;Or eAx,-1 ;- это уже конечно не экономия памяти, ;а показатель собранности и аккуратности ;низкоуровнего программирования. ;jmp lbl_createInstance_End ;lbl_createInstance_Return0: ;xor rax,rax ; при jz lbl_createInstance_End он и так уже 0 lbl_createInstance_End: ;Epilogue leave ret createInstance endp 1 вместо 6 возможно это имеелось ввиду?
Соглашусь: mov rax,-1 выглядит топорно. Но тут масштаб задачи такой, что это простительно. Собранность и аккуратность здесь в другом проявляются. Например, надо сравнить два массива строковых переменных (не две строки, а два массива разной длины!), причём один массив - это последовательность буферов: Код (ASM): sz_validationLayer0 db 'VK_LAYER_KHRONOS_validation',0 sz_validationLayer1 db 'Validation_Layer_1',0 gpValidationLayers dq offset sz_validationLayer0 а другой массив - это последовательность экземпляров структуры, количество которых программа получает динамически: Код (ASM): VkLayerProperties struct layerName db 256 dup(0) ;char[256] specVersion dd 0 ;VkVersion implementationVersion dd 0 ;VkVersion description db 256 dup(0) ;char[256] VkLayerProperties ends Как-то вот так: Код (C++): for (const char* layerName : validationLayers) { bool layerFound = false; for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; Для ассемблера важна каждая инструкция. Это понимают все, кто проводил ночи в дебаггере в поисках какой-нибудь переменной не того размера. Или целочисленного аргумента, который Kronos решил передавать через xmm. Но тут, как говорится, потом напильником доработаем, иначе в конечное время задачу не решимШаг 3. Слои валидации и отладочный мессенджерСразу скажу: вышеуказанное сравнение строк я ниасилил. Мне сейчас больше интересен сам Вулкан. Там, оказывается, чуть ли не своя операционная система. Реализован свой мессенджер, который реагирует на ошибки. Например, если (по рекомендации туториала) закомментировать функцию очистки, то перед выходом Вулкан поругается. Причём есть возможность направлять сообщения и в консоль, и в лог, и в MessageBox:
Строго говоря этот код и не является ООП-нутым. Код классами, но без ООП. Если можно код с классами превратить в код со структурами просто переделав все методы на свободные функции принимающие первый параметр указатель на экземпляр класса, то это не ООП, а обычное процедурное программирование с синтаксическим сахаром классов. Не получится сделать такое преобразование если класс используется виртуальные функции и именно применение и использование виртуальных функций и делает код ООП-нутным - оопнутость это когда мы в алгоритм можем засунуть заранее неизвестный тип объекта через указатель/ссылку на его базовый класс. Что касается затеи - эпичненько конечно же. Я лично когда узнал что в вулкане код по инициализации отрисовки одного моноцветного треугольника занимает тысячу или несколько тысяч строк даже знакомиться с ним стало лень.
Это в полной комплектации. На минималках там такие же принципы: контекст, конвейер и шейдеры. А вообще, что-то мне подсказывает, что со временем для Vulkan выйдут в мейнстрим такие же библиотеки, как для OpenGL. А возможно, и те же самые: GLU, GLFW, GLM. Просто скажут, что отныне OpenGL - это legacy. Или форки появятся. Или самостоятельные проекты.
Итак, шаг 3 готов на 120% Реализовано не только сравнение срок: Спойлер: Код MASM Код (ASM): ;2_0_2_checkLayers.asm ; ------------------------------------------------------------------ ; Returns: RAX = 0 if all requested layers are available, ; RAX = 1 if at least one layer missing, ; RAX = BAD if bad data ; ------------------------------------------------------------------ ;For i = 0 to layerCount-1 ; layerFound = 0 ; For j = 0 to availableLayersCount-1 ; If validationLayers[i] = availableLayers[j].layerName Then ; layerFound = 1 ; break ; End If ; Next j ; If layerFound == 0 Then Return false ;Next i ;Return true ;Source pattern: ;const std::vector<const char*> validationLayers = {"VK_LAYER_KHRONOS_validation"}; ;sz_validationLayer0 db 'VK_LAYER_0',0 ;sz_validationLayer1 db 'VK_LAYER_1',0 ;gpValidationLayer0 dq offset sz_validationLayer0 ;gpValidationLayer1 dq offset sz_validationLayer1 ;dq 0 ;Array terminated by a null pointer ;Destination Pattern: ;VkLayerProperties struct ;layerName db 256 dup(0) ;char[256] ;specVersion dd 0 ;VkVersion ;implementationVersion dd 0 ;VkVersion ;description db 256 dup(0) ;char[256] ;VkLayerProperties ends checkLayers proc ; Load validation layer count into r9d (volatile, not used elsewhere) mov r9d, nValidationLayersCount test r9d, r9d jz lbl_checkLayers_Success ; Load available layer count into r10d mov r10d, nAvailableLayersCount test r10d, r10d jz lbl_checkLayers_LayerMissing ; Point to first validation layer pointer array lea rsi, gpValidationLayer0 lbl_checkLayers_OuterLoop: mov r8, [rsi] ; R8 = pointer to validation layer name string ; Quick first-character test (must be 'V') cmp byte ptr [r8], 'V' jne lbl_checkLayers_Err_BadData ; Inner loop: scan available layers mov rdi, ghAvailableLayers ; start of VkLayerProperties array mov r11d, r10d ; inner counter = available layer count lbl_checkLayers_InnerLoop: ; Compare strings: validation (R8) vs available layer name (RDI) mov rcx, r8 ; RCX = validation string mov rdx, rdi ; RDX = available layer name lbl_checkLayers_CompareBytes: mov al, [rcx] cmp al, byte ptr [rdx] jne lbl_checkLayers_NextAvailable test al, al je lbl_checkLayers_FoundMatch inc rcx inc rdx jmp lbl_checkLayers_CompareBytes lbl_checkLayers_NextAvailable: add rdi, 208h ; sizeof(VkLayerProperties) = 520 bytes dec r11d jnz lbl_checkLayers_InnerLoop ; No match for this validation layer jmp lbl_checkLayers_LayerMissing lbl_checkLayers_FoundMatch: add rsi, 8 ; move to next validation layer pointer dec r9d jnz lbl_checkLayers_OuterLoop ; All validation layers found jmp lbl_checkLayers_Success lbl_checkLayers_LayerMissing: mov rax, 1 ret lbl_checkLayers_Err_BadData: mov rax, 0BADh ret lbl_checkLayers_Success: xor rax, rax ret checkLayers endp но и дамп слоёв, что очень полезно и чего нет в оригинале: Спойлер: Код MASM Код (ASM): ;2_0_3_dumpAvailableLayers.asm ; Dumps all available Vulkan layer properties to "AvailableLayers.txt" dumpAvailableLayers proc push rbp mov rbp, rsp and rsp, -16 sub rsp, 100h ; Create/overwrite output file lea rcx, sz_DumpFileName mov rdx, 40000000h ; GENERIC_WRITE mov r8, 1 ; FILE_SHARE_READ xor r9, r9 mov qword ptr [rsp+20h], 2 ; CREATE_ALWAYS mov qword ptr [rsp+28h], 80h; FILE_ATTRIBUTE_NORMAL mov qword ptr [rsp+30h], 0 call CreateFileA cmp rax, -1 je dump_end mov hDumpFile, rax ; Write header mov rcx, hDumpFile lea rdx, sz_Header mov r8, sizeof sz_Header - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 ; lpOverlapped = NULL call WriteFile ; Get available layer count and pointer mov r14d, nAvailableLayersCount test r14d, r14d jz close_file mov r15, ghAvailableLayers test r15, r15 jz close_file xor r12d, r12d ; index next_layer: mov rax, r12 imul rax, 520 lea r13, [r15 + rax] ; R13 = VkLayerProperties* ; "Layer X: " mov rcx, hDumpFile lea rdx, sz_LayerHeader mov r8, sizeof sz_LayerHeader - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; Convert index to decimal mov eax, r12d lea rdi, tempBuffer + 31 mov byte ptr [rdi], 0 mov r10d, 10 test eax, eax jnz idx_conv mov byte ptr [rdi-1], '0' dec rdi jmp idx_write idx_conv: xor edx, edx div r10d add dl, '0' dec rdi mov [rdi], dl test eax, eax jnz idx_conv idx_write: mov rcx, hDumpFile mov rdx, rdi lea r8, tempBuffer + 31 sub r8, rdi lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; ": " mov rcx, hDumpFile lea rdx, sz_ColonSpace mov r8, sizeof sz_ColonSpace - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; newline mov rcx, hDumpFile lea rdx, sz_NewLine mov r8, sizeof sz_NewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; " Name: " mov rcx, hDumpFile lea rdx, sz_IndentName mov r8, sizeof sz_IndentName - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; layerName (string at R13) mov rcx, hDumpFile mov rdx, r13 mov rbx, r13 dec rbx @@: inc rbx cmp byte ptr [rbx], 0 jne @b sub rbx, r13 mov r8, rbx lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; newline mov rcx, hDumpFile lea rdx, sz_NewLine mov r8, sizeof sz_NewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; " Spec Version: " mov rcx, hDumpFile lea rdx, sz_IndentSpec mov r8, sizeof sz_IndentSpec - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; specVersion (dword at R13+256) mov eax, dword ptr [r13 + 256] lea rdi, tempBuffer + 31 mov byte ptr [rdi], 0 mov r10d, 10 test eax, eax jnz spec_conv mov byte ptr [rdi-1], '0' dec rdi jmp spec_write spec_conv: xor edx, edx div r10d add dl, '0' dec rdi mov [rdi], dl test eax, eax jnz spec_conv spec_write: mov rcx, hDumpFile mov rdx, rdi lea r8, tempBuffer + 31 sub r8, rdi lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; newline mov rcx, hDumpFile lea rdx, sz_NewLine mov r8, sizeof sz_NewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; " Implementation Version: " mov rcx, hDumpFile lea rdx, sz_IndentImpl mov r8, sizeof sz_IndentImpl - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; implementationVersion (dword at R13+260) mov eax, dword ptr [r13 + 260] lea rdi, tempBuffer + 31 mov byte ptr [rdi], 0 mov r10d, 10 test eax, eax jnz impl_conv mov byte ptr [rdi-1], '0' dec rdi jmp impl_write impl_conv: xor edx, edx div r10d add dl, '0' dec rdi mov [rdi], dl test eax, eax jnz impl_conv impl_write: mov rcx, hDumpFile mov rdx, rdi lea r8, tempBuffer + 31 sub r8, rdi lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; newline mov rcx, hDumpFile lea rdx, sz_NewLine mov r8, sizeof sz_NewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; " Description: " mov rcx, hDumpFile lea rdx, sz_IndentDesc mov r8, sizeof sz_IndentDesc - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; description (string at R13+264) mov rcx, hDumpFile lea rdx, [r13 + 264] mov rbx, rdx dec rbx @@: inc rbx cmp byte ptr [rbx], 0 jne @b sub rbx, rdx mov r8, rbx lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; newline mov rcx, hDumpFile lea rdx, sz_NewLine mov r8, sizeof sz_NewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile ; double newline between layers mov rcx, hDumpFile lea rdx, sz_DoubleNewLine mov r8, sizeof sz_DoubleNewLine - 1 lea r9, dwBytesWritten mov qword ptr [rsp+20h], 0 call WriteFile inc r12d cmp r12d, r14d jb next_layer close_file: mov rcx, hDumpFile call CloseHandle dump_end: leave ret dumpAvailableLayers endp Пример дампа: Спойлер: Дамп Available Vulkan Layers: Layer 0: Name: VK_LAYER_NV_optimus Spec Version: 4211013 Implementation Version: 1 Description: NVIDIA Optimus layer Layer 1: Name: VK_LAYER_NV_present Spec Version: 4211013 Implementation Version: 1 Description: NVIDIA Presentation Layer Layer 2: Name: VK_LAYER_EOS_Overlay Spec Version: 4202632 Implementation Version: 1 Description: Vulkan overlay layer for Epic Online Services Layer 3: Name: VK_LAYER_LUNARG_api_dump Spec Version: 4211029 Implementation Version: 2 Description: LunarG API dump layer Layer 4: Name: VK_LAYER_LUNARG_gfxreconstruct Spec Version: 4211029 Implementation Version: 4194309 Description: GFXReconstruct Capture Layer Version 1.0.5 Layer 5: Name: VK_LAYER_KHRONOS_synchronization2 Spec Version: 4211029 Implementation Version: 1 Description: Khronos Synchronization2 layer Layer 6: Name: VK_LAYER_KHRONOS_validation Spec Version: 4211029 Implementation Version: 1 Description: Khronos Validation Layer Layer 7: Name: VK_LAYER_LUNARG_monitor Spec Version: 4211029 Implementation Version: 1 Description: Execution Monitoring Layer Layer 8: Name: VK_LAYER_LUNARG_screenshot Spec Version: 4211029 Implementation Version: 1 Description: LunarG image capture layer Layer 9: Name: VK_LAYER_KHRONOS_profiles Spec Version: 4211029 Implementation Version: 1 Description: Khronos Profiles layer Layer 10: Name: VK_LAYER_KHRONOS_shader_object Spec Version: 4211029 Implementation Version: 1 Description: Khronos Shader object layer Layer 11: Name: VK_LAYER_LUNARG_crash_diagnostic Spec Version: 4211029 Implementation Version: 1 Description: Crash Diagnostic Layer is a crash/hang debugging tool that helps determines GPU progress in a Vulkan application. "Исправлены ошибки, повышена устойчивость"...И на этой позитивной ноте позволю себе взять паузу, перевести дух и осмыслить проделанное, ибо следующий шаг - это "Физические устройства и семьи очередей" - что бы это ни значило.