2013-03-22 2 views
5

2 차원 게임 엔진에 대한 캔버스 기반 렌더링을 다시 작성하려고합니다. 나는 좋은 성과를 거두었고 스케일링, 회전 및 블렌딩으로 완성 된 webgl 컨텍스트에 텍스처를 잘 렌더링 할 수 있습니다. 그러나 나의 연기는 빤다. 내 테스트 랩톱에서는 바닐라 2D 캔버스에서 화면 당 1,000 개의 항목으로 한 번에 30fps를 얻을 수 있습니다. WebGL에서 화면에 500 개의 항목이있는 30fps를 얻습니다. 나는 그 상황이 역전 될 것으로 기대한다!WebGL에서 가장 빠른 호출 일괄 처리

나는 잠들었습니다. 범인은이 모든 것입니다. Float32Array 쓰레기 버리는 곳입니다. 더 나은 성능을 얻는 방법에

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

어떤 아이디어 : (& 회전 스케일링, 이러한 혼합 누락) 내 간단한 테스트 쉐이더의 여기

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

을 그리고 : 여기 내 렌더링 코드는? 내 drawArrays를 일괄 처리하는 방법이 있습니까? 버퍼 쓰레기를 줄이는 방법이 있습니까?

감사합니다.

+1

좀 더 집중적으로하기 위해 "WebGL에서 drawArrays를 일괄 처리하는 가장 빠른 방법" –

+0

@Mikko Done과 같은 제목을 사용하는 것이 좋습니다. 감사! –

+0

@AbrahamWalters : 각 프레임의 각 엔티티에 대한 정확한 렌더링 코드를 실행하고 있습니까? (일명 500 * 30 = 1500 번/초) 그렇다면 탭/브라우저의 메모리가 1 시간 안에 부족한 경우 (10 분이 아닌 경우) 그냥 앉아있을 것입니다. –

답변

7

여기서 볼 수있는 두 가지 큰 문제가 성능에 나쁜 영향을 미칩니다.

당신은 현재 임시로 많은 수의 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(); 
} 

.

+0

많은 의미가 있습니다. 지도 타일과 입자 같은 것들을 배치 할 수 있지만, 하루가 끝날 때마다 텍스처를 여러 프레임마다 묶을 것입니다. 텍스처 아트라스가 없으면 주위에 어떤 방법이 있습니까? –

2

WebGL Inspector를 사용하는 경우 불필요한 GL 지침 (밝은 노란색 배경으로 표시됨)을 수행하면 추적에 표시됩니다. 이렇게하면 렌더링을 최적화하는 방법에 대한 아이디어를 얻을 수 있습니다.

일반적으로 말하자면 동일한 프로그램, 속성, 텍스처 및 유니폼을 순서대로 사용하도록 그리기 호출을 정렬하십시오. 이렇게하면 가능한 한 GL 지침 (및 JS 지침)을 거의 갖지 않게됩니다.