У тех, кто работает с низкоуровневой графикой, сегодня на слуху WebGPU – новый кроссплатформенный API для доступа к возможностям современных видеокарт. WebGPU призван объединить Vulkan, Metal и D3D12 под унифицированным набором функций и станет не просто веб-стандартом, но и, в перспективе, неплохой заменой OpenGL: реализации этого API уже существуют в виде рабочих прототипов wgpu-native от Mozilla и Dawn от Google – любой может использовать их в своих собственных приложениях.
WebGPU имеет сравнительно простую архитектуру, доступную для понимания “простыми смертными” практически с первого прочтения заголовочного файла. Единственной проблемой до недавнего времени было отсутствие консенсуса по шейдерному языку – существующие реализации WebGPU использовали двоичное промежуточное представление SPIR-V от Khronos, а Apple настаивала на текстовом языке на основе WSL. Компромиссом стал WGSL (WebGPU Shading Language), высокоуровневый язык со строго определенной семантикой и буквальной трансляцией в/из SPIR-V. Многие разработчики оказались недовольны, так как SPIR-V уже успел стать привычным решением и оброс инструментами – сегодня можно компилировать в SPIR-V код на всех языках предыдущих поколений. Однако я вижу больше преимуществ, чем недостатков – перечислю некоторые из них.
- Использование SPIR-V усложняет жизнь при создании игрового движка, требует внедрения дополнительной стадии компиляции шейдеров на стороне разработчика. Референсным компилятором шейдеров считатеся GLSLang от Khronos, но его довольно трудно встроить в приложение как библиотеку, особенно если вы не пишете на C++ – приходится использовать GLSLang как приложение, и это усложняет тулчейн разработки, если нужна кроссплатформенность. Встроенный в API высокоуровневый язык решает эту проблему.
- WGSL разрабатывается как текстовый аналог SPIR-V – они имеют общий набор возможностей. Это значит, что не будет повторения ситуации с GLSL, когда язык по-разному обрабатывается в компиляторах от различных поставщиков. Сохраняется главное преимущество SPIR-V при высоком удобстве использования.
- Vulkan-диалект GLSL 4.60, являющийся де-факто стандартным языком под SPIR-V, имеет множество костылей и архаизмов – у WGSL более продуманный синтаксис, лишенный неявности и многозначности.
Синтаксис WGSL имеет много общего с Rust, особенно заголовки функций:
fn someFunc(x: i32) -> i32 {
//...
}
Типы объявляются через двоеточие после идентификатора, переменные – при помощи ключевого слова let
, константность подразумевается по умолчанию. Для изменяемых переменных есть ключевое слово var
. Система типов также пришла из вселенной Rust. Векторные типы имеют форму vec4<f32>
(вместо простого vec4
), что позволяет явным образом указать битовость используемых чисел. При этом можно объявить type vec4f = vec4<f32>;
и писать коротко, если вам так привычнее.
Кстати, очень порадовало, что есть вывод типов – можно не указывать тип переменной, если она тут же инициализируется:
let a = vec4<f32>(0.0, 0.0, 0.0, 1.0);
Вместо ключевого слова layout
– нотация с использованием двойных квадратных скобок, внутри которых записываются атрибуты location
и др.:
[[location(0)]] position: vec4<f32>;
Встроенные переменные конвейера обозначаются атрибутом builtin
, что весьма удобно при объявлении структур для хранения промежуточных результатов:
struct VertexOutput
{
[[builtin(position)]] position: vec4<f32>;
[[location(0)]] color: vec4<f32>;
};
Сравните это с GLSL, где для встроенных переменных используется зарезервированный префикс gl_
.
Структуры, являющиеся uniform-блоками, помечаются атрибутом block
:
[[block]] struct Uniforms {
//...
};
Прямым аналогом вулкановских set
и binding
являются атрибуты group
и binding
.
Vulkan/GLSL:
layout(set=0, binding=0) uniform Uniforms uniforms;
WGSL:
[[group(0), binding(0)]] var uniforms: Uniforms;
Программы на WGSL можно не разделять на два текста – вершинный и фрагментный шейдеры можно хранить в одном файле и, таким образом, использовать общие объявления. Для этого используется атрибут stage
. Названия самих входных точек могут быть произвольными, но чаще всего в примерах используют vs_main
и fs_main
.
[[stage(vertex)]]
fn vs_main() -> VertexOutput
{
//...
}
[[stage(fragment)]]
fn fs_main(input: FragmentInput) -> [[location(0)]] vec4<f32>
{
//...
}
Очень непривычно в WGSL записываются циклы:
var i = i32(0);
loop {
break if (i == 5);
//...
continuing {
i = i + 1;
}
}
Впрочем, на момент написания статьи обсуждается возможность поддержки классического for
.
Подведу итог: с первого взгляда WGSL кажется хорошим решением давней проблемы с языками шейдеров. Высокоуровневое представление SPIR-V – это отличная идея. Непривычный синтаксис и конструкции со спорным юзабилити могут усложнить портирование на WGSL готовых шейдеров, но в целом впечатление от языка весьма позитивное.