Представление информации и инструмента времени WebGPU
Добро пожаловать, в двух предыдущих постах я пошел на поиски пределов WGSL (оба
в спецификацииа также
из этого)
В этом посте я хочу представить простое приложение WebGPU, где вы можете создать шейдеры WGSL, перетаскивая несколько ползунков, чтобы увеличить их сложность.
Идея состоит в том, что время компиляции и сложность могут быть известны для вычислительных и рендеринговых трубопроводов, а также на разных этапах (вершина и фрагмент).
Это важно для меня, потому что я хотел знать, сколько стоит увеличить сложность шейдера и где можно начать, чтобы начать использовать буферы/текстуры для некоторых из них.
Сколько времени нужно, чтобы собрать 3MEGABYTE Vertex Shader?
Легко узнать, вот единственный результат холодных времен в Chrome (140.0.7339.215) от My Intel MacBook Pro (2019):
Как насчет 30 -мегабайтного вершин -шейдера ??

Это ошибки. Ах!

Простой инструмент, в духе потрясающего
Отчет WebGPUно не так исчерпывающе.
Вы можете найти его здесь:
Инструмент диагностики WebGPU (и эталона компиляции)Полем
Мое намерение состояло в том, чтобы составить его из 3 основных частей:
- Простой образец (вращающийся треугольник).
- Сложность шейдера ползунков и времен (компиляция, запрос на адаптер и создание трубопровода).
- Информация о устройствах и адаптере (функции и объявленные ограничения)
Вращающийся треугольник
Вращающийся треугольник сделан из 3 начальных шейдеров. Компьютный шейдер выводит вершины, затем вершино -шейдер преобразует их, и, наконец, фрагментный шейдер рисует его.
Я считаю это своего рода «Hello World» для WebGPU, но также и о простой программе, которая выполняет счастливый путь самого быстрого рендеринга (или близко к нему).
Идея вдоль приложения заключается в том, что этот треугольник никогда не меняетсяПолем Это всегда остается таким же, как и сложность шейдера.
Сложность Шейдера Слайдеры
Сложность шейдера может быть увеличена на каждой стадии трубопровода. Это включает в себя отделение вершин -шейдера от фрагментного шейдера на разные модули, чтобы позволить проверить, насколько они отличаются (если они вообще делают).

Строка шейдера создается до начала компиляции. Это делается в потоке веб -работников, чтобы поддерживать реагирование (как можно больше) по мере того, как увеличивается время прохождения памяти и строки.
Я хотел, чтобы эти ползунки были смещены в сторону сложности. Небольшое изменение в них должно немного увеличить размер шейдера. Сделать их целенаправленно неуравновешенными, чтобы гарантировать, что пределы устройств могут быть проверены.
Какой код WGSL генерируют ползунки?
Они разделены на три категории:
- Количество функций – сброс N функций с 1
f32Аргумент каждый, кто возвращает единственную переменную, объявленную в теле. - Количество операторов – для каждой функции в 1., создает n операторов, которые просто выполняют простое назначение.
- Глубина выражения – для каждого утверждения в 2. Расширяет его, чтобы иметь n сумм.
Поэтому, если мы установим их все на 8 (8 функций, каждая с 8 операторами, каждая с 8 суммами), он создает файл 5KILOBYTE.
Вы можете проверить код генератора
здесьПолем
Или нажмите здесь, чтобы увидеть выход, произведенный с тремя ползунками в значении 8
struct Vertex { position: vec3f, color: vec3f, }
struct Params { angle: f32, }
// Storage buffer that will hold our generated vertices @group(0) @binding(0) var
vertices: array ; @group(0) @binding(1) var params: Params; fn function0(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function1(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function2(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function3(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function4(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function5(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function6(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
fn function7(a: f32) -> f32 { var tmp: f32 = 0.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; tmp += 0.0+1.0+2.0+3.0+4.0+5.0+6.0+7.0; return a + tmp; }
// Compute shader that generates a triangle's vertices @compute @workgroup_size(1) fn compute_main(@builtin(global_invocation_id) global_id: vec3u) { var accum: f32 = 0.0; accum += function0(8.0); accum += function1(8.0); accum += function2(8.0); accum += function3(8.0); accum += function4(8.0); accum += function5(8.0); accum += function6(8.0); accum += function7(8.0);
// Generate 3 vertices for a triangle // We only need one invocation for this simple example if (global_id.x == 0) { let angle = params.angle + accum * 0.0001; let cos_angle = cos(angle); let sin_angle = sin(angle); let base_positions = array
( vec2f(0.0, 0.5), vec2f(-0.5, -0.8), vec2f(0.5, -0.5) ); let top = vec2f( base_positions[0].x * cos_angle - base_positions[0].y * sin_angle, base_positions[0].x * sin_angle + base_positions[0].y * cos_angle, ); let left = vec2f( base_positions[1].x * cos_angle - base_positions[1].y * sin_angle, base_positions[1].x * sin_angle + base_positions[1].y * cos_angle, ); let right = vec2f( base_positions[2].x * cos_angle - base_positions[2].y * sin_angle, base_positions[2].x * sin_angle + base_positions[2].y * cos_angle, ); let depth = clamp(accum * 0.001, -0.5, 0.5); // Top vertex (red) vertices[0].position = vec3f(top, depth); vertices[0].color = vec3f(1.0, 0.0, 0.0); // Bottom-left vertex (green) vertices[1].position = vec3f(left, depth); vertices[1].color = vec3f(0.0, 1.0, 0.0); // Bottom-right vertex (blue) vertices[2].position = vec3f(right, depth); vertices[2].color = vec3f(0.0, 0.0, 1.0);}}
Информация о устройстве и адаптере
Особенности и ограничения – это хорошая вещь, чтобы иметь в таких инструментах. Они не используются, просто общаются. Многие из них я не знаю, что они делают, поэтому я включил ссылку на соответствующую главу Spec Spec.
Важной частью здесь является то, что я хотел разделить адаптер ограничения от устройство ограничения. Они могут быть разными, и знание этих различий может быть важным, когда мы стремимся к верхнему концу участка.
В WebGPU пределы адаптера можно рассматривать как объявленные значения того, что может существовать, в то время как ограничения устройства можно рассматривать как то, с чем мы должны работать.
Они могут быть запрошены через
Требуемые limits
на творении.
Еще одна важная часть мотивации для этого – это использовать
скукаПолем Минимальная структура JS, которую я создал как забавный проект, который пытается сделать то, что делают основные рамки JS, но с другим подходом только потому, почему нет? (Нет связывания, нет компиляции, без министрации и небольшого размера).
Было весело и круто видеть, что он работает без комплекта/компиляции при обработке сложности WebGPU.
Заключение
Иди, поиграй с этим немного здесь И дайте мне знать, как ваше устройство и браузер реагируют на массивные шейдеры.
Я обнаружил, что существует значительная разница между холодным и теплым временем компиляции. Это различие с холодным и теплым временем может быть интересно для дальнейшего изучения, и, возможно, его можно даже использовать/эксплуатировать, чтобы скрыть чрезвычайно сложные шейдеры, разделяя их на многих небольших трубопроводах (я думаю, просто дикая гипотеза).
Этот пост завершает усилия WGSL. Для следующих я мог бы представить двигатель рендеринга, который я работаю, который пытается выступить с тем же духом, что и скука для фреймворков JS. Спасибо, что прочитали все это и следите за обновлениями!
2025-10-06 21:14:00
1759789768
#Webgpu #цена #на #компиляцию #WGSL #Hugo #Daniel
Читайте также

