paint-brush
Переход с WebGL на WebGPUк@dmitrii
6,377 чтения
6,377 чтения

Переход с WebGL на WebGPU

к Dmitrii Ivashchenko13m2023/12/20
Read on Terminal Reader

Слишком долго; Читать

В этом руководстве объясняется переход от WebGL к WebGPU, рассматриваются ключевые различия, общие концепции и практические советы. Поскольку WebGPU становится будущим веб-графики, эта статья предлагает бесценную информацию как для инженеров-программистов, так и для менеджеров проектов.
featured image - Переход с WebGL на WebGPU
Dmitrii Ivashchenko HackerNoon profile picture

Переход на будущий WebGPU означает больше, чем просто переключение графических API. Это также шаг в будущее веб-графики. Но эта миграция окажется лучше при подготовке и понимании — и эта статья поможет вам подготовиться.


Привет всем, меня зовут Дмитрий Иващенко, я инженер-программист в MY.GAMES. В этой статье мы обсудим различия между WebGL и будущим WebGPU, а также расскажем, как подготовить ваш проект к миграции.


Обзор контента

  1. Хронология WebGL и WebGPU

  2. Текущее состояние WebGPU и что будет дальше

  3. Концептуальные различия высокого уровня

  4. Инициализация

    • WebGL: контекстная модель

    • WebGPU: модель устройства.

  5. Программы и конвейеры

    • WebGL: программа

    • WebGPU: конвейер

  6. Униформа

    • Униформа в WebGL 1.

    • Униформа в WebGL 2.

    • Униформа в WebGPU

  7. Шейдеры

    • Язык шейдеров: GLSL против WGSL.

    • Сравнение типов данных

    • Структуры

    • Объявления функций

    • Встроенные функции

    • Преобразование шейдеров

  8. Конвенционные различия

  9. Текстуры

    • Пространство видового экрана

    • Пространства обрезки

  10. Советы и рекомендации по WebGPU

    • Минимизируйте количество используемых вами конвейеров.

    • Создавайте конвейеры заранее

    • Используйте RenderBundles.

  11. Краткое содержание


Хронология WebGL и WebGPU

WebGL , как и многие другие веб-технологии, имеет корни, уходящие в далекое прошлое. Чтобы понять динамику и мотивацию перехода к WebGPU, полезно сначала взглянуть на историю разработки WebGL:


  • OpenGL Desktop (1993) Дебют настольной версии OpenGL.
  • WebGL 1.0 (2011 г.) : это была первая стабильная версия WebGL, основанная на OpenGL ES 2.0, которая сама была представлена в 2007 году. Она предоставила веб-разработчикам возможность использовать 3D-графику непосредственно в браузерах без необходимости использования дополнительных плагинов.
  • WebGL 2.0 (2017 г.) : представленный через шесть лет после первой версии, WebGL 2.0 был основан на OpenGL ES 3.0 (2012 г.). Эта версия принесла с собой ряд улучшений и новых возможностей, делающих 3D-графику в Интернете еще более мощной.


В последние годы наблюдается всплеск интереса к новым графическим API, которые предоставляют разработчикам больше контроля и гибкости:


  • Vulkan (2016) : этот кроссплатформенный API, созданный группой Khronos, является «преемником» OpenGL. Vulkan обеспечивает низкоуровневый доступ к ресурсам графического оборудования, позволяя создавать высокопроизводительные приложения с лучшим контролем над графическим оборудованием.
  • D3D12 (2015 г.) : этот API был создан Microsoft и предназначен исключительно для Windows и Xbox. D3D12 является преемником D3D10/11 и предоставляет разработчикам более глубокий контроль над графическими ресурсами.
  • Metal (2014 г.) : Metal — это эксклюзивный API для устройств Apple, созданный Apple. Он был разработан с учетом максимальной производительности оборудования Apple.


Текущее состояние WebGPU и что будет дальше

Сегодня WebGPU доступен на нескольких платформах, таких как Windows, Mac и ChromeOS, через браузеры Google Chrome и Microsoft Edge, начиная с версии 113. Поддержка Linux и Android ожидается в ближайшем будущем.


Вот некоторые из движков, которые уже поддерживают (или предлагают экспериментальную поддержку) WebGPU:


  • Babylon JS : Полная поддержка WebGPU.
  • ThreeJS : на данный момент экспериментальная поддержка.
  • PlayCanvas : В разработке, но с очень многообещающими перспективами.
  • Unity : очень ранняя и экспериментальная поддержка WebGPU была анонсирована в альфа-версии 2023.2.
  • Cocos Creator 3.6.2 : официально поддерживает WebGPU, что делает его одним из пионеров в этой области.
  • Construct : в настоящее время поддерживается только в версии 113+ для Windows, macOS и ChromeOS.



