SDL3 GPU?

Мир эксплицитных графических API переживает бурный период фрагментации. Это похоже на 90-е, когда одновременно были Glide, Direct3D 5-6, OpenGL 1.x и программные рендеры, только сейчас все куда сложнее, потому что и железо намного сложнее.

Есть Vulkan, который де-юре является современной заменой OpenGL — в том смысле, что работает на тех же платформах. Де-факто, я считаю, не является, так как далеко не каждое существующее приложение OpenGL реально перенести на Vulkan без тотальной переделки. На macOS его нет, как, собственно, и актуальных версий OpenGL. Я считаю, что на чистом Vulkan писать опасно для ментального здоровья — поверх него обязательно нужна абстракция.

Есть Direct3D 12, который работает только под Windows и на XBox. Я его традиционно в расчет не беру, поскольку писать windows-онли приложения в наши дни уже моветон. Как минимум, поддержка Linux нужна обязательно.

Есть Metal, который аналогично существует только в экосистеме Apple. Писать на нем напрямую тоже не надо, если только вы не создаете мак-эксклюзивы.

Есть WebGPU, который, казалось, должен был исправить ситуацию, объединив предыдущую троицу под единым API. С точки зрения дизайна эта задача с грехом пополам продвигается, хотя и медленно, но вот реализации пока оставляют желать лучшего. wgpu от Mozilla написан на Rust и работает как-то не очень стабильно — до сих пор утечки памяти в простейших приложениях. Ну и WGSL поделил сообщество графических разработчиков на два лагеря — одним ок, другие не признают ничего, кроме SPIR-V. Язык и правда слишком специфический, с сильным уклоном в Rust, да и в целом от WebGPU ощущение такое, что это чисто растоманский проект, не учитывающий другие языки и парадигмы программирования. И мне лично жутко не нравится его модель биндинга ресурсов. Все эти bind group layout’ы неудобные… API пилят уже пять лет, а на нем все еще страшно писать реальный код — то и дело что-то ломается непредсказуемым образом.

Наконец, появился GPU API в SDL3. Надо сказать, библиотека вообще замечательная, она уже много лет делает возможной поддержку единой кодовой базы в играх (да и не только в играх) под Windows и Linux. То, что в третьей версии появился аж целый графический API — это нехилый такой аргумент перейти на нее как можно быстрее. Мне, конечно, было очень интересно сравнить этот API с WebGPU, и данный пост я пишу как раз с этой целью.

Но прежде — небольшое резюме. Что вся эта ситуация означает для инди-разработчика? К сожалению, ничего хорошего. Сегодня уже как минимум пять мейнстримных графических API, а завтра будет сколько? Изобретут еще пять, или в результате конкуренции останутся два, как в нулевые? Кто кому уступит? Непонятно, что выбрать, потому что не хочется вкладываться в технологию, которая может сгинуть. Корпорации, разрабатывающие AAA-продукты, обладают ресурсами для поддержки всех технологий, с них не убудет, но для пет-проектов и альтернативных движков, таких, как мой, это недостижимо — приходится выбирать что-то одно.

SDL3 GPU vs WebGPU

Главное, чем подкупает SDL3 GPU — минималистичность. Инициалирующие структуры не выглядят, как монстры а ля объекты JavaScript. Привязка рендера к окну — это всего лишь один вызов функции SDL_ClaimWindowForGPUDevice, а не целая эпопея с платформоспецифичными функциями, как в WebGPU. Сказывается, что SDL полностью берет управление окном и контекстом на себя. Также тут нет управления очередью и почти не нужно беспокоиться о синхронизации: вы просто создаете командные буферы под ваши задачи и отправляете их на исполнение функцией SDL_SubmitGPUCommandBuffer. Командный буфер, как и в других низкоуровневых API, — это список команд видеопроцессору выполнить какую-либо операцию (создать ресурс, скопировать данные, нарисовать примитив).

