Измерение производительности Drawcall на разных мобильных устройствах / архитектурах - шейдеры с помощью discard () или размещение вершин за пределами дальней плоскости? - PullRequest
1 голос
/ 02 мая 2019

Я хочу измерить производительность шейдера в WebGL для мобильных устройств / архитектуры.

Я знаю, что использование 'discard ()' в шейдерах не очень эффективно, но я хочу провести некоторые эксперименты и получить некоторое представление о том, как шейдеры работают в терминах вызовов отрисовки - один из основных критериев - это измерение производительность для различных мобильных устройств и архитектур (iPhone, iPad, рендеринг тайлов и отложенный рендеринг) при использовании discard () или просто размещении объекта / вершин за пределами дальней плоскости усеченного конуса.

Я довольно новичок в Javascript / WebGL, поэтому я хочу попросить какие-нибудь указатели, или, может быть, у кого-то уже есть какой-то похожий тест, на котором я могу опираться, чтобы получить некоторые цифры. Я не встречал таких фрагментов в интернете. Любая вещь, использующая THREE.js или машинописный текст или чистые образцы js, будет хороша в качестве начального шаблона.

Спасибо, и любой указатель будет оценен.

Спасибо

1 Ответ

0 голосов
/ 02 мая 2019

Вы можете возможно измерить, что быстрее, позвонив gl.readPixels, как это

const startTime = performance.now();
drawLotsOfStuff();
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
const endTime = performance.now();
const elaspedTimeInMilliseconds = endTime - startTime();

Это не скажет вам, как быстро что-то рендерится, но может сказать, какой метод быстрее.

WebGL, особенно в Chrome, является многопроцессорным. По умолчанию, когда вы просто выполняете рендеринг в приложении со скоростью 60 кадров в секунду, все может работать параллельно. JavaScript вызывает gl.drawXXX, предыдущие команды рисования выполняются параллельно. Когда вы вызываете gl.readPixels, все параллельные части должны быть остановлены, чтобы все предыдущие команды рисования выполнялись перед чтением данных.

Это означает, что использование gl.readPixels не говорит вам, насколько быстро что-то работает. Он говорит вам, сколько времени заняло

  1. запуск 2 или 3 процессов
  2. координировать их взятие друг к другу
  3. выдать несколько команд
  4. ждать выполнения этих команд
  5. остановить оба процесса
  6. синхронизация обоих процессов
  7. передача данных из одного процесса в другой

Если вы хотите знать, как быстро что-то рисует, вы действительно хотите рассчитать время шага 4 выше, но исходя из того факта, что все параллельно, у вас есть шаги 1., 2., 3., 5., 6., и 7. включены в ваши сроки.

Тем не менее, предполагая, что все они постоянны, вы можете, по крайней мере, сказать, является ли шаг 3 быстрее или медленнее, чем какой-либо другой шаг 3.

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

Еще одна проблема заключается в том, что браузеры делают или, по крайней мере, намеренно возвращают результаты с низким разрешением для синхронизации. Это должно смягчить проблемы Spectre . Я думаю, что Chrome вернул результаты высокого разрешения теперь, когда к ним добавлена ​​изоляция процесса, хотя и не уверен.

Давайте проверим и получим ли мы последовательные результаты

function main() {
  const gl = document.createElement('canvas').getContext('webgl');

  const vs = `
  attribute vec4 position;
  void main() {
    gl_PointSize = 128.0;
    gl_Position = position;
  }
  `;

  const fastFS = `
  precision highp float;
  void main() {
    gl_FragColor = vec4(1);
  }
  `;

  const slowFS = `
  precision highp float;
  // these are here to try to make sure the loop
  // is not optimized. (though it could still easily
  // be as it's really just color = junk * moreJunk * 100
  uniform vec4 junk;
  uniform vec4 moreJunk;

  void main() {
    vec4 color = vec4(0);
    for (int i = 0; i < 100; ++i) {
      color += junk * moreJunk;
    }
    gl_FragColor = color;
  }
  `;

  const locations = ['position']; // make position location 0
  const fastPrg = twgl.createProgram(gl, [vs, fastFS], locations);
  const slowPrg = twgl.createProgram(gl, [vs, slowFS], locations);

  const fastTime = time(gl, 'fast', fastPrg);
  const slowTime = time(gl, 'slow', slowPrg);

  // Because Safari is the new IE we can't not have attirbutes
  // as Safari fails the WebGL conformance tests for no attribute
  // situations.
  {
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, 1000000, gl.STATIC_DRAW);

    const posLoc = 0;  // assigned in createProgramInfo
    gl.enableVertexAttribArray(posLoc);
    // only taking X from buffer
    gl.vertexAttribPointer(posLoc, 1, gl.FLOAT, false, 0, 0);
  }

  const fastX = slowTime / fastTime;
  console.log(`fast is maybe ${fastX.toFixed(4)}x faster than slow`);
  console.log(gl.getError());

  function time(gl, label, prg) {
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.useProgram(prg);
    // use program once so any initialization behind the scenes
    // happens (voodoo)
    gl.drawArrays(gl.POINTS, 0, 1);
    sync(gl);
    const startTime = performance.now();
    for (let i = 0; i < 100; ++i) {
      gl.drawArrays(gl.POINTS, 0, 1000);
    }
    sync(gl);
    const endTime = performance.now();
    const elapsedTime = endTime - startTime;
    console.log(label, 'elapsedTime:', elapsedTime.toFixed(4));
    return elapsedTime;
  }
  
  function sync(gl) {
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
  }  
}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

