2013-03-22 15 views
5

Ich versuche, meine Canvas-basierte Rendering für meine 2d Game Engine neu zu schreiben. Ich habe gute Fortschritte gemacht und kann Texturen in den Webgl-Kontext übertragen, komplett mit Skalierung, Drehung und Überblendung. Aber meine Leistung ist schlecht. Auf meinem Test-Laptop kann ich 30 fps in Vanille 2d Leinwand mit 1.000 Einheiten auf dem Bildschirm auf einmal bekommen; In WebGL bekomme ich 30 fps mit 500 Entitäten auf dem Bildschirm. Ich würde erwarten, dass die Situation umgekehrt ist!Der schnellste Weg zu Batchaufrufen in WebGL

Ich habe einen schleichenden Verdacht, dass der Schuldige ist all dies Float32Array Puffer Müll Ich bin herumwerfen. Hier ist mein Render-Code:

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

Und hier ist meine einfachen Test-Shadern (diese Mischung fehlt, Skalierung & Rotation):

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

Alle Ideen, wie eine bessere Leistung zu bekommen? Gibt es eine Möglichkeit, meine drawArrays zu chargen? Gibt es eine Möglichkeit, den Puffermüll zu reduzieren?

Danke!

+1

Um interessanter zu werden, schlage ich vor, dass Sie Ihre Frage wie "Der schnellste Weg, Batch-DrawArrays auf WebGL" –

+0

@Mikko Done. Vielen Dank! –

+0

@AbrahamWalters: Führen Sie diesen genauen Render-Code für jede Entität für jeden Frame aus? (aka 500 * 30 = 1500 mal pro Sekunde) Wenn das der Fall ist, denke ich, dass dem Tab/Browser innerhalb einer Stunde (wenn nicht zehn Minuten) der Speicher ausgehen wird, wenn Sie es einfach dort sitzen lassen. –

Antwort

7

Es gibt zwei große Probleme, die ich hier sehen kann, die Ihre Leistung beeinträchtigen werden.

Sie erstellen eine Menge temporärer Float32Arrays, die derzeit teuer zu bauen sind (Das sollte in Zukunft besser werden). Es wäre in diesem Fall viel besser, ein einzelnes Array zu erstellen und zu den Eckpunkten jedes Mal, wie so festgelegt:

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

Das größere Problem bei weitem jedoch ist, dass Sie nur eine einzige Quad zu einer Zeit, Zeichnung . 3D-APIs sind einfach nicht dafür ausgelegt, dies effizient zu tun. Was Sie tun möchten, ist zu versuchen, so viele Dreiecke wie möglich in jeden drawArrays/drawElements Aufruf zu drücken, den Sie machen.

Es gibt mehrere Möglichkeiten, das zu tun, die einfachste ist, einen Puffer mit so vielen Quadraten wie möglich zu füllen, die die gleiche Textur teilen, und dann alle auf einmal zu zeichnen. In 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(); 
} 

Das ist eine grobe Vereinfachung ist, und es gibt Möglichkeiten, um Batch noch, aber hoffentlich, dass Sie eine Vorstellung davon, wie Dosieren Sie Ihre Anrufe für eine bessere Leistung zu starten gibt.

+0

Das macht viel Sinn. Ich kann zwar Dinge wie Kartenkacheln und Partikel parieren, aber am Ende des Tages werde ich viele Frames binden. Gibt es einen Weg ohne einen Texturatlas? –

2

Wenn Sie WebGL Inspector verwenden, sehen Sie in der Ablaufverfolgung, wenn Sie unnötige GL-Anweisungen (sie sind mit leuchtend gelben Hintergrund markiert). Dies könnte Ihnen eine Idee geben, wie Sie Ihr Rendering optimieren können.

Im Allgemeinen sortieren Sie Ihre Zeichenaufrufe, so dass alle mit dem gleichen Programm, dann Attribute, dann Texturen und dann Uniformen in der Reihenfolge erfolgen. Auf diese Weise haben Sie so wenig GL-Anweisungen (und JS-Anweisungen) wie möglich.

Verwandte Themen