WebGL: асинхронные операции?
Я хотел бы знать, есть ли какие-либо асинхронные вызовы для WebGL, которыми можно воспользоваться?
Я смотрел в Spec v1 и Spec v2, они ничего не упоминают. В V2 есть механизм WebGL Query, который я не думаю, что я ищу.
Поиск в сети не дал ничего определенного. Есть этот пример, и неясно, чем отличаются версия синхронизации и асинхронная. http://toji.github.io/shader-perf/
В конечном итоге я бы хотел использовать некоторые из них асинхронно:
- readPixels
- texSubImage2D и texImage2D
- Шейдерная компиляция
- привязка программы
- рисовать???
Существует операция glFinish, и в документации к ней говорится: "не возвращается, пока не будут выполнены эффекты всех ранее вызванных команд GL". Для меня это означает, что существуют асинхронные операции, которые можно ожидать, вызывая Finish()?
А некоторые публикации в Интернете предполагают, что вызов getError() также вызывает некоторую синхронность и является не очень желательной вещью после каждого вызова.
1 ответ
Это зависит от вашего определения асинхронности.
В 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 мг! Как только этот 4meg загружен, остальное асинхронно, но загрузка занимает время. То же самое для обеих команд, просто добавление 2-8 байтов занимает намного меньше времени, чем добавление 4meg.
Но это поднимает еще немного информации.
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
fetch('https://i.imgur.com/TSiyiJv.jpg', {mode: 'cors'})
.then((response) => {
if (!response.ok) {
throw response;
}
return response.blob();
})
.then((blob) => {
return createImageBitmap(blob, {
premultiplyAlpha: 'none',
colorSpaceConversion: 'none',
});
}).then((bitmap) => {
run(bitmap);
}).catch(function(e) {
console.error(e);
});
//----------------------------
function run(bitmap) {
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);
}
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 здесь. Другие браузеры я не знаю.