Самый быстрый способ пакетных вызовов в WebGL

Я пытаюсь переписать мой рендеринг на основе холста для моего 2-го игрового движка. Я добился хороших результатов и прекрасно обрабатываю текстуры в контексте webgl, включая масштабирование, вращение и смешивание. Но моя работа отстой. На моем тестовом ноутбуке я могу получить 30 кадров в секунду в ванили 2d с 1000 объектами на экране одновременно; в WebGL я получаю 30 кадров в секунду с 500 объектами на экране. Я ожидаю, что ситуация будет обратная!

У меня есть подозрительное подозрение, что виновником является весь этот мусор буфера Float32Array, который я бросаю. Здесь мой код визуализации:

// boilerplate code and obj coordinates

// grab gl context
var canvas = sys.canvas;
var gl = sys.webgl;
var program = sys.glProgram;

// width and height
var scale = sys.scale;
var tileWidthScaled = Math.floor(tileWidth * scale);
var tileHeightScaled = Math.floor(tileHeight * scale);
var normalizedWidth = tileWidthScaled / this.width;
var normalizedHeight = tileHeightScaled / this.height;

var worldX = targetX * scale;
var worldY = targetY * scale;

this.bindGLBuffer(gl, this.vertexBuffer, sys.glWorldLocation);
this.bufferGLRectangle(gl, worldX, worldY, tileWidthScaled, tileHeightScaled);

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.texture);

var frameX = (Math.floor(tile * tileWidth) % this.width) * scale;
var frameY = (Math.floor(tile * tileWidth / this.width) * tileHeight) * scale;

// fragment (texture) shader
this.bindGLBuffer(gl, this.textureBuffer, sys.glTextureLocation);
this.bufferGLRectangle(gl, frameX, frameY, normalizedWidth, normalizedHeight);

gl.drawArrays(gl.TRIANGLES, 0, 6);

bufferGLRectangle: function (gl, x, y, width, height) {
    var left = x;
    var right = left + width;
    var top = y;
    var bottom = top + height;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        left, top,
        right, top,
        left, bottom,
        left, bottom,
        right, top,
        right, bottom
    ]), gl.STATIC_DRAW);
},

bindGLBuffer: function (gl, buffer, location) {
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
},

И вот мои простые тестовые шейдеры (им не хватает смешивания, масштабирования и вращения):

// fragment (texture) shader
precision mediump float;
uniform sampler2D image;
varying vec2 texturePosition;

void main() {
    gl_FragColor = texture2D(image, texturePosition);
}

// vertex shader
attribute vec2 worldPosition;
attribute vec2 vertexPosition;

uniform vec2 canvasResolution;
varying vec2 texturePosition;

void main() {
    vec2 zeroToOne = worldPosition / canvasResolution;
    vec2 zeroToTwo = zeroToOne * 2.0;
    vec2 clipSpace = zeroToTwo - 1.0;

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

Любые идеи о том, как повысить производительность? Есть ли способ запустить мои drawArrays? Есть ли способ сократить размер буфера?

Спасибо!

5
задан Abraham Walters 22 марта '13 в 5:46
источник поделиться

2 ответов

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

Вы создаете много временных Float32Arrays, которые в настоящее время дороги для построения (это должно стать лучше в будущем). В этом случае было бы намного лучше создать единый массив и каждый раз устанавливать вершины так:

verts[0] = left; verts[1] = top;
verts[2] = right; verts[3] = top;
// etc... 
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);

Тем не менее, большая проблема заключается в том, что вы рисуете только один квад за один раз. 3D API просто не предназначены для этого эффективно. То, что вы хотите сделать, это попытаться сжать как можно больше треугольников в каждый вызов drawArrays/drawElements, который вы делаете.

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

var MAX_QUADS_PER_BATCH = 100;
var VERTS_PER_QUAD = 6;
var FLOATS_PER_VERT = 2;
var verts = new Float32Array(MAX_QUADS_PER_BATCH * VERTS_PER_QUAD * FLOATS_PER_VERT);

var quadCount = 0;
function addQuad(left, top, bottom, right) {
    var offset = quadCount * VERTS_PER_QUAD * FLOATS_PER_VERT;

    verts[offset] = left; verts[offset+1] = top;
    verts[offset+2] = right; verts[offset+3] = top;
    // etc...

    quadCount++;

    if(quadCount == MAX_QUADS_PER_BATCH) {
        flushQuads();
    }
}

function flushQuads() {
    gl.bindBuffer(gl.ARRAY_BUFFER, vertsBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW); // Copy the buffer we've been building to the GPU.

    // Make sure vertexAttribPointers are set, etc...

    gl.drawArrays(gl.TRIANGLES, 0, quadCount + VERTS_PER_QUAD);
}

// In your render loop

for(sprite in spriteTypes) {
    gl.bindTexture(gl.TEXTURE_2D, sprite.texture);

    for(instance in sprite.instances) {
        addQuad(instance.left, instance.top, instance.right, instance.bottom);  
    }

    flushQuads();
}

Это упрощение, и есть способы сделать еще больше, но, надеюсь, это даст вам представление о том, как начать пакетные звонки для повышения производительности.

7
ответ дан Toji 23 марта '13 в 19:52
источник поделиться

Если вы используете WebGL Inspector, вы увидите в трассировке, если вы сделаете ненужные инструкции GL (они отмечены ярким желтым фоном). Это может дать вам представление о том, как оптимизировать рендеринг.

Вообще говоря, сортируйте свои призывы на призыв, чтобы все с использованием одной и той же программы, затем атрибуты, затем текстуры, а затем униформа выполнялись по порядку. Таким образом, вы будете иметь как можно меньше инструкций GL (и инструкций JS).

2
ответ дан MikaelEmtinger 23 марта '13 в 18:37
источник поделиться

Другие вопросы по меткам