2013-03-22 13 views
5

我正在嘗試爲我的2D遊戲引擎重寫基於畫布的渲染。我已經取得了很好的進展,並且可以很好地將紋理渲染到webgl上下文中,並通過縮放,旋轉和混合來完成。但我的表現很糟糕。在我的測試筆記本電腦上,我可以一次獲得屏幕上1000個實體的香草2d畫布中的30 fps;在WebGL中,我在屏幕上獲得了500個實體的30 fps。我期望的情況是相反的!在WebGL中批量調用的最快方式

我有一個偷偷摸摸的嫌疑,罪魁禍首是這一切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?有沒有辦法減少緩衝垃圾?

謝謝!

+1

爲了吸引更多的intested我建議你題爲你的問題,如「在WebGL上批量drawArrays的最快方法」 – 2013-03-22 15:54:40

+0

@Mikko完成。謝謝! – 2013-03-22 19:32:28

+0

@AbrahamWalters:您是否爲每個框架的每個實體運行確切的呈現代碼? (又名500 * 30 = 1500次/秒)如果是這樣,我認爲如果你讓它坐在那裏,標籤/瀏覽器在一個小時內(如果不是十分鐘)就會耗盡內存。 – 2013-03-22 23:01:51

回答

7

我在這裏可以看到兩個大問題,這會對您的表現產生不利影響。

您正在創建大量臨時Float32Arrays,這些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調用中擠壓儘可能多的三角形。

有幾種方法可以做到這一點,最直接的方法是用盡可能多的四邊形填充緩衝區,以便共享相同的紋理,然後一次繪製所有紋理。在僞代碼:

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

這是一個過於簡單化,而且有辦法批次甚至更多,但我希望,讓你如何開始分批您的通話獲得更好的性能的想法。

+0

這很有道理。我當然可以像地圖瓷磚和粒子一樣批量生產,但是在一天結束時,我會在每個框架上綁定紋理。沒有紋理圖集有沒有辦法解決這個問題? – 2013-03-24 03:46:43

2

如果您使用WebGL Inspector,您會在跟蹤中看到如果您執行了任何不必要的GL指令(它們標有明亮的黃色背景)。這可能會讓您瞭解如何優化渲染。

一般來說,排序您的繪製調用,所有使用相同的程序,然後屬性,然後紋理,然後制服按順序完成。通過這種方式,您將擁有儘可能少的GL指令(和JS指令)。