Передача данных в видеопамять осуществляется при помощи так называемых трансфер-буферов (SDL_GPUTransferBuffer). Они отображаются в системную память, так что вы можете копировать в них данные вызовом memcpy или любым другим удобным вам способом. Когда трансфер-буфер готов, он используется как источник данных для прохода копирования (SDL_GPUCopyPass), который заносится в командный буфер. В общем, основную идею эксплицитных API (сократить частоту синхронизаций CPU и GPU до необходимого минимума) тут постарались сохранить, и это чувствуется.

Отдельная головная боль WebGPU — управление свопчейном, и тут SDL3 GPU снова устраняет все сложности. Вы просто получаете текущий задний буфер функцией SDL_WaitAndAcquireGPUSwapchainTexture, обновляете ваш color target и используете его для вызова SDL_BeginGPURenderPass — и все!

Единого шейдерного языка в SDL3 GPU нет — он зависит от выбранного бэкенда. Если вы выбрали Vulkan, то это SPIR-V, если Direct3D 12 — DXIL, если Metal — MSL. Так что для мультитаргетного движка вам обязательно понадобится тулчейн трансляции шейдеров, например SPIRV-Cross. Это, пожалуй, единственная сложность, в особенности если вы не используете C/C++. Но для D я уже решил эту проблему, написав биндинги bindbc-glslang и bindbc-spirvcross. Процесс ручной компиляции шейдеров может показаться немного мудреным, но на самом деле это вещь из разряда «написал и забыл». Использование SPIR-V в качестве внутреннего промежуточного представления дает невиданную свободу — можно писать на привычном GLSL, а можно попробовать что-то новое.

Отдельная песня — привязка ресурсов к шейдерам. В SDL эту задачу решили элегантно, все 4 дескриптор-сета жестко распределены:

  • Сет 0 — текстуры/сэмплеры и SSBO вершинной стадии
  • Сет 1 — UBO вершинной стадии
  • Сет 2 — текстуры/сэмплеры и SSBO фрагментной стадии
  • Сет 3 — UBO фрагментной стадии

Биндинг-поинты вы задаете сами (в шейдере и программном коде). Единственное, что важно знать перед созданием шейдера — сколько именно сэмплеров, SSBO и UBO в нем используется. Это может поставить в тупик, так как усложняет абстракцию в движке, но даю лайфхак: эту информацию предоставляет все тот же SPIRV-Cross (см. spvc_resources_get_resource_list_for_type). Я считаю, по сравнению с WebGPU это просто рай.

Для рендеринга используются объекты SDL_GPUGraphicsPipeline (которые задают параметры растеризатора) и SDL_GPURenderPass (к которому биндятся ресурсы). В deferred-движке их удобно объединить в одну абстракцию RenderPass. Пайплайны традиционно неизменяемые, так что после OpenGL придется привыкать к новым реалиям и оптимизировать рендер.

Вот, вроде бы, и все. Я пока не затронул compute, это тема для отдельной статьи. Разработчики SDL приятно удивили, выкатив отличный API за сравнительно короткие сроки — уже сейчас на нем вполне можно делать серьезные проекты. Хочется верить, что он не заглохнет и будет развиваться.

Итоги 2015 года

Наступают новогодние праздники — а значит, настало время традиционного подведения итогов по проделанной за год работе:

  • Вышло 6 номеров электронного журнала «FPS» (№№ 34, 35, 36, 37, 38, 39). В 2016 году журналу исполняется 8 лет, не за горами и юбилейный 40-й номер.
  • Открылся ресурс CG World — новостной блог, посвященный компьютерной графике
  • Значительно улучшен графический движок DGL, на котором создается Atrium. Реализовано динамическое освещение, шейдерные эффекты, тени, пост-процессинг и т.д.
  • Игра «засветилась» в PC Magazine, на IndieDB, а также в официальной группе новостей и русскоязычных сообществах по языку D.
  • Обновился сайт Atrium.
  • Состоялся выход dlib 0.7.0 со множеством улучшений во всех модулях библиотеки и поддержкой Travis-CI.
  • Физический движок dmech обновился до версии 0.2.6, обзавелся C-интерфейсом, компиляцией в динамическую библиотеку и поддержкой Mac OS X. Появились новые демки и уроки по движку.
  • Система сборки Cook обновилась до версии 2.1.x. Основные нововведения: поддержка response-файлов, кросс-компиляции и поддиректорий во внешних зависимостях.