Учитывая это, переход на WebGPU или хотя бы подготовка проектов к такому переходу представляется своевременным шагом в ближайшем будущем.


Концептуальные различия высокого уровня

Давайте уменьшим масштаб и взглянем на некоторые концептуальные различия высокого уровня между WebGL и WebGPU, начиная с инициализации.

Инициализация

Приступая к работе с графическими API, одним из первых шагов является инициализация основного объекта для взаимодействия. Этот процесс различается в WebGL и WebGPU, но имеет некоторые особенности для обеих систем.

WebGL: контекстная модель

В WebGL этот объект известен как «контекст», который по сути представляет собой интерфейс для рисования на элементе холста HTML5. Получить этот контекст довольно просто:

 const gl = canvas.getContext('webgl');


Контекст WebGL фактически привязан к конкретному холсту. Это означает, что если вам нужно выполнить рендеринг на нескольких холстах, вам понадобится несколько контекстов.

WebGPU: модель устройства

WebGPU представляет новую концепцию под названием «устройство». Это устройство представляет собой абстракцию графического процессора, с которой вы будете взаимодействовать. Процесс инициализации немного сложнее, чем в WebGL, но обеспечивает большую гибкость:

 const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); const context = canvas.getContext('webgpu'); context.configure({ device, format: 'bgra8unorm', });


Одним из преимуществ этой модели является то, что одно устройство может выполнять рендеринг на нескольких холстах или даже ни на одном. Это обеспечивает дополнительную гибкость; например, одно устройство может управлять рендерингом в нескольких окнах или контекстах.



Программы и конвейеры

WebGL и WebGPU представляют разные подходы к управлению и организации графического конвейера.

WebGL: Программа

В WebGL основное внимание уделяется программе шейдеров. Программа объединяет вершинные и фрагментные шейдеры, определяя, как должны быть преобразованы вершины и как должен быть окрашен каждый пиксель.

 const program = gl.createProgram(); gl.attachShader(program, vertShader); gl.attachShader(program, fragShader); gl.bindAttribLocation(program, 'position', 0); gl.linkProgram(program);


Шаги по созданию программы в WebGL:


  1. Создание шейдеров : Исходный код шейдеров написан и скомпилирован.
  2. Создание программы : скомпилированные шейдеры прикрепляются к программе, а затем компонуются.
  3. Использование программы : программа активируется перед рендерингом.
  4. Передача данных : Данные передаются в активированную программу.


Этот процесс обеспечивает гибкое управление графикой, но также может быть сложным и подвержен ошибкам, особенно для больших и сложных проектов.

WebGPU: конвейер

