Что выглядит читабельнее? Это:
struct VertexOutput
{
@builtin(position) position: vec4<f32>,
@location(0) fragmentPosition: vec4<f32>
};
@vertex
fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput
{
let x = f32(i32(vertexIndex) - 1);
let y = f32(i32(vertexIndex & 1u) * 2 - 1);
var output: VertexOutput;
output.position = vec4<f32>(x * 0.5, y * 0.5, 0.0, 1.0);
output.fragmentPosition = 0.5 * (vec4<f32>(x, y, 0.0, 1.0) + 1.0);
return output;
}
…или это:
struct VertexOutput
{
float4 position: SV_Position;
float4 fragmentPosition;
};
[shader("vertex")]
VertexOutput vertexMain(uint vertexIndex: SV_VertexID)
{
let x = float(int(vertexIndex) - 1);
let y = float(int(vertexIndex & 1u) * 2 - 1);
VertexOutput output;
output.position = float4(x * 0.5, y * 0.5, 0.0, 1.0);
output.fragmentPosition = 0.5 * (float4(x, y, 0.0, 1.0) + 1.0);
return output;
}
Второй листинг, на мой взгляд, выигрывает сравнение. Это код на Slang, HLSL-подобном языке, который позиционируется в качестве платформонезависимой основы для написания одних и тех же шейдеров под любые графические API, включая D3D12, Vulkan, Metal, OpenGL, а с недавних пор и WebGPU. Идея, конечно, не новая – если вы писали шейдеры в нулевые, то, наверное, помните Cg от NVIDIA – но с со времен попыток написать универсальный компилятор в низкоуровневые шейдерные языки утекло немало воды и сменилось несколько поколений графического железа. Сейчас, в эпоху Vulkan и SPIR-V, кросс-компиляция шейдеров актуальна как никогда: старичок GLSL уже сдает позиции, для новых стандартов создаются новые языки – индустрия переживает очередной этап фрагментации.
Я уже писал о своих впечатлениях от WGSL, встроенного шейдерного языка WebGPU, и многие его конструкции мне до сих кажутся спорными и неудобными. Особенно бесят типы вида vec4<f32>
. Не так давно поддержка WGSL была добавлена в компилятор Slang, в связи с чем я теперь всерьез рассматриваю этот язык как основной для создания графического движка на WebGPU.
Главная киллер-фича Slang – это, конечно, модули. Причем, что интересно, есть и препроцессор а ля C с директивами #include
, #ifdef
и др. От HLSL и GLSL язык выгодно отличается большим набором фич, присущих современным высокоуровневым языкам: поддержкой вывода типов, пространств имен, функций-членов структур (с неизменяемым по умолчанию неявным this
– по-моему, отличная идея!), а также конструкторов, геттеров/сеттеров и даже перегрузки операторов. Есть дженерики, интерфейсы, кортежи. Интересен тип Optional
, который дополняет любой другой тип поддержкой значения none – чтобы можно было указать отсутствие какого-либо значения. Для SPIR-V и CUDA в языке есть ограниченная поддержка указателей. Очень полезный инструмент – декоратор ForceInline
, который заменяет вызов функции подстановкой ее кода. Наконец, в языке есть автоматическое дифференцирование, которое используется в задачах машинного обучения.
Старую как мир проблему некоммутативности умножения матриц и векторов в Slang решили следующим образом: оператор *
всегда означает произведение двух матриц, как принято в математике. Чтобы трансформировать вектор матрицей, нужно вместо m * v
писать mul(v, m)
. Для такого старого ветерана OpenGL, как я, слегка непривычно, но жить можно 🙂
Репозиторий
Документация по языку