2013-03-22 14 views
5

Sto tentando di riscrivere il rendering basato su canvas per il mio motore di gioco 2D. Ho fatto buoni progressi e posso rendere tridimensionali le trame sul contesto webgl, complete di ridimensionamento, rotazione e fusione. Ma la mia prestazione fa schifo. Sul mio portatile di prova, posso ottenere 30 fps in canvas vanilla 2d con 1.000 entità sullo schermo contemporaneamente; in WebGL, ottengo 30 fps con 500 entità sullo schermo. Mi aspetterei che la situazione fosse invertita!Il modo più veloce per le chiamate batch in WebGL

Ho un vago sospetto che il colpevole sia tutto questo immondizia Float32Array nel buffer che mi sto lanciando. Ecco il mio codice di rendering:

// 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); 
}, 

Ed ecco i miei semplici shader di test (questi mancano miscelazione, ridimensionamento & rotazione):

// 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; 
} 

Tutte le idee su come ottenere prestazioni migliori? C'è un modo per raggruppare i miei DrawArray? C'è un modo per ridurre la spazzatura del buffer?

Grazie!

+1

Per attrarre più intestati ti suggerisco di intitolare la tua domanda come "Il modo più veloce per estrarre batchArray su WebGL" –

+0

@Mikko Fatto. Grazie! –

+0

@AbrahamWalters: stai eseguendo l'esatto codice di rendering per ogni entità per ogni frame? (alias 500 * 30 = 1500 volte al secondo) Se è così, penso che la scheda/browser esaurirà la memoria entro un'ora (se non dieci minuti) se la lasci semplicemente seduta lì. –

risposta

7

Ci sono due grandi problemi che posso vedere qui che influenzeranno negativamente la vostra performance.

Stai creando molti Float32Array temporanei, che sono attualmente costosi da costruire (che dovrebbe migliorare in futuro). Sarebbe molto meglio in questo caso per creare un singolo array e impostare i vertici di volta in volta in questo modo:

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

Il problema più grande di gran lunga, tuttavia, è che si sta solo disegnando un singolo quad alla volta . Le API 3D semplicemente non sono progettate per farlo in modo efficiente. Quello che vuoi fare è provare e spremere tutti i triangoli possibili in ogni chiamata drawArrays/drawElements che fai.

Ci sono molti modi per farlo, il più semplice è riempire un buffer con quanti più quad puoi condividere la stessa trama, quindi disegnarli tutti in una volta. In psuedocodarlo:

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(); 
} 

Questo è una semplificazione eccessiva, e ci sono modi per lotti anche di più, ma si spera che vi dà un'idea di come iniziare il dosaggio le chiamate per migliorare le prestazioni.

+0

Questo ha molto senso. Posso sicuramente fare cose come tessere mappa e particelle, ma alla fine della giornata, legherò molto ogni fotogramma. C'è un modo per aggirare questo senza un atlante di texture? –

2

Se si utilizza WebGL Inspector, verrà visualizzato nella traccia se si eseguono istruzioni GL non necessarie (sono contrassegnate da uno sfondo giallo brillante). Questo potrebbe darti un'idea su come ottimizzare il tuo rendering.

In generale, ordina le chiamate di estrazione in modo che tutti utilizzino lo stesso programma, quindi gli attributi, quindi le trame e le uniformi vengano eseguite in ordine. In questo modo avrai poche istruzioni GL (e istruzioni JS) il più possibile.