Когда я бегу выше, я получаю результаты от 3,5x до 4,5x для быстрого шейдера над медленным шейдером, так что вы можете видеть, что результаты противоречивы, но по крайней мере мы знаем, что быстрый шейдер на самом деле быстрее медленного шейдера, что достаточно для нам выбрать один метод над другим.

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

gl.enable(gl.BLEND);

Таким образом, я считаю, что рендерер с плитками может рендерить только последний квад.

К сожалению, когда я запускаю приведенный выше пример на iOS, я получаю, что быстрое происходит медленнее, чем медленное, что показывает одно из (1), как плохое разрешение синхронизации в браузерах или (2), как работают разные плиточные архитектуры, или (3) что iOS драйвер действительно оптимизировал цикл.

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

function main() {
  const gl = document.createElement('canvas').getContext('webgl');

  const vs = `
  attribute vec4 position;
  void main() {
    gl_PointSize = 128.0;
    gl_Position = position;
  }
  `;

  const fastFS = `
  precision highp float;
  void main() {
    gl_FragColor = vec4(1);
  }
  `;

  const slowFS = `
  precision highp float;
  uniform vec4 junk;
  uniform vec4 moreJunk;
  uniform sampler2D tex;

  void main() {
    vec4 color = vec4(0);
    for (int i = 0; i < 100; ++i) {
      // AFAIK this can not be optimized too much as the inputs
      // change over the loop looking up different parts of the texture.
      color += texture2D(tex, fract(junk * moreJunk * float(i)).xy * gl_PointCoord.xy);
    }
    gl_FragColor = color;
  }
  `;

  const fastPrg = twgl.createProgram(gl, [vs, fastFS]);
  const slowPrg = twgl.createProgram(gl, [vs, slowFS]);

  // Because Safari is the new IE we can't not have attirbutes
  // as Safari fails the WebGL conformance tests for no attribute
  // situations.
  {
    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, 1000000, gl.STATIC_DRAW);

    const posLoc = 0;  // assigned in createProgramInfo
    gl.enableVertexAttribArray(posLoc);
    // only taking X from buffer
    gl.vertexAttribPointer(posLoc, 1, gl.FLOAT, false, 0, 0);
  }

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, tex);
  const data = new Uint8Array(1024 * 1024 * 4);
  for (let i = 0; i < data.length; ++i) {
    data[i] = Math.random() * 256;
  }
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
  gl.generateMipmap(gl.TEXTURE_2D);

  const fastTime = time(gl, 'fast', fastPrg);
  const slowTime = time(gl, 'slow', slowPrg);

  const fastX = slowTime / fastTime;
  console.log(`fast is maybe ${fastX.toFixed(4)}x faster than slow`);

  function time(gl, label, prg) {
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.useProgram(prg);
    // use program once so any initialization behind the scenes
    // happens (voodoo)
    gl.drawArrays(gl.POINTS, 0, 1);
    sync(gl);
    const startTime = performance.now();
    for (let i = 0; i < 100; ++i) {
      gl.drawArrays(gl.POINTS, 0, 1000);
    }
    sync(gl);
    const endTime = performance.now();
    const elapsedTime = endTime - startTime;
    console.log(label, 'elapsedTime:', elapsedTime.toFixed(4));
    return elapsedTime;
  }

  function sync(gl) {
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
  }

}
main();
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>

Теперь на моем iPhoneX я получаю, что быстрый намного быстрее, чем медленный.

Итак, что мы узнали? Вероятно, мы узнали, что если у ваших шейдеров одинаковый уровень производительности, будет сложно определить, какой из них надежнее.

...