#gpu #webgl
#gpu #webgl
Вопрос:
Я исследую практические верхние границы объема данных вершин, которые я могу загрузить в приложение WebGL 1, используя bufferData
и STATIC_DRAW
. Я подготовил фрагмент, который позволяет регулировать количество треугольников, передаваемых за кадр, от 0 до 10 000 000. Я не использую более эффективные bufferSubData
STREAM_DRAW
варианты и, потому что я действительно хочу испытать наихудший случай. Кроме того, я рисую треугольники очень маленькими на экране, чтобы скорость заполнения не сильно влияла на результаты.
const canvas = document.createElement("canvas");
canvas.width = 320;
canvas.height = 180;
canvas.style.border = "1px solid black";
const gl = canvas.getContext("webgl");
document.body.appendChild(canvas);
const MAX_TRIANGLES = 10000000;
document.body.appendChild(document.createTextNode("Triangles to upload each frame: "));
const range = document.createElement("input");
range.type = "range";
range.min = 0;
range.max = MAX_TRIANGLES;
range.value = 100;
document.body.appendChild(range);
let nTriangles = range.value;
range.addEventListener("input", () => {
nTriangles = range.value;
});
const mbPerFrame = document.createElement("div");
document.body.appendChild(mbPerFrame);
const fps = document.createElement("div");
document.body.appendChild(fps);
gl.clearColor(0.2, 0.3, 0.5, 1.0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, "attribute vec2 a_position; void main(void) { gl_Position = vec4(a_position / 100.0, 0.0, 1.0); }");
gl.compileShader(vs);
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, "precision mediump float; void main(void) { gl_FragColor = vec4(0.05, 0.0, 0.0, 0.05); }");
gl.compileShader(fs);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 8, 0);
gl.enableVertexAttribArray(0);
const randomVertexData = new Float32Array(3 * 2 * MAX_TRIANGLES);
for (let i = 0; i < 3 * MAX_TRIANGLES; i ) {
randomVertexData[i * 2 0] = Math.random() * 2 - 1;
randomVertexData[i * 2 1] = Math.random() * 2 - 1;
}
gl.useProgram(program);
const frameTimes = new Set();
function frame() {
frameTimes.add(performance.now());
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(randomVertexData.buffer, 0, nTriangles * 3 * 2), gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLES, 0, nTriangles * 3);
requestAnimationFrame(frame);
}
setInterval(() => {
const now = performance.now();
let framesLastSecond = 0;
frameTimes.forEach((frameTime) => {
if (now - frameTime < 1000) {
framesLastSecond ;
} else {
frameTimes.delete(frameTime);
}
});
mbPerFrame.innerText = `${Math.floor((nTriangles * 3 * 2 * 4 / (1024 * 1024)) * 100) / 100} MB per frame`;
fps.innerText = `${framesLastSecond} FPS`;
}, 1000);
requestAnimationFrame(frame);
Цикл рендеринга в основном выполняет это:
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(randomVertexData.buffer, 0, nTriangles * 3 * 2), gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLES, 0, nTriangles * 3);
Где randomVertexData
некоторые данные подготовлены заранее и nTriangles
управляются ползунком.
При перемещении ползунка вправо вы увидите увеличение размера в МБ и падение частоты кадров.
На моей машине (ноутбук Dell с i7 vPro и Quadro M1000) загрузка 30 МБ на кадр по-прежнему приводит к 60 кадрам в секунду, что более чем достаточно для моего приложения. На моем смартфоне (Motorola G7 Power) загрузка 4 МБ на кадр по-прежнему приводит к 60 кадрам в секунду.
Вопросы
- Правильно ли я спроектировал этот эксперимент с производительностью?
- Могу ли я доверять / распространять его результаты на реальный мир?
- Как вы думаете, какой разумный объем данных нужно передавать за кадр для мобильных устройств и ноутбуков среднего уровня 2020 года?
Комментарии:
1. Это было бы намного полезнее в качестве фрагмента , чем codepen. Правила сайта, по сути, гласят, что если код, о котором вы спрашиваете, отсутствует в самом вопросе, тогда вопрос не по теме. Вы спрашиваете, правильно ли вы выполняете тест, но код для вашего perf-теста отсутствует в самом вопросе.
2. Спасибо @ gman, отличное предложение. Я преобразовал код в фрагмент. Я подумал, что, возможно, 1) само по себе является скорее вопросом CodeReview, но дух 1) 2) 3) в целом лучше подходит для StackOverflow, я думаю.
Ответ №1:
- Правильно ли я спроектировал этот эксперимент с производительностью
Это зависит от вашего определения правильного. setInterval
не гарантирует, что он будет удаленно точным, но код предполагает, что вы запросили 1000 мс, и вы получили 1000 мс, а не 1100 мс или 900 мс и т.д.. Если бы это был я, я бы не использовал setInterval
и не использовал время, прошедшее в requestAnimationFrame
, и не получал результат за последнюю 1 секунду кадров с учетом фактического времени, затраченного на эти кадры
- Могу ли я доверять / распространять его результаты на реальный мир?
Я не знаю, что означает этот вопрос. Каждый графический процессор / процессор / браузер / драйвер может давать разные результаты. Вы можете попытаться сопоставить результаты с некоторым отпечатком пальца графического процессора / процессора / браузера. Отпечаток пальца может быть таким же простым, как пользовательский агент webgl_debug_render_info информация, хотя не все браузеры поддерживают webgl_debug_render_info или, если на то пошло, пользовательский агент.
- Как вы думаете, какой разумный объем данных нужно передавать за кадр для мобильных устройств и ноутбуков среднего уровня 2020 года?
Я не знаю, мне пришлось бы протестировать кучу разных устройств и протестировать в разных браузерах.
еще несколько комментариев к вашему тесту
-
Он использует
gl.STATIC_DRAW
, но, возможно, должен использоватьgl.DYNAMIC_DRAW
илиgl.STREAM_DRAW
, поскольку это сообщает браузеру / драйверу, что вы будете часто менять данные (понятия не имею, какие драйверы используют эту информацию) -
Это зависит от драйвера, но для многих драйверов, вероятно, быстрее выделить буфер один раз и загрузить новые данные
bufferSubData
. СемантическиbufferData
выделяет память иbufferSubData
не выделяет, и, как правило, выделение памяти происходит медленнее, чем не-выделение, хотя я слышал о случаях, когда все наоборот. -
Если вас интересует только скорость загрузки вершин, то вы, вероятно, захотите нарисовать свои треугольники как можно меньше или, возможно, вообще не рисовать (треугольник нулевого размера), чтобы вы могли удалить накладные расходы на рисование. Другими словами, 1000 треугольников 512×512 будут отрисовываться намного медленнее, чем 1000 треугольников 2×2, потому что один рисует 4000 пикселей, а другой — 262 миллиона пикселей.
const canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
canvas.style.border = "1px solid black";
const gl = canvas.getContext("webgl");
document.body.appendChild(canvas);
const MAX_TRIANGLES = 10000000;
document.body.appendChild(document.createTextNode("Triangles to upload each frame: "));
const range = document.createElement("input");
range.type = "range";
range.min = 0;
range.max = MAX_TRIANGLES;
range.value = 100;
document.body.appendChild(range);
let nTriangles = range.value;
range.addEventListener("input", () => {
nTriangles = range.value;
});
const mbPerFrame = document.createElement("div");
document.body.appendChild(mbPerFrame);
const fps = document.createElement("div");
document.body.appendChild(fps);
gl.clearColor(0.2, 0.3, 0.5, 1.0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
const vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, "attribute vec2 a_position; void main(void) { gl_Position = vec4(a_position / 100.0, 0.0, 1.0); }");
gl.compileShader(vs);
const fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, "precision mediump float; void main(void) { gl_FragColor = vec4(0.05, 0.0, 0.0, 0.05); }");
gl.compileShader(fs);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, MAX_TRIANGLES * 3 * 2 * 4, gl.STREAM_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 8, 0);
gl.enableVertexAttribArray(0);
const randomVertexData = new Float32Array(3 * 2 * MAX_TRIANGLES);
for (let i = 0; i < 3 * MAX_TRIANGLES; i ) {
randomVertexData[i * 2 0] = Math.random() * 2 - 1;
randomVertexData[i * 2 1] = Math.random() * 2 - 1;
}
gl.useProgram(program);
let then = 0;
let numFrames = 0;
function frame(now) {
const elapsedTime = now - then;
if (elapsedTime >= 1000) {
then = now;
mbPerFrame.innerText = `${(nTriangles * 3 * 2 * 4 / 1000 / 1000).toFixed(1)} MB per frame`;
fps.innerText = `${(numFrames / (elapsedTime * 0.001)).toFixed(1)} FPS`;
numFrames = 0;
}
numFrames;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(randomVertexData.buffer, 0, nTriangles * 3 * 2));
gl.drawArrays(gl.TRIANGLES, 0, nTriangles * 3);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
Комментарии:
1. Спасибо. Для СТАТИЧЕСКОГО и ДИНАМИЧЕСКОГО / ПОТОКА, а также для данных и подданных это было сделано намеренно; Я попробовал также DYNAMIC и Data и не заметил никакой разницы на своей машине. Возможно, ДИНАМИЧЕСКИЕ и вложенные данные приведут к заметным изменениям.
2. Для размера треугольника я не сделал его равным нулю, потому что я не хотел, чтобы какая-то экзотическая оптимизация / хитрость драйвера сработала и начала НЕ загружать буфер в каждом кадре. Но теперь я понимаю, что был параноиком, драйвер не может быть таким умным, и нет причин кодировать такую оптимизацию для такого крайнего случая.