WebGPU вводит концепцию «конвейера» вместо отдельной программы. Этот конвейер объединяет не только шейдеры, но и другую информацию, которая в WebGL представлена как состояния. Итак, создание конвейера в WebGPU выглядит сложнее:

 const pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { module: shaderModule, entryPoint: 'vertexMain', buffers: [{ arrayStride: 12, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x3' }] }], }, fragment: { module: shaderModule, entryPoint: 'fragmentMain', targets: [{ format, }], }, });


Шаги по созданию конвейера в WebGPU:


  1. Определение шейдера . Исходный код шейдера пишется и компилируется аналогично тому, как это делается в WebGL.
  2. Создание конвейера : шейдеры и другие параметры рендеринга объединяются в конвейер.
  3. Использование конвейера : конвейер активируется перед рендерингом.


В то время как WebGL разделяет каждый аспект рендеринга, WebGPU пытается инкапсулировать больше аспектов в один объект, делая систему более модульной и гибкой. Вместо отдельного управления шейдерами и состояниями рендеринга, как это сделано в WebGL, WebGPU объединяет всё в один объект конвейера. Это делает процесс более предсказуемым и менее подверженным ошибкам:



Униформа

Униформные переменные предоставляют постоянные данные, доступные всем экземплярам шейдера.

Униформа в WebGL 1

В базовом WebGL у нас есть возможность устанавливать uniform -переменные непосредственно через вызовы API.


ГЛСЛ :

 uniform vec3 u_LightPos; uniform vec3 u_LightDir; uniform vec3 u_LightColor;


JavaScript :

 const location = gl.getUniformLocation(p, "u_LightPos"); gl.uniform3fv(location, [100, 300, 500]);


Этот метод прост, но требует нескольких вызовов API для каждой uniform переменной.

Униформа в WebGL 2

С появлением WebGL 2 у нас появилась возможность группировать uniform переменные в буферы. Хотя вы по-прежнему можете использовать отдельные шейдеры униформ, лучший вариант — сгруппировать разные униформы в более крупную структуру с помощью буферов униформ. Затем вы сразу отправляете все эти однородные данные в графический процессор, аналогично тому, как вы можете загрузить буфер вершин в WebGL 1. Это имеет несколько преимуществ в производительности, таких как сокращение вызовов API и приближение к тому, как работают современные графические процессоры.


ГЛСЛ :

 layout(std140) uniform ub_Params { vec4 u_LightPos; vec4 u_LightDir; vec4 u_LightColor; };


JavaScript :

 gl.bindBufferBase(gl.UNIFORM_BUFFER, 1, gl.createBuffer());


Чтобы связать подмножества большого универсального буфера в WebGL 2, вы можете использовать специальный вызов API, известный bindBufferRange . В WebGPU есть нечто подобное, называемое динамическими универсальными смещениями буфера, где вы можете передать список смещений при вызове API setBindGroup .



Униформа в WebGPU

WebGPU предлагает нам еще лучший метод. В этом контексте отдельные uniform переменные больше не поддерживаются, и работа ведется исключительно через uniform буферы.


РГСЛ :

 [[block]] struct Params { u_LightPos : vec4<f32>; u_LightColor : vec4<f32>; u_LightDirection : vec4<f32>; }; [[group(0), binding(0)]] var<uniform> ub_Params : Params;


JavaScript :

 const buffer = device.createBuffer({ usage: GPUBufferUsage.UNIFORM, size: 8 });


Современные графические процессоры предпочитают, чтобы данные загружались одним большим блоком, а не множеством маленьких. Вместо того, чтобы каждый раз заново создавать и перепривязывать небольшие буферы, рассмотрите возможность создания одного большого буфера и использования разных его частей для разных вызовов отрисовки. Такой подход позволяет значительно повысить производительность.


WebGL более важен: он сбрасывает глобальное состояние при каждом вызове и стремится быть как можно более простым. WebGPU, с другой стороны, стремится быть более объектно-ориентированным и ориентированным на повторное использование ресурсов, что приводит к повышению эффективности.


Переход с WebGL на WebGPU может показаться трудным из-за различий в методах. Однако, начав с перехода на WebGL 2 в качестве промежуточного шага, можно упростить себе жизнь.



Шейдеры

Переход с WebGL на WebGPU требует изменений не только в API, но и в шейдерах. Спецификация WGSL создана для того, чтобы сделать этот переход плавным и интуитивно понятным, сохраняя при этом эффективность и производительность современных графических процессоров.

Язык шейдеров: GLSL против WGSL

WGSL спроектирован как мост между WebGPU и собственными графическими API. По сравнению с GLSL, WGSL выглядит немного более многословным, но структура остается знакомой.


Вот пример шейдера для текстуры:


ГЛСЛ :

 sampler2D myTexture; varying vec2 vTexCoord; void main() { return texture(myTexture, vTexCoord); }


РГСЛ :

 [[group(0), binding(0)]] var mySampler: sampler; [[group(0), binding(1)]] var myTexture: texture_2d<f32>; [[stage(fragment)]] fn main([[location(0)]] vTexCoord: vec2<f32>) -> [[location(0)]] vec4<f32> { return textureSample(myTexture, mySampler, vTexCoord); } 



Сравнение типов данных

В таблице ниже показано сравнение базового и матричного типов данных в GLSL и WGSL:



Переход от GLSL к WGSL демонстрирует стремление к более строгой типизации и явному определению размеров данных, что может улучшить читаемость кода и снизить вероятность ошибок.



Структуры

Синтаксис объявления структур также изменился:


ГЛСЛ:

 struct Light { vec3 position; vec4 color; float attenuation; vec3 direction; float innerAngle; float angle; float range; };


РГСЛ:

 struct Light { position: vec3<f32>, color: vec4<f32>, attenuation: f32, direction: vec3<f32>, innerAngle: f32, angle: f32, range: f32, };


Введение явного синтаксиса для объявления полей в структурах WGSL подчеркивает стремление к большей ясности и упрощает понимание структур данных в шейдерах.



Объявления функций

ГЛСЛ :

 float saturate(float x) { return clamp(x, 0.0, 1.0); }


РГСЛ :

 fn saturate(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }


Изменение синтаксиса функций в WGSL отражает унификацию подхода к объявлениям и возвращаемым значениям, делая код более последовательным и предсказуемым.



Встроенные функции

В WGSL многие встроенные функции GLSL были переименованы или заменены. Например:



Переименование встроенных функций в WGSL не только упрощает их имена, но и делает их более интуитивно понятными, что может облегчить процесс перехода для разработчиков, знакомых с другими графическими API.



Преобразование шейдеров

Тем, кто планирует конвертировать свои проекты из WebGL в WebGPU, важно знать, что существуют инструменты для автоматического конвертирования GLSL в WGSL, например **[Naga](https://github.com/gfx-rs/naga /)** — библиотека Rust для преобразования GLSL в WGSL. Он даже может работать прямо в вашем браузере с помощью WebAssembly.


Вот конечные точки, поддерживаемые Naga:



Конвенционные различия

Текстуры

После миграции вы можете столкнуться с сюрпризом в виде перевернутых изображений. Те, кто когда-либо портировал приложения из OpenGL в Direct3D (или наоборот), уже сталкивались с этой классической проблемой.


В контексте OpenGL и WebGL текстуры обычно загружаются таким образом, что начальный пиксель соответствует левому нижнему углу. Однако на практике многие разработчики загружают изображения, начиная с верхнего левого угла, что приводит к ошибке перевернутого изображения. Тем не менее, эта ошибка может быть компенсирована другими факторами, в конечном итоге нивелирующими проблему.



В отличие от OpenGL, такие системы, как Direct3D и Metal, традиционно используют верхний левый угол в качестве отправной точки для текстур. Учитывая, что многим разработчикам такой подход кажется наиболее интуитивным, создатели WebGPU решили последовать этой практике.

Пространство видового экрана

Если ваш код WebGL выбирает пиксели из буфера кадра, будьте готовы к тому, что WebGPU использует другую систему координат. Возможно, вам придется применить простую операцию «y = 1,0 - y», чтобы исправить координаты.



Отсечь пробелы

Когда разработчик сталкивается с проблемой, когда объекты обрезаются или исчезают раньше, чем ожидалось, это часто связано с различиями в области глубины. Разница между WebGL и WebGPU заключается в том, как они определяют диапазон глубины клипового пространства. В то время как WebGL использует диапазон от -1 до 1, WebGPU использует диапазон от 0 до 1, аналогично другим графическим API, таким как Direct3D, Metal и Vulkan. Такое решение было принято из-за ряда преимуществ использования диапазона от 0 до 1, выявленных при работе с другими графическими API.



Основная ответственность за преобразование позиций вашей модели в пространство клипа лежит на матрице проекции. Самый простой способ адаптировать ваш код — убедиться, что результаты вашей матрицы проекции находятся в диапазоне от 0 до 1. Для тех, кто использует такие библиотеки, как gl-matrix, есть простое решение: вместо использования функции perspective вы можете использовать perspectiveZO ; аналогичные функции доступны и для других матричных операций.

 if (webGPU) { // Creates a matrix for a symetric perspective-view frustum // using left-handed coordinates mat4.perspectiveZO(out, Math.PI / 4, ...); } else { // Creates a matrix for a symetric perspective-view frustum // based on the default handedness and default near // and far clip planes definition. mat4.perspective(out, Math.PI / 4, …); }


Однако иногда у вас может быть существующая матрица проекции, и вы не можете изменить ее источник. В этом случае, чтобы преобразовать его в диапазон от 0 до 1, вы можете предварительно умножить свою матрицу проекции на другую матрицу, корректирующую диапазон глубины.



Советы и рекомендации по WebGPU

Теперь давайте обсудим некоторые советы и рекомендации по работе с WebGPU.

Минимизируйте количество используемых вами конвейеров.

Чем больше конвейеров вы используете, тем больше у вас переключений состояний и тем меньше производительность; это может быть непросто, в зависимости от того, откуда берутся ваши активы.

Создайте конвейеры заранее

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


Или, что еще лучше, используйте варианты create*PipelineAsync ! Промис выполняется, когда конвейер готов к использованию, без каких-либо остановок.

 device.createComputePipelineAsync({ compute: { module: shaderModule, entryPoint: 'computeMain' } }).then((pipeline) => { const commandEncoder = device.createCommandEncoder(); const passEncoder = commandEncoder.beginComputePass(); passEncoder.setPipeline(pipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatchWorkgroups(128); passEncoder.end(); device.queue.submit([commandEncoder.finish()]); });

Используйте рендербандлы

Пакеты рендеринга — это предварительно записанные частичные проходы рендеринга многократного использования. Они могут содержать большинство команд рендеринга (за исключением таких вещей, как настройка области просмотра) и могут быть «воспроизведены» позже как часть фактического прохода рендеринга.


 const renderPass = encoder.beginRenderPass(descriptor); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.executeBundles([renderBundle]); renderPass.setPipeline(renderPipeline); renderPass.draw(3); renderPass.end();


Пакеты рендеринга могут выполняться вместе с обычными командами прохода рендеринга. Состояние прохождения рендеринга сбрасывается до значений по умолчанию до и после каждого выполнения пакета. В первую очередь это сделано для уменьшения накладных расходов JavaScript на рисование. Производительность графического процессора остается неизменной независимо от подхода.

Краткое содержание

Переход на WebGPU означает больше, чем просто переключение графических API. Это также шаг в будущее веб-графики, объединяющий успешные функции и методы различных графических API. Этот переход требует глубокого понимания технических и философских изменений, но преимущества значительны.

Полезные ресурсы и ссылки: