разница в производительности webgl между firefox и chrome - PullRequest
1 голос
/ 10 июля 2020

В настоящее время я разрабатываю инструмент обработки изображений для веб-приложения. Мне нужно взять png-изображения одинакового размера и объединить их попиксельно. Пока что я создал прототип (во многом вдохновленный учебниками на webglfundamentals.org), который берет два изображения и просто умножает их пиксели. Я использую библиотеку twgl-helper для webgl из http://twgljs.org/. (Что я, к сожалению, не смог вставить в скрипку).

У меня следующий вопрос: Может ли кто-нибудь объяснить или дать подсказки, почему Firefox 78 работает намного медленнее, чем недавний Chrome? FF в среднем составляет около 34 мс на рендеринг (полное обновление sh и стирание примерно sh между сэмплами), а Chrome в среднем 0,27 мс за рендеринг. Это разница на два порядка, которую я просто не могу объяснить. Я пробовал webgl2, он немного быстрее для обоих, но сохраняет безумную разницу между ними.

Если мне нужно предоставить дополнительную информацию, пожалуйста, дайте мне знать, я вернусь в офис в четверг. Спасибо за поддержку и идеи.

function main() {


    // Get A WebGL context
    var canvas = document.getElementById("webgl");
    var gl = canvas.getContext("webgl");
    if (!gl) {
        return;
    }

    var canvas1 = document.getElementById("canvas1");
    var canvas2 = document.getElementById("canvas2");

    // setup GLSL program
    var program = twgl.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
    gl.useProgram(program);

    var time0 = performance.now();
    // look up where the vertex data needs to go.
    var positionLocation = gl.getAttribLocation(program, "a_position");
    var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

    // provide texture coordinates for the rectangle.
    var texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0,
        0.0, 1.0,
        1.0, 0.0,
        1.0, 1.0]), gl.STATIC_DRAW);

    // vertex attributes need to be turned on explicitly
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

    // lookup uniforms
    var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

    // set the resolution
    gl.uniform2f(resolutionLocation, canvas1.width, canvas1.height);

    // Create a buffer for the position of the rectangle corners.
    var buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    // Set a rectangle the same size as the image.
    setRectangle(gl, 0, 0, canvas.width, canvas.height);
    // setRectangle(gl, 0, 0, 1000, 1000);


    function setupTexture(canvas, textureUnit, program, uniformName) {
        var tex = gl.createTexture();

        updateTextureFromCanvas(tex, canvas, textureUnit);

        // Set the parameters so we can render any size image.
        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_MAG_FILTER, gl.LINEAR);
        // mal ausprobieren
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        var location = gl.getUniformLocation(program, uniformName);
        gl.uniform1i(location, textureUnit);
    }

    function updateTextureFromCanvas(tex, canvas, textureUnit) {
        gl.activeTexture(gl.TEXTURE0 + textureUnit);
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
    }

    var tex1 = setupTexture(canvas1, 0, program, "u_canvas1");
    var tex2 = setupTexture(canvas2, 1, program, "u_canvas2");

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    var time1 = performance.now();
    console.log("Processing image took " + (time1 - time0) + " ms.");
    document.getElementById("performance").innerHTML = "Processing image took " + (time1 - time0) + " ms.";
}

function setRectangle(gl, x, y, width, height) {
    var x1 = x;
    var x2 = x + width;
    var y1 = y;
    var y2 = y + height;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        x1, y1,
        x2, y1,
        x1, y2,
        x1, y2,
        x2, y1,
        x2, y2]), gl.STATIC_DRAW);
}

const WIDTH = 1600;
const HEIGHT = 900;

addNewImage = function (path, id, width, height) {
    console.log(path)
    let newElement = document.createElement("canvas");
    document.body.appendChild(newElement);
    newElement.id = id;
    let ctx = newElement.getContext("2d");
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    let input = new Image();
    input.crossOrigin = "anonymous";
    input.onload = function () {
        ctx.drawImage(input, 0, 0);
    }
    input.src = path;
}



