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 за сравнительно короткие сроки — уже сейчас на нем вполне можно делать серьезные проекты. Хочется верить, что он не заглохнет и будет развиваться.