воскресенье, 17 апреля 2016 г.

Delphi и Vulkan, первое знакомство.

Не так давно мне в этом блоге напомнили о существовании давно забытой мной GLScene.
Как оказалось - все еще есть энтузиасты, пытающиеся вдохнуть жизнь в этот продукт, даже портировать ее на Vulkan. Могу только пожелать успеха ребятам в этом нелегком деле, надеюсь у них получится то, что не удалось когда-то нашей команде :)

Но история о другом, портирование GLScene на Vulkan началось с портирования заголовочных файлов, а это уже достаточно чтоб попробовать там что-то сделать, да и приятно было вернуться на Делфи, после нескольких лет разработки на С++. Вообщем вот что из этого получилось...

Как я уже сказал, все началось с ссылки на ветку GLScene, где ребята выложили порт заголовков Вулкана на Делфи:
https://sourceforge.net/p/glscene/code/HEAD/tree/branches/GLSceneVKS/Source/Basis/

Посмотрел я модуль GLS.Vulkan.pas - выглядел прилично, решил попробовать.
Однако проблемы начались уже на этапе компиляции, к сожалению дефайны в GLS.VK_Platform.pas были не рабочие, пришлось править. Вторая проблема с которой я столкнулся при использовании этого модуля - отсутствие импорта расширений, в частности пришлось добавить загрузку следующих расширений:
    //  VK_KHR_swapchain
    vkCreateSwapchainKHR
    vkDestroySwapchainKHR
    vkGetSwapchainImagesKHR
    vkAcquireNextImageKHR
    vkQueuePresentKHR
  
    //  VK_KHR_display_swapchain
    vkCreateSharedSwapchainsKHR;

Так же не была реализована загрузка Win32 функций WSI, в частности:
vkCreateWin32SurfaceKHR и vkGetPhysicalDeviceWin32PresentationSupportKHR

За пределами области видимости оказалась так же константа: VK_KHR_WIN32_SURFACE_EXTENSION_NAME
Мелочь, но неприятно :)

Это все было оперативно добавлено в модуль загрузки и я перешел к инициализации Вулкана и успешно создал инстанс. Даже удалось успешно получить список устройств в системе и их свойства. Я уже было обрадовался что все идет гладко, как грянул гром среди чистого неба :)

При попытке создать Win32Surface и при попытке проверке допустимых для презентации очередей через vkGetPhysicalDeviceWin32PresentationSupportKHR приложение падало. Подключение отладочных слоев так же мало чем помогло, в логе значилось что-то типа "передан пустой объект". Перепроверка всей инициализации ничего не дала, весь код был корректный и аналогичный код отлично работал на С++. В первую очередь я грешил на то, что расширения были загружены не корректно - перепроверил, все было в порядке, перепроверил по спецификации соглашения вызовов, думал что Win32 расширения имеют другой тип соглашения передачи параметров - опять мимо.... Все правильно но ничего не работает...

Для чего я это все расписал - в сети можно найти несколько попыток портирования заголовков Вулкана на Делфи, в частности, кроме названных выше заголовков из GLScene:

https://github.com/MaksymTymkovych/Delphi-Vulkan
и
http://git.ccs-baumann.de/bitspace/Vulkan

Проверил обе версии, у всех были одни и те же проблемы, как-будто кто-то это копипастил :)

Данная ситуация мн неочень понравилась - как так, есть работающая dll, есть заголовки, но нет результата, не порядок!!!

Начал разбираться кто, что и как портировал. До этого я заметил что объявленная там константа VK_NULL_HANDLE бесполезна, так как ее тип не совпадает с типами хэндлов объектов Вулкана. Просмотр кода показал что все хэндлы там были объявлены как:
type
  PPVkInstance = ^PVkInstance;
  PVkInstance = ^TVkInstance;
  TVkInstance = record end;
Это был копипаст с Сишного объявления:
typedef  struct VkInstance_T *VkInstance;
Буду честен, до того как увидел заголовки Вулкана сам не знал что можно делать такой хак (не смотря на то, что тип VkInstance_T не определен, компилятор это переваривает и интерпретирует как указатель на несуществующую структуру типа VkInstance_T).
К сожалению те, кто делал порт с Сишных заголовков, видно так же не сталкивались с такой записью, потому просто объявили это как TVkInstance = record end;
Все бы ничего, вот только в этом месте Паскаль и С++ ведут себя по-разному. В С++ объект такого типа будет простым указателем размера NativeInt, а вот Делфи под такую запись вообще не выделяет места (sizeof(TVkInstance) = 0). Вот и получается, что мы при попытке создать такой вот хэндл указывали ссылку на не выделенную область памяти, а потом использовали эту же ссылку для инициализации других объектов. Чем это закончилось - читайте выше.

Исправление всего этого добра на "TVkInstance = ^IntPtr" решило проблему и удалось успешно проинициализировать поверхность. Однако после этого я столкнулся с еще какой-то проблемой в объявлениях типов данных, потом с еще одной, какие-то мелочи, но я уже и не помню в чем они заключались, в результате мне это порядком надоело и в этот самый момент мне попался на глаза вот этот проект:

https://github.com/BeRo1985/pasvulkan/

Человек не поленился и написал генератор заголовочного файла на основе предоставляемой xml схемы, включая объявление всех расширений, так же там он использовал корректный тип хэндлов. Замена заголовочного файла на свежесгенерированный версии 1.0.8 решил все проблемы, код собрался и запустился буквально со второго раза (я посчитал что для инициализации достаточно вызвать LoadVulkanLibrary, оказалось требуется еще вызов LoadVulkanGlobalCommands, чтоб "догрузить" остальные команды).

Вот такая вот история со счастливым концом :)

Ну и в качестве компенсации вам за потраченное на чтение время - прилагаю пример "ClearScreen" на Делфи: DelphiVulkanClearScreen.zip

В примере создается окно (важно - окно, с форматом пикселей настроенным под OpenGL не подойдет, на видеокартах от NVidia будет системная ошибка). Инициализируется Вулкан, опрашиваются свойства всех существующих в системе устройств (если их у вас несколько - нужно ввести номер нужно устройства), далее инициализируется WSI (поверхность и swapchain), запрашиваются доступные буферы кадров для двойной буферизации (или более, если выключить VSync), создается логическое устройство с поддержкой презентации на экран, создается пулл командных буферов, с поддержкой графической очереди, создаем серию буферов команд, по одному на каждый кадр, настраиваем лайауты для очистки буферов кадра, настраиваем семафоры, чтоб произвести синхронизацию между готовностью кадра к очистке и к отображению, и отправляем все это добро в очередь отрисовки.

Всю иницифализацию обернул в серию классов, объявленных в модуле vulkan_api.
Пример тестировался с драйверами AMD и NVidia 1.0.8 и 1.0.5 соответственно.
Использовался LunarG SDK 1.0.8: https://vulkan.lunarg.com/app/download

Комментариев нет:

Отправить комментарий