addNewImage("https://i.imgur.com/KjUybBD.png", "canvas1", WIDTH, HEIGHT);
addNewImage("https://i.imgur.com/ZKMnXce.png", "canvas2", WIDTH, HEIGHT);
canvas {
    border: 2px solid black;
    display: inline-block;
    width: 100%;
}
<script src="twgl.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<button onclick="main()">click</button>

<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform vec2 u_resolution;

varying vec2 v_texCoord;

void main() {
   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = a_position / u_resolution;

   // convert from 0->1 to 0->2
   vec2 zeroToTwo = zeroToOne * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;

    // our 2 canvases
    uniform sampler2D u_canvas1;
    uniform sampler2D u_canvas2;

    // the texCoords passed in from the vertex shader.
    // note: we're only using 1 set of texCoords which means
    //   we're assuming the canvases are the same size.
    varying vec2 v_texCoord;

    void main() {
         // Look up a pixel from first canvas
         vec4 color1 = texture2D(u_canvas1, v_texCoord);

         // Look up a pixel from second canvas
         vec4 color2 = texture2D(u_canvas2, v_texCoord);

         // return the 2 colors multiplied
         gl_FragColor = color1 * color2;
    }
</script>

<!-- <canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas> -->
<div id="performance"></div>
<canvas id="webgl" width="1600" height="900"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

1 Ответ

0 голосов
/ 11 июля 2020

Невозможно рассчитывать на такую ​​же производительность браузеров. Существует множество тестов, в которых один браузер в 2–40 раз быстрее другого.

В данном конкретном случае я не знаю, почему современный Firefox медленнее Chrome. Chrome является многопроцессорным (я думал, что Firefox было слишком в этот момент, но, возможно, нет), поэтому в Chrome время - это только время, необходимое для вставки команд в буфер команд, чтобы разговаривать с запущенным процессом веб-страницу в отдельный процесс, который общается с графическим процессором. Это не время, необходимое для запуска тех команд, которые выполняются параллельно с веб-страницей.

Если я добавлю это после вашего вызова отрисовки

// Force the webpage to wait for the GPU process
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));

Тогда я получу сопоставимое время для Chrome (27 мс) против Firefox (32 мс)

function main() {


    // Get A WebGL context
    var canvas = document.getElementById("webgl");
    var gl = canvas.getContext("webgl");
    if (!gl) {
        return;
    }

    var canvas1 = document.getElementById("canvas1");
    var canvas2 = document.getElementById("canvas2");

    // setup GLSL program
    var program = twgl.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
    gl.useProgram(program);

    var time0 = performance.now();
    // look up where the vertex data needs to go.
    var positionLocation = gl.getAttribLocation(program, "a_position");
    var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

    // provide texture coordinates for the rectangle.
    var texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        0.0, 0.0,
        1.0, 0.0,
        0.0, 1.0,
        0.0, 1.0,
        1.0, 0.0,
        1.0, 1.0]), gl.STATIC_DRAW);

    // vertex attributes need to be turned on explicitly
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

    // lookup uniforms
    var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

    // set the resolution
    gl.uniform2f(resolutionLocation, canvas1.width, canvas1.height);

    // Create a buffer for the position of the rectangle corners.
    var buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    // Set a rectangle the same size as the image.
    setRectangle(gl, 0, 0, canvas.width, canvas.height);
    // setRectangle(gl, 0, 0, 1000, 1000);


    function setupTexture(canvas, textureUnit, program, uniformName) {
        var tex = gl.createTexture();

        updateTextureFromCanvas(tex, canvas, textureUnit);

        // Set the parameters so we can render any size image.
        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_MAG_FILTER, gl.LINEAR);
        // mal ausprobieren
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        var location = gl.getUniformLocation(program, uniformName);
        gl.uniform1i(location, textureUnit);
    }

    function updateTextureFromCanvas(tex, canvas, textureUnit) {
        gl.activeTexture(gl.TEXTURE0 + textureUnit);
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
    }

    var tex1 = setupTexture(canvas1, 0, program, "u_canvas1");
    var tex2 = setupTexture(canvas2, 1, program, "u_canvas2");

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    // Force the webpage to wait for the GPU process
    gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));

    var time1 = performance.now();
    console.log("Processing image took " + (time1 - time0) + " ms.");
    document.getElementById("performance").innerHTML = "Processing image took " + (time1 - time0) + " ms.";
}

