API WebGPU
Ограниченная доступность
Эта функция не является базовой, поскольку она не работает во всех широко используемых браузерах.
Экспериментально: Это экспериментальная технология
Перед использованием в рабочей среде тщательно проверьте таблицу совместимости браузеров.
Безопасный контекст: Эта функция доступна только в безопасных контекстах (HTTPS) в поддерживающих браузерах (или во всех поддерживающих).
API WebGPU позволяет веб-разработчикам использовать графический процессор (GPU) для выполнения высокопроизводительных вычислений и отрисовки сложных изображений в браузере.
WebGPU является преемником WebGL, обеспечивая лучшую совместимость с современными GPU, поддержку вычислений на GPU общего назначения, более быстрые операции и доступ к более продвинутым функциям GPU.
Концепции и использование
Можно с уверенностью сказать, что WebGL произвел революцию в веб-графике после своего появления примерно в 2011 году. WebGL является JavaScript-портом графической библиотеки OpenGL ES 2.0, позволяя веб-страницам передавать вычисления для отрисовки непосредственно на графический процессор устройства для очень быстрого обработки и отображения результата внутри элемента <canvas>.
WebGL и язык GLSL, используемый для написания шейдерного кода WebGL, сложны, поэтому для упрощения создания приложений WebGL было создано несколько библиотек WebGL: Популярные примеры включают Three.js, Babylon.js и PlayCanvas. Разработчики использовали эти инструменты для создания интерактивных веб-приложений, 3D-игр, музыкальных видеороликов, обучающих и моделирующих инструментов, VR- и AR-опытов и многого другого.
Однако WebGL имеет некоторые фундаментальные проблемы, которые необходимо было решить:
- После выпуска WebGL появилось новое поколение собственных API графических процессоров — наиболее популярными из которых являются Direct3D 12 Майкрософт, Metal Apple и Vulkan Khronos Group — предоставляющие множество новых функций. Для OpenGL (и, следовательно, WebGL) больше обновлений не планируется, поэтому он не получит эти новые функции. WebGPU, с другой стороны, будет получать новые функции в дальнейшем.
- WebGL полностью ориентирован на отрисовку графики на холсте. Он не очень хорошо справляется с вычислениями на GPU общего назначения (GPGPU). Вычисления GPGPU становятся все более важными для многих различных случаев использования, например, для тех, которые основаны на моделях машинного обучения.
- Приложения 3D-графики становятся все более требовательными, как с точки зрения количества объектов, отображаемых одновременно, так и с точки зрения использования новых функций отрисовки.
WebGPU решает эти проблемы, предоставляя обновлённую архитектуру общего назначения, совместимую с современными API GPU, которая ощущается более "веб-ориентированной". Она поддерживает отрисовку графики, но также имеет поддержку вычислений GPGPU. Отрисовка отдельных объектов значительно дешевле со стороны ЦП, и она поддерживает современные функции отрисовки GPU, такие как частицы на основе вычислений и эффекты после обработки, такие как цветовые эффекты, резкость и симуляция глубины резкости. Кроме того, она может обрабатывать дорогостоящие вычисления, такие как обрезка и преобразование моделей с анимацией костей, непосредственно на GPU.
Общая модель
Существует несколько уровней абстракции между графическим процессором устройства и веб-браузером, в котором выполняется API WebGPU. Полезно понять их, когда вы начинаете изучать WebGPU:
-
Физические устройства имеют GPU. Большинство устройств имеют только один GPU, но некоторые имеют более одного. Доступны различные типы GPU:
- Встроенные GPU, которые находятся на той же плате, что и ЦП, и делят его память.
- Дискретные GPU, которые находятся на своей собственной плате, отдельно от ЦП.
- «Виртуальные» GPU, реализованные на ЦП.
Примечание: Приведенная выше диаграмма предполагает устройство с одним GPU.
-
Нативный API GPU, являющийся частью ОС (например, Metal на macOS), представляет собой интерфейс программирования, позволяющий нативным приложениям использовать возможности GPU. Инструкции API отправляются на GPU (и ответы принимаются) через драйвер. Система может иметь несколько нативных API ОС и драйверов, доступных для взаимодействия с GPU, хотя приведенная выше диаграмма предполагает устройство с одним нативным API/драйвером.
-
Реализация WebGPU в браузере отвечает за взаимодействие с GPU через драйвер нативного API GPU. Адаптер WebGPU фактически представляет собой физический GPU и драйвер, доступный на базовой системе, в вашем коде.
-
Логическое устройство представляет собой абстракцию, с помощью которой одно веб-приложение может получить доступ к возможностям GPU в компартментированном виде. Логические устройства необходимы для обеспечения возможностей мультиплексирования. Физический GPU устройства используется многими приложениями и процессами одновременно, включая, потенциально, множество веб-приложений. Каждое веб-приложение должно иметь возможность изолированно получать доступ к WebGPU по соображениям безопасности и логики.
Получение доступа к устройству
Логическое устройство — представленное объектом GPUDevice — является основой, с помощью которой веб-приложение получает доступ ко всем функциям WebGPU. Доступ к устройству осуществляется следующим образом:
- Свойство
Navigator.gpu(илиWorkerNavigator.gpu, если вы используете функциональность WebGPU внутри работника) возвращает объектGPUдля текущего контекста. - Вы получаете доступ к адаптеру через метод
GPU.requestAdapter(). Этот метод принимает необязательный объект настроек, позволяющий, например, запросить высокопроизводительный или энергоэффективный адаптер. Если его нет, устройство предоставит доступ к адаптеру по умолчанию, который подходит для большинства случаев. - Устройство можно запросить через
GPUAdapter.requestDevice(). Этот метод также принимает объект параметров (называемый описателем), который можно использовать для указания точных функций и ограничений, которые вы хотите иметь у логического устройства. Если его нет, предоставленное устройство будет иметь разумную спецификацию общего назначения, которая подходит для большинства целей.
Объединив это с проверкой функций, вышеуказанный процесс можно реализовать следующим образом:
async function init() {
if (!navigator.gpu) {
throw Error("WebGPU not supported.");
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw Error("Couldn't request WebGPU adapter.");
}
const device = await adapter.requestDevice();
//...
}
Структура приложения WebGPU: конвейеры и шейдеры
Пайплайн — это логическая структура, содержащая программируемые этапы, которые выполняются для выполнения работы вашей программы. WebGPU в настоящее время может обрабатывать два типа пайплайнов:
-
Рендеринговый пайплайн рендерит графику, обычно в элементе
<canvas>, но он также может рендерить графику офскрин. Он имеет два основных этапа:-
Этап вершин, на котором вершинный шейдер принимает данные позиционирования, передаваемые в GPU, и использует их для позиционирования ряда вершин в 3D-пространстве, применяя указанные эффекты, такие как вращение, смещение или перспектива. Затем GPU собирает вершины в примитивы, такие как треугольники (основной строительный блок рендерной графики), и растрирует их, чтобы определить, какие пиксели должен покрывать каждый примитив на холсте.
-
Этап фрагментов, на котором фрагментный шейдер вычисляет цвет для каждого пикселя, покрытого примитивами, созданными вершинным шейдером. Эти вычисления часто используют входные данные, такие как изображения (в виде текстур), которые предоставляют детали поверхности, а также положение и цвет виртуальных источников света.
-
-
Пайплайн вычислений предназначен для общих вычислений. Пайплайн вычислений содержит один этап вычислений, на котором шейдер вычислений принимает общие данные, обрабатывает их параллельно на заданном количестве рабочих групп, а затем возвращает результат в одном или нескольких буферах. Буферы могут содержать любые данные.
Упомянутые выше шейдеры представляют собой наборы инструкций, обрабатываемых GPU. Шейдеры WebGPU написаны на низкоуровневом языке, похожем на Rust, под названием Язык шейдеров WebGPU (WGSL).
Существует несколько способов архитектуры приложения WebGPU, но процесс, скорее всего, будет содержать следующие шаги:
- Создать модули шейдеров: Напишите код шейдера на WGSL и упакуйте его в один или несколько модулей шейдеров.
-
Получить и настроить контекст холста: Получите контекст
webgpuэлемента<canvas>и настройте его для получения информации о том, какую графику рендерить от вашего логического устройства GPU. Этот шаг не нужен, если ваше приложение не имеет графического вывода, например, если оно использует только пайплайны вычислений. - Создать ресурсы, содержащие ваши данные: Данные, которые вы хотите обработать с помощью пайплайнов, должны храниться в буферах или текстурах GPU для доступа к ним вашему приложению.
- Создать пайплайны: Определите описания пайплайнов, подробно описывающие желаемые пайплайны, включая необходимую структуру данных, связи, шейдеры и макеты ресурсов, а затем создайте пайплайны из них. Наши базовые примеры демонстрируют только один пайплайн, но в нетривиальных приложениях обычно используется несколько пайплайнов для разных целей.
-
Выполнить вычислительный/рендеринговый проход: Это включает в себя ряд подшагов:
- Создать кодировщик команд, который может закодировать набор команд для передачи в GPU для выполнения.
- Создать объект кодировщика прохода, на котором выполняются вычислительные/рендеринговые команды.
- Выполнить команды для указания, какие пайплайны использовать, из каких буферов получать необходимые данные, сколько операций отрисовки выполнить (в случае рендеринговых пайплайнов) и т.д.
- Завершить список команд и упаковать его в буфер команд.
- Отправить буфер команд в GPU через очередь команд логического устройства.
В разделах ниже мы рассмотрим демонстрацию базового рендерингового пайплайна, чтобы вы могли изучить его требования. Позже мы также рассмотрим пример базового пайплайна вычислений, рассмотрев его отличия от рендерингового пайплайна.
Базовый рендеринговый пайплайн
В нашей базовой демонстрации рендеринга мы задаем элементу <canvas> тёмно-синий фон и отрисовываем на нём треугольник.
Создать модули шейдеров
Мы используем следующий код шейдера. Этап вершинного шейдера (@vertex блок) принимает фрагмент данных, содержащий позицию и цвет, позиционирует вершину в соответствии с заданной позицией, интерполирует цвет, а затем передает данные на этап фрагментного шейдера. Этап фрагментного шейдера (@fragment блок) принимает данные с этапа вершинного шейдера и окрашивает вершину в соответствии с заданным цветом.
const shaders = `
struct VertexOut {
@builtin(position) position : vec4f,
@location(0) color : vec4f
}
@vertex
fn vertex_main(@location(0) position: vec4f,
@location(1) color: vec4f) -> VertexOut
{
var output : VertexOut;
output.position = position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
{
return fragData.color;
}
`;
Примечание: В наших демонстрациях мы храним код шейдера внутри шаблона литерала, но вы можете хранить его где угодно, откуда он может быть легко извлечён как текст для подачи в вашу программу WebGPU. Например, другой распространённой практикой является хранение шейдеров в элементе <script> и извлечение содержимого с помощью Node.textContent. Правильный тип MIME для WGSL — text/wgsl.
Чтобы сделать ваш код шейдера доступным для WebGPU, необходимо поместить его в GPUShaderModule через вызов GPUDevice.createShaderModule(), передав ваш код шейдера как свойство в объекте описания. Например:
const shaderModule = device.createShaderModule({
code: shaders,
});
Получить и настроить контекст холста
В рендеринговом пайплайне нам нужно указать место для рендеринга графики. В данном случае мы получаем ссылку на элемент холста <canvas> и вызываем HTMLCanvasElement.getContext() с параметром webgpu для возвращения его контекста GPU (экземпляр GPUCanvasContext).
Затем мы настраиваем контекст с помощью вызова GPUCanvasContext.configure(), передав ему объект опций, содержащий GPUDevice, откуда будет поступать информация рендеринга, формат текстур и режим альфа-канала при рендеринге полупрозрачных текстур.
const canvas = document.querySelector("#gpuCanvas");
const context = canvas.getContext("webgpu");
context.configure({
device: device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: "premultiplied",
});
Примечание: Лучшей практикой для определения формата текстуры является использование метода GPU.getPreferredCanvasFormat(); это выбирает наиболее эффективный формат (либо bgra8unorm или rgba8unorm) для устройства пользователя.
Создать буфер и записать в него данные треугольника
Далее мы предоставим нашей программе WebGPU наши данные в удобном для неё формате. Наши данные первоначально предоставлены в виде Float32Array, который содержит 8 точек данных для каждой вершины треугольника — X, Y, Z, W для позиции и R, G, B, A для цвета.
const vertices = new Float32Array([ 0.0, 0.6, 0, 1, 1, 0, 0, 1, -0.5, -0.6, 0, 1, 0, 1, 0, 1, 0.5, -0.6, 0, 1, 0, 0, 1, 1, ]);
Однако у нас есть проблема. Нам нужно поместить наши данные в GPUBuffer. Под капотом этот тип буфера хранится в памяти, очень тесно связанной с ядрами GPU, чтобы обеспечить желаемую высокую производительность. В качестве побочного эффекта, к этой памяти не могут получить доступ процессы, работающие на хостовой системе, например, в браузере.
Буфер GPUBuffer создаётся с помощью вызова GPUDevice.createBuffer(). Мы задаём размер, равный длине массива vertices, чтобы он мог содержать все данные, а также флаги использования VERTEX и COPY_DST, чтобы указать, что буфер будет использоваться как буфер вершин и место назначения операций копирования.
const vertexBuffer = device.createBuffer({
size: vertices.byteLength, // make it big enough to store vertices in
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
Мы могли бы обработать загрузку данных в GPUBuffer с помощью операции отображения, как мы используем в примере пайплайна вычислений для чтения данных из GPU обратно в JavaScript. Однако в этом случае мы будем использовать удобный метод GPUQueue.writeBuffer(), который принимает в качестве параметров буфер для записи, источник данных для записи, смещение для каждого и размер записываемых данных (мы указали всю длину массива). Браузер затем подбирает наиболее эффективный способ обработки записи данных.
device.queue.writeBuffer(vertexBuffer, 0, vertices, 0, vertices.length);
Определить и создать рендеринговый пайплайн
Теперь, когда наши данные находятся в буфере, следующим этапом настройки является создание нашей конвейерной линии, готовой к использованию для рендеринга.
Прежде всего, мы создаём объект, описывающий требуемую структуру наших данных вершин. Он точно описывает то, что мы видели ранее в нашем vertices массиве и стадии вершинного шейдера — каждая вершина имеет данные позиции и цвета. Оба отформатированы в формате float32x4 (который соответствует типу WGSL vec4<f32>), а данные цвета начинаются с смещения в 16 байтов в каждой вершине. arrayStride указывает шаг, то есть количество байтов, составляющих каждую вершину, а stepMode указывает, что данные должны извлекаться по каждой вершине.
const vertexBuffers = [
{
attributes: [
{
shaderLocation: 0, // position
offset: 0,
format: "float32x4",
},
{
shaderLocation: 1, // color
offset: 16,
format: "float32x4",
},
],
arrayStride: 32,
stepMode: "vertex",
},
];
Далее, мы создаём объект дескриптора, который определяет конфигурацию этапов нашей конвейерной линии рендеринга. Для обеих стадий шейдера мы указываем GPUShaderModule, где можно найти соответствующий код (shaderModule), и имя функции, которая служит точкой входа для каждой стадии.
Кроме того, в случае стадии вершинного шейдера мы предоставляем наш vertexBuffers объект, чтобы указать ожидаемое состояние наших данных вершин. А в случае стадии фрагментного шейдера мы предоставляем массив состояний целевых цветов, которые указывают на заданный формат рендеринга (это соответствует формату, указанному в конфигурации контекста нашего холста ранее).
Мы также указываем состояние primitive, которое в данном случае просто указывает тип примитива, который мы будем отрисовывать, и layout значение auto. Свойство layout определяет структуру (структуру, назначение и тип) всех ресурсов графического процессора (буферы, текстуры и т. д.), используемых во время выполнения конвейера. В более сложных приложениях это будет представлять собой объект GPUPipelineLayout, созданный с помощью GPUDevice.createPipelineLayout() (вы можете посмотреть пример в нашей Основной конвейер вычислений), который позволяет графическому процессору определить, как запустить конвейер наиболее эффективно заранее. Однако здесь мы указываем значение auto, которое заставит конвейер сгенерировать неявную структуру расположения группы связей на основе любых связей, определённых в коде шейдера.
const pipelineDescriptor = {
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: vertexBuffers,
},
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [
{
format: navigator.gpu.getPreferredCanvasFormat(),
},
],
},
primitive: {
topology: "triangle-list",
},
layout: "auto",
};
Наконец, мы можем создать GPURenderPipeline на основе нашего объекта pipelineDescriptor, передав его в качестве параметра в вызов метода GPUDevice.createRenderPipeline().
const renderPipeline = device.createRenderPipeline(pipelineDescriptor);
Выполнение прохода рендеринга
Теперь, когда все настройки завершены, мы можем фактически выполнить проход рендеринга и нарисовать что-то на нашем <canvas>. Для кодирования команд, которые позже будут выпущены графическому процессору, необходимо создать экземпляр GPUCommandEncoder, что делается с помощью вызова GPUDevice.createCommandEncoder().
const commandEncoder = device.createCommandEncoder();
Далее мы запускаем проход рендеринга, создавая экземпляр GPURenderPassEncoder с помощью вызова GPUCommandEncoder.beginRenderPass(). Этот метод принимает объект дескриптора в качестве параметра, единственным обязательным свойством которого является массив colorAttachments. В данном случае мы указываем:
- Представление текстуры для рендеринга; мы создаём новое представление из
<canvas>с помощьюcontext.getCurrentTexture().createView(). - Что представление должно быть «очищено» до указанного цвета после загрузки и перед любым рисованием. Именно это вызывает синюю область позади треугольника.
- Что значение текущего прохода рендеринга должно храниться для этого цветного приложения.
const clearColor = { r: 0.0, g: 0.5, b: 1.0, a: 1.0 };
const renderPassDescriptor = {
colorAttachments: [
{
clearValue: clearColor,
loadOp: "clear",
storeOp: "store",
view: context.getCurrentTexture().createView(),
},
],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
Теперь мы можем вызывать методы кодировщика прохода рендеринга, чтобы нарисовать наш треугольник:
-
GPURenderPassEncoder.setPipeline()вызывается с нашим объектомrenderPipelineв качестве параметра для указания конвейера, который нужно использовать для прохода рендеринга. -
GPURenderPassEncoder.setVertexBuffer()вызывается с нашим объектомvertexBufferв качестве параметра, чтобы использовать его в качестве источника данных для передачи конвейеру для рендеринга. Первый параметр — слот для установки буфера вершин, а это ссылка на индекс элемента в массивеvertexBuffers, который описывает структуру этого буфера. -
GPURenderPassEncoder.draw()запускает отрисовку. В нашемvertexBufferесть данные для трёх вершин, поэтому мы устанавливаем значение количества вершин3, чтобы нарисовать их все.
passEncoder.setPipeline(renderPipeline); passEncoder.setVertexBuffer(0, vertexBuffer); passEncoder.draw(3);
Для завершения кодирования последовательности команд и их выдачи графическому процессору необходимо выполнить ещё три шага.
- Мы вызываем метод
GPURenderPassEncoder.end(), чтобы указать конец списка команд прохода рендеринга. - Мы вызываем метод
GPUCommandEncoder.finish(), чтобы завершить запись последовательности выпущенных команд и упаковать её в экземпляр объектаGPUCommandBuffer. - Мы отправляем
GPUCommandBufferв очередь команд устройства (представленную экземпляромGPUQueue) для отправки графическому процессору. Очередь устройства доступна через свойствоGPUDevice.queue, и массив экземпляровGPUCommandBufferможно добавить в очередь с помощью вызоваGPUQueue.submit().
Эти три шага можно выполнить с помощью двух следующих строк:
passEncoder.end(); device.queue.submit([commandEncoder.finish()]);
Основной конвейер вычислений
В нашем основном примере вычислений мы заставляем графический процессор вычислить некоторые значения, сохранить их в выходном буфере, скопировать данные в буфер подготовки, затем отобразить этот буфер подготовки, чтобы данные можно было прочитать в JavaScript и вывести в консоль.
Приложение имеет структуру, аналогичную основному демо-приложению рендеринга. Мы создаём ссылку GPUDevice графического процессора аналогичным образом, и упаковываем наш код шейдера в GPUShaderModule с помощью вызова GPUDevice.createShaderModule(). Разница здесь в том, что наш код шейдера имеет только одну стадию шейдера, стадию @compute.
// Define global buffer size
const NUM_ELEMENTS = 1000;
const BUFFER_SIZE = NUM_ELEMENTS * 4; // Buffer size, in bytes
const shader = `
@group(0) @binding(0)
var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(
@builtin(global_invocation_id)
global_id : vec3u,
@builtin(local_invocation_id)
local_id : vec3u,
) {
// Avoid accessing the buffer out of bounds
if (global_id.x >= ${NUM_ELEMENTS}) {
return;
}
output[global_id.x] =
f32(global_id.x) * 1000. + f32(local_id.x);
}
`;
Создание буферов для обработки наших данных
В этом примере мы создаём два экземпляра GPUBuffer для обработки наших данных, буфер output для записи результатов вычислений графического процессора с высокой скоростью и буфер stagingBuffer , в который мы скопируем содержимое output, который можно отобразить, чтобы JavaScript мог получить доступ к значениям.
-
outputзадан как буфер хранилища, который будет источником операции копирования. -
stagingBufferзадан как буфер, который можно отобразить для чтения JavaScript, и он будет местом назначения операции копирования.
const output = device.createBuffer({
size: BUFFER_SIZE,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
});
const stagingBuffer = device.createBuffer({
size: BUFFER_SIZE,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
Создание структуры расположения группы связей
При создании конвейера мы указываем группу связей для использования в конвейере. Это включает в себя предварительно создание GPUBindGroupLayout (с помощью вызова GPUDevice.createBindGroupLayout()), который определяет структуру и назначение ресурсов графического процессора, таких как буферы, которые будут использоваться в этом конвейере. Эта структура используется как шаблон для соответствия групп связей. В данном случае мы предоставляем конвейеру доступ к одному буферу памяти, связанному со слотом связи 0 (это соответствует соответствующему номеру связи в нашем коде шейдера — @binding(0)), применимому на стадии вычислений конвейера, и с определением назначения буфера как storage.
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
buffer: {
type: "storage",
},
},
],
});
Далее мы создаём GPUBindGroup с помощью вызова GPUDevice.createBindGroup(). Мы передаём этому методу объект дескриптора, который указывает на структуру расположения группы связей в качестве основы для этой группы связей, и детали переменной, которая должна быть связана со слотом, определённым в структуре расположения. В данном случае мы объявляем связь 0 и указываем, что буфер output , который мы определили ранее, должен быть связан с ним.
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer: output,
},
},
],
});
Примечание: Вы можете получить неявную структуру расположения для использования при создании группы связей, вызвав метод GPUComputePipeline.getBindGroupLayout(). Также существует версия для конвейеров рендеринга: см. GPURenderPipeline.getBindGroupLayout().
Создание конвейера вычислений
После всего вышеперечисленного мы можем создать конвейер вычислений, вызвав GPUDevice.createComputePipeline(), передав ему объект дескриптора конвейера. Это работает аналогично созданию конвейера рендеринга. Мы описываем вычислительный шейдер, указывая, в каком модуле найти код и какова точка входа. Мы также указываем layout для конвейера, в данном случае создавая структуру расположения на основе bindGroupLayout, определённой нами ранее с помощью вызова GPUDevice.createPipelineLayout().
const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout],
}),
compute: {
module: shaderModule,
entryPoint: "main",
},
});
Одно отличие от структуры расположения конвейера рендеринга заключается в том, что мы не указываем тип примитива, так как мы ничего не рисуем.
Выполнение прохода вычислений
Выполнение прохода вычислений аналогично по структуре выполнению прохода рендеринга, но с некоторыми другими командами. Прежде всего, кодировщик прохода создаётся с помощью GPUCommandEncoder.beginComputePass().
При выполнении команд мы указываем конвейер для использования так же, как и раньше, с помощью GPUComputePassEncoder.setPipeline(). Затем мы используем GPUComputePassEncoder.setBindGroup(), чтобы указать, что хотим использовать наш bindGroup для указания данных для использования в вычислениях, и GPUComputePassEncoder.dispatchWorkgroups() для указания количества групп вычислений графического процессора для выполнения вычислений.
Затем мы указываем конец списка команд прохода рендеринга с помощью GPURenderPassEncoder.end().
passEncoder.setPipeline(computePipeline); passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatchWorkgroups(Math.ceil(NUM_ELEMENTS / 64)); passEncoder.end();
Чтение результатов обратно в JavaScript
Перед отправкой закодированных команд на GPU для выполнения с помощью GPUQueue.submit(), мы копируем содержимое буфера output в буфер stagingBuffer с помощью GPUCommandEncoder.copyBufferToBuffer().
// Copy output buffer to staging buffer commandEncoder.copyBufferToBuffer( output, 0, // Source offset stagingBuffer, 0, // Destination offset BUFFER_SIZE, // Length, in bytes ); // End frame by passing array of command buffers to command queue for execution device.queue.submit([commandEncoder.finish()]);
После того, как выходные данные станут доступны в stagingBuffer, мы используем метод GPUBuffer.mapAsync() для отображения данных в промежуточную память, получаем ссылку на отображенный диапазон с помощью GPUBuffer.getMappedRange(), копируем данные в JavaScript и затем выводим их в консоль. Мы также раз отображаем stagingBuffer после того, как закончим с ним.
// map staging buffer to read results back to JS await stagingBuffer.mapAsync( GPUMapMode.READ, 0, // Offset BUFFER_SIZE, // Length, in bytes ); const copyArrayBuffer = stagingBuffer.getMappedRange(0, BUFFER_SIZE); const data = copyArrayBuffer.slice(); stagingBuffer.unmap(); console.log(new Float32Array(data));
Обработка ошибок GPU
Вызовы WebGPU асинхронно проверяются в процессе GPU. Если ошибки найдены, вызов-проблема помечается как недействительный со стороны GPU. Если другой вызов опирается на возвращаемое значение недействительного вызова, этот объект также будет помечен как недействительный, и так далее. По этой причине ошибки в WebGPU называются «заразными».
Каждый экземпляр GPUDevice поддерживает свой собственный стек области видимости ошибок. Этот стек изначально пуст, но вы можете начать помещать область видимости ошибок в стек, вызвав GPUDevice.pushErrorScope() для захвата ошибок определённого типа.
После завершения захвата ошибок вы можете завершить захват, вызвав GPUDevice.popErrorScope(). Это извлекает область видимости из стека и возвращает Promise, который разрешается до объекта (GPUInternalError, GPUOutOfMemoryError или GPUValidationError) описывающего первую ошибку, захваченную в области видимости, или null если ошибки не были захвачены.
Мы попытались предоставить полезную информацию, чтобы помочь вам понять, почему возникают ошибки в вашем коде WebGPU в разделах «Проверка» (Validation) при необходимости, в которых перечислены критерии для избежания ошибок. Например, см. GPUDevice.createBindGroup() Раздел проверки. Часть этой информации сложная; вместо того, чтобы повторять спецификацию, мы решили просто перечислить критерии ошибок, которые:
- Неочевидные, например, комбинации свойств описателей, которые приводят к ошибкам проверки. Нет смысла говорить вам, чтобы вы убедились, что используете правильную структуру объекта описателя. Это и очевидно, и расплывчато.
- Управляемые разработчиком. Некоторые критерии ошибок основаны исключительно на внутренних механизмах и не очень актуальны для веб-разработчиков.
Более подробную информацию об обработке ошибок WebGPU вы можете найти в описании — см. Действительность объекта и уничтожение и Ошибки. Рекомендации по обработке ошибок WebGPU предоставляют полезные реальные примеры и советы.
Примечание: Исторический способ обработки ошибок в WebGL — предоставление метода getError() для возврата информации об ошибках. Это проблематично, поскольку возвращает ошибки синхронно, что плохо для производительности — каждый вызов требует обмена с GPU и требует завершения всех ранее выпущенных операций. Его модель состояния также плоская, что означает, что ошибки могут просачиваться между не связанными участками кода. Создатели WebGPU были нацелены на улучшение этого.
Интерфейсы
Точка входа в API
-
Точка входа в API — возвращает объект
GPUдля текущего контекста. GPU-
Начальная точка для использования WebGPU. Она может быть использована для возврата
GPUAdapter. GPUAdapter-
Представляет адаптер GPU. Из него вы можете запросить
GPUDevice, информацию об адаптере, возможности и ограничения. GPUAdapterInfo-
Содержит идентифицирующую информацию об адаптере.
Настройка GPUDevices
GPUDevice-
Представляет логическое устройство GPU. Это основной интерфейс, через который осуществляется большинство функций WebGPU.
GPUSupportedFeatures-
Объект типа множество, который описывает дополнительные функции, поддерживаемые
GPUAdapterилиGPUDevice. GPUSupportedLimits-
Описывает ограничения, поддерживаемые
GPUAdapterилиGPUDevice.
Настройка <canvas>
-
HTMLCanvasElement.getContext()—"webgpu"contextType -
Вызов
getContext()с"webgpu"contextTypeвозвращает объектGPUCanvasContext, который затем может быть настроен с помощьюGPUCanvasContext.configure(). GPUCanvasContext-
Представляет контекст рендеринга WebGPU элемента
<canvas>.
Представление ресурсов конвейера
GPUBuffer-
Представляет блок памяти, который может быть использован для хранения сырых данных для использования в операциях GPU.
GPUExternalTexture-
Объект-оболочка, содержащий снимок
HTMLVideoElement, который может использоваться как текстура в операциях рендеринга GPU. GPUSampler-
Управляет тем, как шейдеры преобразуют и фильтруют данные ресурсов текстур.
GPUShaderModule-
Ссылка на внутренний объект модуля шейдера, контейнер для кода шейдера WGSL, который может быть отправлен на GPU для выполнения конвейером.
GPUTexture-
Контейнер, используемый для хранения 1D, 2D или 3D массивов данных, таких как изображения, для использования в операциях рендеринга GPU.
GPUTextureView-
Вид на некоторую часть подресурсов текстуры, определенных конкретной
GPUTexture.
Представление конвейеров
GPUBindGroup-
Основанный на
GPUBindGroupLayout,GPUBindGroupопределяет набор ресурсов, которые будут связаны вместе в группе, и как эти ресурсы используются в стадиях шейдера. GPUBindGroupLayout-
Определяет структуру и назначение связанных ресурсов GPU, таких как буферы, которые будут использоваться в конвейере, и используется как шаблон при создании
GPUBindGroup. GPUComputePipeline-
Управляет стадией шейдера вычислений и может быть использован в
GPUComputePassEncoder. GPUPipelineLayout-
Определяет
GPUBindGroupLayouts, используемые конвейером.GPUBindGroups, используемые с конвейером во время кодирования команд, должны иметь совместимыеGPUBindGroupLayouts. GPURenderPipeline-
Управляет стадиями шейдеров вершин и фрагментов и может использоваться в
GPURenderPassEncoderилиGPURenderBundleEncoder.
Кодирование и отправка команд на GPU
GPUCommandBuffer-
Представляет записанный список команд GPU, которые можно отправить в
GPUQueueдля выполнения. GPUCommandEncoder-
Представляет кодировщик команд, используемый для кодирования команд, которые будут отправлены на GPU.
GPUComputePassEncoder-
Кодирует команды, связанные с управлением этапом шейдера вычислений, как издаётся
GPUComputePipeline. Часть общей кодировочной активностиGPUCommandEncoder. GPUQueue-
Управляет выполнением закодированных команд на GPU.
GPURenderBundle-
Контейнер для предварительно записанных наборов команд (см.
GPURenderBundleEncoder). GPURenderBundleEncoder-
Используется для предварительной записи наборов команд. Их можно повторно использовать в
GPURenderPassEncoderс помощью методаexecuteBundles(), сколько угодно раз. GPURenderPassEncoder-
Кодирует команды, связанные с управлением этапами вершинного и фрагментного шейдеров, как издаётся
GPURenderPipeline. Часть общей кодировочной активностиGPUCommandEncoder.
Запуск запросов в проходах отрисовки
GPUQuerySet-
Используется для записи результатов запросов к проходам, таких как запросы по затенению или отметкам времени.
Отладка ошибок
GPUCompilationInfo-
Массив объектов
GPUCompilationMessage, сгенерированный компилятором модулей шейдеров GPU для помощи в диагностике проблем с кодом шейдеров. GPUCompilationMessage-
Представляет собой одно информационное, предупреждающее или ошибочное сообщение, сгенерированное компилятором модулей шейдеров GPU.
GPUDeviceLostInfo-
Возвращается, когда
GPUDevice.lostPromiseразрешается, предоставляя информацию о причинах потери устройства. GPUError-
Базовый интерфейс для ошибок, отображаемых
GPUDevice.popErrorScopeи событиемuncapturederror. GPUInternalError-
Один из типов ошибок, отображаемых
GPUDevice.popErrorScopeи событиемGPUDeviceuncapturederrorсобытия. Указывает, что операция завершилась неудачно по причине, связанной с системой или реализацией, даже если все требования к валидации были выполнены. GPUOutOfMemoryError-
Один из типов ошибок, отображаемых
GPUDevice.popErrorScopeи событиемGPUDeviceuncapturederrorсобытия. Указывает, что свободная память недостаточно для завершения запрошенной операции. GPUPipelineError-
Описание сбоя конвейера. Значение, полученное, когда
Promise, возвращённый вызовомGPUDevice.createComputePipelineAsync()илиGPUDevice.createRenderPipelineAsync(), отклоняется. GPUUncapturedErrorEvent-
Тип объекта события для события
GPUDeviceuncapturederror. GPUValidationError-
Один из типов ошибок, отображаемых
GPUDevice.popErrorScopeи событиемGPUDeviceuncapturederrorсобытия. Описывает ошибку приложения, указывающую, что операция не прошла проверку ограничений API WebGPU.
Требования к безопасности
Весь API доступен только в безопасном контексте.
Примеры
Спецификации
| Спецификация |
|---|
| WebGPU # gpu-interface |
Совместимость с браузерами
| Рабочий стол | Мобильные устройства | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox для Android | Opera Android | Safari на iOS | Samsung Internet | WebView Android | |
WebGPU_API |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превьюВ настоящее время поддерживается только на Linux и Windows. |
99В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превью | 121 | Нет | 81 | Нет | 25.0 | 121 |
getPreferredCanvasFormat |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превьюВ настоящее время поддерживается только на Linux и Windows. |
99В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превью | 121 | Нет | 81 | Нет | 25.0 | 121 |
requestAdapter |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
113В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превьюВ настоящее время поддерживается только на Linux и Windows. |
99В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превью | 121 | Нет | 81 | Нет | 25.0 | 121 |
wgslLanguageFeatures |
115В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
115В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
Нет | 101В настоящее время поддерживается только на ChromeOS, macOS и Windows. |
превью | 121 | Нет | 81 | Нет | 25.0 | 121 |
См. также
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API