Чем для меня был интересен прошедший 2015 год? Вот самые, на мой скромный взгляд, значимые события в мире CG, СПО и любительского геймдева:

  • Появление Vulkan, графического API нового поколения, который должен преодолеть недостатки OpenGL и Direct3D, сократив прослойку между пользовательским кодом и видеодрайвером, что позволит более глубоко оптимизировать приложения.
  • Выход нового короткометражного открытого фильма от Blender Institute — «Космическая прачечная» (проект Gooseberry). 
  • Релиз Krum: Edge of Darkness — игры, созданной на Blender Game Engine, которая создавалась несколько лет.
  • «Выстрел» Krita, которую я бы смело назвал программой года. Разработка этого малоизвестного ранее пакета сейчас идет семимильными шагами, по популярности Krita догоняет GIMP, а по функциональности превосходит уже многие коммерческие продукты.
  • GIMP 2.9 с поддержкой новых цветовых режимов и OpenEXR.
  • Открытие исходников Unreal Engine 4 и PhysX.

Как я стал D-шником

С тех пор, как у меня появился компьютер, меня всегда интересовало то, как он работает. Надо сказать, что, хотя по профессии я художник, я всю жизнь интересуюсь механизмами и электроникой. Больше всего, конечно, меня привлекает создание и обработка изображений при помощи вычислительной техники. Но знакомства с одними только графическими редакторами мне оказалось мало – гораздо интереснее изучить и понять, как, собственно, все эти фотошопы и 3ds-max’ы устроены. Ну и, конечно, игры – куда же без них? Мне кажется, что компьютерные игры можно считать «восьмым искусством» после кино. Эволюция четко прослеживается: сначала были статические картины, затем появились «движущиеся» – кинематограф и мультипликация; логично допустить, что следующее поколение произведений искусства должно быть интерактивным: зритель сам должен принимать участие в раскрывающемся перед ним действии. Художники XXI века – это создатели игр…
Моим первым языком программирования был BASIC. Если быть точным, его надмножество Blitz BASIC – это, в сущности, была целая среда для создания простых приложений и игр. Тогда меня поразил сам факт того, что программа для компьютера – это не что иное, как набор инструкций, человекочитаемый текст на специальном языке! Впрочем, человекочитаемость иных текстов на BASIC можно поставить под сомнение – особенно помня слова Эдсгера Дейкстры: «Студентов, ранее изучавших Бейсик, практически невозможно обучить хорошему программированию. Как потенциальные программисты они подверглись необратимой умственной деградации…» И, тем не менее, эта ни с чем не сравнимая магия переменных, циклов, функций – равно как и желтых букв на синем фоне – сделала свое дело. Не говоря уже о трехмерной графике на основе DirectX 7, которая была встроена в язык – стоит ли говорить, насколько поражала воображение возможность вот так, запросто, безо всяких специальных знаний нарисовать на экране вращающийся кубик?..
До кубиков, правда, дело дошло нескоро – ведь необходимо было изучить азы. В этом большую помощь оказали школьные уроки информатики: помимо непосредственно азов (устройство компьютера, двоичная система, теория алгоритмов и т.д.), на них изучался всеми любимый и тепло вспоминаемый Pascal. Конечно, работать в DOS-режиме Windows 95 было еще тем удовольствием, но именно тогда началось мое знакомство со всем семейством паскалеподобных языков, что не могло не сказаться на будущих предпочтениях.
Однако Паскаль, при всех его достоинствах, не слишком хорошо подходил для моей главной цели – создания 2D-игр (трехмерная графика Blitz BASIC мне тогда была еще не по зубам). И тут мне посчастливилось наткнуться на Game Maker – специализированную среду для быстрой разработки игр с собственным редактором уровней, объектной системой, графическим редактором и встроенным скриптовым языком GML. Это была некая смесь Pascal и C++: можно было либо использовать фигурные скобки, либо begin/end. 
GML завоевал мое внимание на долгие пять лет. За это время я изучил Game Maker, что называется, вдоль и поперек, написал множество разнообразных 2D-демок (большинство из которых, увы, не сохранилось) и несколько полноценных игр – вы, кстати, можете ознакомиться с некоторыми тогдашними творениями на странице «Игры и демки». Меня разочаровывало только одно: отсутствие в программе приличных инструментов для рисования трехмерной графики. Был простенький 3D-режим на основе Direct3D – очень медленный, без доступа к программируемому конвейеру и другим современным «наворотам». Хотя, справедливости ради стоит отметить, что и на нем можно было делать неплохие вещи.

К этому времени появились специализированные 3D-движки, специально написанные для GM. В их числе был враппер популярной Delphi-библиотеки GLScene – Xtreme3D, поддерживавший практически все возможности оной: большое количество поддерживаемых форматов моделей и текстур, анимация, различные спецэффекты вроде динамической воды и системы частиц, менеджер управления ресурсами, встроенная проверка столкновений и даже физика на основе движка ODE. Я заинтересовался, изучил этот движок и даже открыл по нему сайт – http://xtreme3d.narod.ru(впрочем, после переезда на Ucoz он переживает не лучшие времена).

Так совпало, что именно в это время я познакомился с движением СПО (свободного программного обеспечения), и это навсегда изменило мое отношение к компьютерам. В какой-то момент моими привычными инструментами стали исключительно свободные программы – GIMP, Blender, OpenOffice.org и т.д. Я понял, что свобода изучения, изменения и распространения программ важнее их качества – хотя это, на первый взгляд, кажется абсурдным. Так утверждает Ричард Столлман – великий человек, основатель проекта GNU, автор лицензии GPL и создатель таких программ, как компилятор GCC и текстовый редактор Emacs. Над его словами часто иронизируют – но в итоге он всегда оказывается прав.

Закрытость и несвободность ПО – это зло, которое в равной степени вредит и его пользователям, и разработчикам. Пользователям – потому что они никогда не могут быть уверены, что закрытая программа не следит за ними, не ворует их информацию. А разработчикам – потому что закрытую программу труднее поддерживать, улучшать и исправлять: ведь этим занимаются только работники фирмы, владеющей программой. В результате ошибки в ПО исправляются годами – а то и вовсе не исправляются (и это не шутка). А если бы исходный код был публично доступен, любой квалифицированный программист сразу исправил бы найденный баг, отправил бы разработчикам патч, предложил бы рекомендации к улучшению тех или иных конструкций. И таких помощников были бы сотни – взгляните на крупные открытые проекты, развиваемые сообществом. Наконец, если фирма, занимающаяся разработкой программы, обанкротится, то сообщество ее пользователей, не имея на руках исходного кода, оказывается асболютно беспомощным – но зачастую люди продолжают использовать такие «мертвые» программы годами и десятилетиями, если им нет альтернативы. Такая ситуация, к примеру, наблюдается в нашей промышленности и оборонной технике. Вот такие абсурды порождает собственническое отношение к коду. 
Осознав все это, я пришел к выводу, что проприетарная Xtreme3D (к тому же, по всей видимости, заброшенная автором) – это тупиковый путь. В разработке ПО нужно пользоваться только свободными инструментами – и я начал изучать C++, выбрав этот язык как наиболее доступный из всех компилируемых. Моей первой IDE под Windows стала Dev-C++ — среда, основанная на инструментарии GCC/MinGW. Язык сразу поразил меня своей красотой и выразительностью – до этого я не имел никакого понятия о ООП и составных типах данных. Впечатлившись этим богатством и относительной простотой его использования (Dev-C++ позволяла устанавливать пакеты расширения – можно было найти пакеты для работы с OpenGL, DirectX, Irrlicht и т.д.), я загорелся идеей написать свой собственный 3D-движок для Game Maker. 

