WebGL: асинхронные операции?

Я хотел бы знать, есть ли какие-либо асинхронные вызовы для WebGL, которыми можно было бы воспользоваться?

Я просмотрел Spec v1 и Spec v2, они ничего не упоминают. В V2 есть механизм запросов WebGL, который я не думаю, что мне нужно.

Поиск в Интернете не дал ничего окончательного. Вот этот пример, и непонятно, чем отличаются синхронная и асинхронная версии. http://toji.github.io/shader-perf/

В конечном итоге я хотел бы иметь возможность выполнять некоторые из них асинхронно:

  • readPixels
  • texSubImage2D и texImage2D
  • Компиляция шейдеров
  • связывание программ
  • рисовать???

Существует операция glFinish, и в документации к ней сказано: «не возвращается, пока не будут выполнены все ранее вызванные команды GL». Для меня это означает, что есть асинхронные операции, которых можно ожидать, вызвав Finish ()?

И некоторые сообщения в сети предполагают, что вызов getError () также вызывает некоторую синхронность и не очень желательно делать после каждого вызова.


person Jeff Saremi    schedule 06.08.2018    source источник


Ответы (1)


Это зависит от вашего определения async.

В Chrome (Firefox тоже может сделать это сейчас? Не уверен). Chrome запускает весь код графического процессора в отдельном процессе от JavaScript. Это означает, что ваши команды выполняются асинхронно. Даже сам OpenGL спроектирован как асинхронный. Функции (WebGL / OpenGL) вставляют команды в буфер команд. Они выполняются другим потоком / процессом. Вы говорите OpenGL: «Эй, у меня есть новые команды, которые нужно выполнить!» позвонив по телефону gl.flush. Он выполняет эти команды асинхронно. Если вы не вызовете gl.flush, он будет вызываться для вас периодически, когда будет введено слишком много команд. Он также будет вызываться при выходе из текущего события JavaScript, если вы вызвали любую команду рендеринга на холсте (gl.drawXXX, gl.clear).

В этом смысле все в WebGL асинхронно. Если вы не запрашиваете что-то (gl.getXXX, gl.readXXX), значит, обработка (рисование) материала не синхронизируется с вашим JavaScript. WebGL дает вам доступ к графическому процессору, который работает отдельно от вашего процессора.

Один из способов воспользоваться этим преимуществом в Chrome - это скомпилировать асинхронные шейдеры, отправив шейдеры.

for each shader
  s = gl.createShader()
  gl.shaderSource(...);
  gl.compileShader(...);
  gl.attachShader(...);
gl.linkProgram(...)
gl.flush()

Теперь процесс GPU будет компилировать ваши шейдеры. Итак, скажем, через 250 мс вы только тогда начинаете спрашивать, удалось ли это, и запрашивать местоположения, тогда, если для компиляции и связывания шейдеров потребовалось менее 250 мс, все это произошло асинхронно.

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

В противном случае на данный момент (август 2018 г.) явно асинхронных API нет.

Обновлять

HankMoody упомянул в комментариях, что texImage2D синхронизируется. Опять же, это зависит от вашего определения асинхронности. Чтобы добавить команды и их данные, нужно время. Команда типа gl.enable(gl.DEPTH_TEST) должна добавить только 2-8 байтов. Команда типа gl.texImage2D(..., width = 1024, height = 1024, RGBA, UNSIGNED_BYTE) должна добавить 4 мегабайта !. После того, как эти 4 мегабайта загружены, все остальное выполняется асинхронно, но загрузка требует времени. То же самое для обеих команд, просто добавление 2-8 байтов занимает намного меньше времени, чем добавление 4 мегабайт.

Для большей ясности, после того, как загружаются 4 мегабайта, многие другие вещи происходят асинхронно. Драйвер называется с 4 мег. Драйвер копирует тот 4мег. Драйвер планирует использовать эти 4 мегабайта позже, так как он не может сразу загрузить данные, если текстура уже используется. Либо так, либо он сразу же загружает его просто в новую область, а затем меняет местами то, на что указывает текстура, непосредственно перед вызовом отрисовки, который фактически использует эти новые данные. Другие драйверы просто копируют данные, сохраняют их и ждут, пока текстура не будет использована в вызове отрисовки, чтобы фактически обновить текстуру. Это связано с тем, что texImage2D имеет сумасшедшую семантику, при которой вы можете загружать mip-файлы разного размера в любом порядке, поэтому драйвер не может знать, что на самом деле нужно в памяти графического процессора, до момента отрисовки, поскольку он не знает, в каком порядке вы собираетесь вызывать texIamge2D. Все, что упомянуто в этом абзаце, происходит асинхронно.

Но это дает дополнительную информацию.