function setRectangle(gl, x, y, width, height) {
    var x1 = x;
    var x2 = x + width;
    var y1 = y;
    var y2 = y + height;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        x1, y1,
        x2, y1,
        x1, y2,
        x1, y2,
        x2, y1,
        x2, y2]), gl.STATIC_DRAW);
}

const WIDTH = 1600;
const HEIGHT = 900;

addNewImage = function (path, id, width, height) {
    console.log(path)
    let newElement = document.createElement("canvas");
    document.body.appendChild(newElement);
    newElement.id = id;
    let ctx = newElement.getContext("2d");
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    let input = new Image();
    input.crossOrigin = "anonymous";
    input.onload = function () {
        ctx.drawImage(input, 0, 0);
    }
    input.src = path;
}



addNewImage("https://i.imgur.com/KjUybBD.png", "canvas1", WIDTH, HEIGHT);
addNewImage("https://i.imgur.com/ZKMnXce.png", "canvas2", WIDTH, HEIGHT);
canvas {
    border: 2px solid black;
    display: inline-block;
    width: 100%;
}
<script src="twgl.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<button onclick="main()">click</button>

<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;

uniform vec2 u_resolution;

varying vec2 v_texCoord;

void main() {
   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = a_position / u_resolution;

   // convert from 0->1 to 0->2
   vec2 zeroToTwo = zeroToOne * 2.0;

   // convert from 0->2 to -1->+1 (clipspace)
   vec2 clipSpace = zeroToTwo - 1.0;

   gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points.
   v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;

    // our 2 canvases
    uniform sampler2D u_canvas1;
    uniform sampler2D u_canvas2;

    // the texCoords passed in from the vertex shader.
    // note: we're only using 1 set of texCoords which means
    //   we're assuming the canvases are the same size.
    varying vec2 v_texCoord;

    void main() {
         // Look up a pixel from first canvas
         vec4 color1 = texture2D(u_canvas1, v_texCoord);

         // Look up a pixel from second canvas
         vec4 color2 = texture2D(u_canvas2, v_texCoord);

         // return the 2 colors multiplied
         gl_FragColor = color1 * color2;
    }
</script>

<!-- <canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas> -->
<div id="performance"></div>
<canvas id="webgl" width="1600" height="900"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

Конечно, тот факт, что Chrome выполняет команды в другом процессе, означает, что вы получаете некоторую параллельную обработку бесплатно. Я думаю, что в большинстве случаев это выигрыш для Chrome производительности WebGL над Firefox производительности WebGL, но не всегда.

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

Прежде всего, 2D-холсты сохраняют свои данные как предварительно умноженные альфа, но по умолчанию WebGL не требует предварительного умножения альфа, поэтому . Он должен преобразовать эти данные в альфа без предварительного умножения, а затем загрузить их через glTexImage2D. (Медленно)

Браузер имеет 2D-холст во vram. Он загружает его в RAM, преобразует его в неумноженный альфа-канал, а затем загружает его через glTexImage2D (Even Slower)

Браузер имеет 2D-холст во vram. Он прикрепляет вашу текстуру к фреймбуферу и визуализирует в нем холст, используя шейдер, который не преумножает альфа-канал (быстро).

Я уверен, что Chrome применяет последний метод. Я знаю, что код существует. Я не знаю всех условий, необходимых для того, чтобы убедиться, что этот код используется, но я вполне уверен, что холст 1900x600 пойдет по этому пути (в какой-то момент холсты меньше определенного размера, например 256x256, были выполнены на CPU, а не на GPU, но Я понятия не имею, правда ли это по-прежнему)

Firefox может делать то же самое, а может и не делать, но если это не так, возможно, поэтому Chrome делает это за 27 мс, а Firefox за 32 мс когда мы останавливаем процесс графического процессора, вызывая gl.readPixels.

Более важным моментом является то, что браузеры могут оптимизировать множество различных способов, и нет гарантии, каким образом они будут или не будут оптимизировать.

...