Дело шло неплохо – пока я в один прекрасный день не установил Linux. Это прямого отношения к моему программерскому хобби не имело, но в силу обстоятельств оказало на него сильное влияние. Несколько лет я большую часть времени проводил в Linux – и, естественно, писал под него программки, изучал линуксовые инструменты для разработчиков. К Game Maker уже практически не прикасался. В какой-то момент я понял, что нет больше смысла привязывать себя к Windows – и с тех пор мой движок существует как отдельный самодостаточный проект. Правда, он до сих пор не принял устоявшейся формы. Нет ни релизов, ни версий, ни постоянного репозитория, ни даже конкретного названия – это просто движок. Периодически я выпускаю какие-то демки и мини-игры, основанные на нем. Но окончательной версии, готовой к использованию сторонними лицами, пока нет.

Дело в том, что я неизлечимо болен перфекционизмом. Я постоянно улучшаю уже написанное и многократно переписываю все с нуля. Очень редко я бываю доволен сделанным, поэтому у меня не наберется и тысячи стабильных строк кода, которые бы я никогда не менял.
Другая причина – переход на язык D. Поскольку я любитель-одиночка, я могу позволить себе роскошь выбирать язык по своему вкусу – и я знаю, что все профессионалы завидуют таким, как я, хотя и тщательно скрывают это под маской снобизма и показной крутизны. К счастью, к тому моменту, когда я познакомился с D, у меня еще не была накоплена слишком большая кодовая база, и не было таких инструментов, от которых я не мог бы отказаться.

Мое представление о D изначально было весьма смутным: мне казалось, что это какое-то расширение С++ – наподобие того, как сам C++ является расширением C. Ознакомившись со статьей на Википедии, я почувствовал, что это именно то, что мне нужно: компиляция в машинный код, как у C++, и автоматическое управление памятью, как в Java (но надо сказать, что с Java мне на тот момент еще не приходилось работать, поэтому сравнивал я, естественно, с C++). И это не считая многочисленных полезных мелочей, которые D унаследовал от своих многочисленных предшественников – C#, Python, Haskell и др. Обрадовало то, что компилятор D компактен и легок в установке: распаковал архив в любой каталог и работай. В одном архиве – версии для Windows, Linux и FreeBSD, плюс подробная документация по языку и стандартной библиотеке.

Я пишу на D уже несколько лет и очень доволен языком. С каждым релизом он становится все лучше. Не берусь рекомендовать его тем, кто вынужден тянуть вагон legacy-кода на C++ (все-таки, полной бинарной совместимости с C++ у D нет), а также тем, кто такого багажа не имеет, но планирует на полном серьезе трудоустраиваться куда-нибудь в качестве программиста (там тоже будет legacy). Но тот, кто свободен от этого балласта – хакер, любитель, инди-разработчик – обязательно оценит язык по достоинству. Даже если у вас есть некоторое количество кода на «плюсах», без которого вы не можете жить, ничто не мешает потратить пару вечеров и портировать его на D. Сам я так и сделал – некоторые компоненты моей библиотеки dlib были портированы с C++.

dlib начиналась как библиотека линейной алгебры (манипуляции над векторами, матрицами, кватернионами и т.д.) – такие пишет для себя каждый игровой программист. Алгебра сейчас составляет пакет dlib.math. Потом были добавлены средства вычислительной геометрии (dlib.geometry) – игровому движку требуются функции обнаружения пересечений фигур, пространственные измерения и т.д. Вслед за ней – пакет для хранения и обработки изображений (dlib.image), который родился в результате изучения мной различных алгоритмов фильтрации. Эта часть dlib еще далека от завершения – к примеру, полностью поддерживается только формат PNG, не реализованы многие другие функции. Однако dlib.image уже вполне годится для использования в играх. Чем будет dlib в будущем? Вероятнее всего, универсальной вычислительной библиотекой: в перспективе я планирую добавить поддержку обработки и кодирования аудио и видео. dlib можно будет применять как backend для построения игровых движков, симуляторов, аудио- и видеопроигрывателей, программ-конвертеров, различных графических редакторов, инструментов монтажа и композитинга.