gl.texImage2D и связанные с ним команды должны сделать ТОННУ работы. Во-первых, они должны соблюдать UNPACK_FLIP_Y_WEBGL и UNPACK_PREMULTIPLY_ALPHA_WEBGL, поэтому им нужно сделать копию нескольких мегабайт данных, чтобы перевернуть или преумножить их. Во-вторых, если вы передадите им видео, холст или изображение, им, возможно, придется выполнить тяжелые преобразования или даже повторно проанализировать изображение из источника, особенно в свете UNPACK_COLORSPACE_CONVERSION_WEBGL. Происходит ли это каким-либо асинхронным способом или нет, зависит от браузера. Поскольку у вас нет прямого доступа к изображению / видео / холсту, браузер может выполнять всю эту асинхронную работу, но так или иначе вся эта работа должна выполняться.

Чтобы сделать большую часть этой работы ASYNC, был добавлен ImageBitmap API. Как и большинство веб-API, он недоопределен, но идея состоит в том, чтобы сначала выполнить fetch (который является асинхронным). Затем вы запрашиваете создание ImageBitmap и даете ему параметры для преобразования цвета, переворачивания, предварительно умноженного альфа-канала. Это тоже происходит асинхронно. Затем вы передаете результат gl.texImage2D с надеждой, что браузер смог сделать все тяжелые части, прежде чем он перешел на этот последний шаг.

Пример:

// note: mode: 'cors' is because we are loading
// from a different domain

async function main() {
  const response = await fetch('https://i.imgur.com/TSiyiJv.jpg', {mode: 'cors'})
  if (!response.ok) {
    return console.error('response not ok?');
  }
  const blob = await response.blob();
  const bitmap = await createImageBitmap(blob, {
    premultiplyAlpha: 'none',
    colorSpaceConversion: 'none',
  });

  const gl = document.querySelector("canvas").getContext("webgl");

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  {
    const level = 0;
    const internalFormat = gl.RGBA;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                  format, type, bitmap);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  }

  const vs = `
  uniform mat4 u_worldViewProjection;
  attribute vec4 position;
  attribute vec2 texcoord;
  varying vec2 v_texCoord;

  void main() {
    v_texCoord = texcoord;
    gl_Position = u_worldViewProjection * position;
  }
  `;
  const fs = `
  precision mediump float;
  varying vec2 v_texCoord;
  uniform sampler2D u_tex;
  void main() {
    gl_FragColor = texture2D(u_tex, v_texCoord);
  }
  `;

  const m4 = twgl.m4;
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
  const uniforms = {
    u_tex: tex,
  };

  function render(time) {
    time *= 0.001;
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.enable(gl.DEPTH_TEST);

    const fov = 30 * Math.PI / 180;
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.5;
    const zFar = 10;
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [1, 4, -6];
    const target = [0, 0, 0];
    const up = [0, 1, 0];

    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);
    const viewProjection = m4.multiply(projection, view);
    const world = m4.rotationY(time);

    uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);

    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

К сожалению, это работает только в Chrome с августа 2018 года. здесь в Firefox. Других браузеров я не знаю.

person gman    schedule 07.08.2018
comment
Большое спасибо @gman за этот подробный ответ. Что-то столь важное даже не упоминалось в спецификации WebGL или не упоминалось в тех терминах, которые я искал. - person Jeff Saremi; 07.08.2018
comment
gl.texImage2D для меня синхронизируется и каждый раз блокирует выполнение JS. - person HankMoody; 07.08.2018
comment
Как я уже сказал, это зависит от вашего определения асинхронности. Для ввода команд требуется время. Команда текстуры занимает много времени, поскольку все данные, которые вы загружаете, должны быть скопированы через буфер команд. Когда он возвращается к JavaScript, большая часть процесса все еще выполняется в отдельном процессе. Вы можете использовать ImageBitmap для более быстрой обработки. Обновлю ответ на это. - person gman; 07.08.2018
comment
Ошибка Not enough arguments to Window.createImageBitmap. появляется в Firefox при запуске последнего фрагмента. - person HankMoody; 07.08.2018
comment
Определение async по умолчанию - не блокировать дальнейшее выполнение JS. Независимо от того, что вы формируете в своем коде, gl.texImage2D, кажется, всегда блокирует выполнение JS-кода (задержка увеличивается с размером текстуры). - person HankMoody; 07.08.2018
comment
@HankMoody, все блокирует выполнение JS, некоторые просто блокируют его меньше, чем другие. var a = 1 блокирует следующую строку JS. fetch(url) также блокирует следующую строку JS. Это может быть ненадолго, но все еще заблокировано. Очевидно, вы выбрали какое-то определение, в котором блокировка ‹произвольная сумма является асинхронной, а блокировка› - синхронной. Но на самом деле это не определение async. async - это если какая-то часть операции выполняется параллельно. Вызов fetch требует короткого времени для настройки, а остальное выполняется параллельно. texImage2D требует много времени для настройки, а затем остальное, если параллельно. - person gman; 07.08.2018
comment
Что касается Firefox, он еще не полностью поддерживает ImageBitmap. - person gman; 07.08.2018
comment
WEBGL_get_buffer_sub_data_async в Chrome разрешает асинхронное чтение из текстуры - person Jeff Saremi; 11.08.2018
comment
Хм. здесь оно указано как отклоненное расширение khronos.org/registry/webgl/extensions - person gman; 11.08.2018