Skip to content

Commit 1b7eaad

Browse files
committed
First (nearly) working batch drawing test. Instead of pushing rectangles as pairs of triangles individually, this version creates a vertex buffer object for all visible tile rectangles, with degenerate triangles separating each one from the next. The drawing is done by a single call to gl.drawArrays.
Bizarrely, this version still chokes in Firefox, implying that the previous demo's horrible performance was not down to the GPU blocking as the rectangles are sent to it. The test is not fully working because it does not scroll the drawing position. So instead of the map moving when you hit a scroll boundary, it simply stops drawing the top or left edges as the scroll region moves away from the visible window. It's an easy fix, but I think I'll leave it until I find out exactly why Firefox still chokes on this demo. Progress text file updated with latest experiments and progress today.
1 parent 55870bc commit 1b7eaad

3 files changed

Lines changed: 147 additions & 32 deletions

File tree

docs/_phaser_tilemap_GL_progress.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,19 @@ tile properties - works perfectly
6262
* tilemap ray cast - draws map but not debug layer, impossible to tell if raycast is working without debug
6363
tileset from bitmapdata - works perfectly
6464

65-
* demos that fail for other reasons that 'tiles out by one'
65+
* demos that fail for other reasons than 'tiles out by one'
6666

67+
create from objects: looks like the actual 'createFromObjects' call is working perfectly (coins). The failure is due to the lack of ability to handle multiple tile source images per tilemap. Looking at features_test.json (the map source) it appears that there are multiple layers with different sources... I'll start by supporting multiple layers.
68+
With some help from Rich, finally discovered the Tileset class and it's firstgid value. Also discovered the 'draw' function in there which is hard-wired to Canvas drawing. Options: spin off TilesetGl class, or create drawGl function and branch on every draw call, or duplicate the firstgid logic in my new GL drawing code. I prefer the third option. At some point that draw in Tileset should be moved because Tileset is a data-holder class and shouldn't really contain drawing logic (despite how convenient it is to put it there to access firstgid etc from Tileset). My rationale for this separation is that rendering for browsers is very similar to writing game for old-school consoles - we separated the code that touches hardware from the logic code, because then we could convert the game to a different platform easily. In this case the 'different platform' is webgl but the approach should be the same.
6769

6870

6971

72+
day four:
73+
74+
Read through the new PIXI 4 tilemap code to see another approach to this task. It looks very similar (beautiful code though, mine is still very rough). They're using TRIANGLE rather than TRIANGLE_STRIP which I believe will give this implementation a slight speed edge when I add proper batching. There's an anim x,y property which is passed to the shaders, I'll need to look at their shader code to see how that's being used... might be a clever way of allowing map tile animations and passing most of the work off to the GPU.
75+
76+
Ran a profiler on my code using FireFox which runs the sci-fly demo extremely slowly (6 fps). As expected 95% of the time is locked in the _renderTile function, I'm virtually certain that the GPU is blocking because I'm only sending pairs in my TRIANGLE_STRIP at the moment. I shall prioritise the batch processing then re-run those tests to make sure the problem is solved. It would be very interesting to find out why Chrome and Canary on my computer do not suffer from the same problem (after all, it's the same GPU!). But I suspect I won't ever be able to answer that question in light of Rich's results (it ran slowly on his Chrome browser with a more powerful GPU...)
77+
78+
The batch changes are in, in a very rough form. The entire visible screen is now drawn with a single gl.drawArrays call after the JS sets up a large VBO with degenerate tris to separate the individual tiles from each other. This approach was hugely successful at drawing in the "pbRenderer" (now called "Beam"), however it is still choking FireFox. Further investigation is needed including: test the old pbRenderer demos to make sure they don't choke FireFox, if it does then check the PIXI 4 tilemap renderer to see if that does too. If the old demos work properly, then compare them with this new implementation line-by-line to find out what's causing the problem now.
79+
80+

src/pixi/extras/Tilemap.js

Lines changed: 108 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, lay
2828
this.texTilesHigh = Math.ceil(this.texture.height / this.tileHigh);
2929

3030
// proportion of texture used by one tile (uv coordinate scales)
31-
this.sx = this.tileWide / this.texture.width;
32-
this.sy = this.tileHigh / this.texture.height;
31+
this.scalex = this.tileWide / this.texture.width;
32+
this.scaley = this.tileHigh / this.texture.height;
3333

3434
// TODO: switch here to create DisplayObjectContainer at correct size for the render mode
3535
this.width = this.mapWide * this.tileWide;
@@ -61,8 +61,11 @@ PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, lay
6161
*/
6262
this.blendMode = PIXI.blendModes.NORMAL;
6363

64+
// calculate size of map
65+
var mapSize = mapwidth * mapheight * 16;
66+
6467
// create buffer with all data required by shader to draw this object
65-
this.buffer = new PIXI.Float32Array(16);
68+
this.buffer = new PIXI.Float32Array( mapSize * 16 );
6669

6770
/**
6871
* create buffer data for the webgl rendering of this tile
@@ -80,20 +83,18 @@ PIXI.Tilemap = function(texture, mapwidth, mapheight, tilewidth, tileheight, lay
8083
var t = 0;
8184
var b = t + this.tileHigh;
8285

83-
this.buffer[ 0 ] = this.buffer[ 4 ] = l;
84-
this.buffer[ 1 ] = this.buffer[ 9 ] = b;
85-
this.buffer[ 8 ] = this.buffer[ 12] = r;
86-
this.buffer[ 5 ] = this.buffer[ 13] = t;
87-
88-
// texture source position for the whole texture (uv coordinates adjusted for each tile)
89-
// l, b, 2,3
90-
// l, t, 6,7
91-
// r, b, 10,11
92-
// r, t, 14,15
93-
this.buffer[ 2 ] = this.buffer[ 6 ] = 0;
94-
this.buffer[ 3 ] = this.buffer[ 11] = 1;
95-
this.buffer[ 10] = this.buffer[ 14] = 1;
96-
this.buffer[ 7 ] = this.buffer[ 15] = 0;
86+
for(var i = 0; i < mapSize; i++)
87+
{
88+
var b = i * 16;
89+
this.buffer[ b + 0 ] = this.buffer[ b + 4 ] = l;
90+
this.buffer[ b + 1 ] = this.buffer[ b + 9 ] = b;
91+
this.buffer[ b + 8 ] = this.buffer[ b + 12] = r;
92+
this.buffer[ b + 5 ] = this.buffer[ b + 13] = t;
93+
this.buffer[ b + 2 ] = this.buffer[ b + 6 ] = 0;
94+
this.buffer[ b + 3 ] = this.buffer[ b + 11] = 1;
95+
this.buffer[ b + 10] = this.buffer[ b + 14] = 1;
96+
this.buffer[ b + 7 ] = this.buffer[ b + 15] = 0;
97+
}
9798
};
9899

99100
// constructor
@@ -225,7 +226,7 @@ PIXI.Tilemap.prototype._renderVisibleTiles = function(renderSession)
225226
// bind the source buffer
226227
gl.bindBuffer( gl.ARRAY_BUFFER, this.positionBuffer );
227228

228-
// draw the entire map layer
229+
// draw the visible portion of the map layer
229230
this._renderVisibleLayer(this.layer, renderSession);
230231
};
231232

@@ -234,24 +235,103 @@ PIXI.Tilemap.prototype._renderVisibleLayer = function( _layer, renderSession )
234235
{
235236
var gl = renderSession.gl;
236237
var shader = renderSession.shaderManager.tilemapShader;
238+
// this.shaders.setProgram(this.shaders.blitShaderProgram, _textureNumber);
239+
237240
var firstX = Math.max(Math.floor(this.scrollX / this.tileWide), 0);
238-
var firstY = Math.max(Math.floor(this.scrollY / this.tileHigh), 0);
239241
var lastX = Math.min(firstX + Math.ceil(this.game.width / this.tileWide) + 1, this.mapWide);
242+
var firstY = Math.max(Math.floor(this.scrollY / this.tileHigh), 0);
240243
var lastY = Math.min(firstY + Math.ceil(this.game.height / this.tileHigh) + 1, this.mapHigh);
244+
var len = (lastX - firstX) * (lastY - firstY);
245+
246+
var screenWide2 = gl.drawingBufferWidth * 0.5;
247+
var screenHigh2 = gl.drawingBufferHeight * 0.5;
248+
249+
// calculate inverse to avoid division in loop
250+
var iWide = 1.0 / screenWide2;
251+
var iHigh = 1.0 / screenHigh2;
252+
253+
var scale = 1.0;
254+
var wide = this.tileWide * scale * 0.5 / screenWide2;
255+
var high = this.tileHigh * scale * 0.5 / screenHigh2;
241256

242-
for(var y = firstY; y < lastY; y++)
257+
var old_t;
258+
var old_r;
259+
260+
var c = 0;
261+
var buffer = this.buffer;
262+
for(var ty = firstY; ty < lastY; ty++)
243263
{
244-
var layerRow = _layer.data[y];
245-
for(var x = firstX; x < lastX; x++)
264+
var layerRow = _layer.data[ty];
265+
var sy = ty * this.tileHigh;
266+
267+
for(var tx = firstX; tx < lastX; tx++)
246268
{
247-
var tile = layerRow[x].index - 1;
269+
var tile = layerRow[tx].index - 1;
270+
248271
if ( tile >= 0 )
249272
{
250-
this._renderTile(gl, shader, x * this.tileWide, y * this.tileHigh, tile);
273+
var sx = tx * this.tileWide;
274+
275+
var tmx = tile % this.texTilesWide;
276+
var tmy = Math.floor(tile / this.texTilesWide);
277+
278+
// from blitSimpleDrawAnimImages
279+
var uvl = tmx * this.scalex;
280+
var uvr = uvl + this.scalex;
281+
var uvt = tmy * this.scaley;
282+
var uvb = uvt + this.scaley;
283+
284+
//this._renderTile(gl, shader, bi, x * this.tileWide, y * this.tileHigh, tile);
285+
286+
var x = sx * iWide - 1;
287+
var y = 1 - sy * iHigh;
288+
var l = x - wide;
289+
var b = y + high;
290+
291+
if ( c > 0 )
292+
{
293+
// degenerate triangle: repeat the last vertex
294+
buffer[ c ] = old_r;
295+
buffer[ c + 1 ] = old_t;
296+
// repeat the next vertex
297+
buffer[ c + 4 ] = l;
298+
buffer[ c + 5 ] = b;
299+
// texture coordinates
300+
buffer[ c + 2 ] = buffer[ c + 6 ] = uvl;
301+
buffer[ c + 3 ] = buffer[ c + 7 ] = uvt;
302+
303+
c += 8;
304+
}
305+
306+
// screen destination position
307+
// l, b, 0,1
308+
// l, t, 4,5
309+
// r, b, 8,9
310+
// r, t, 12,13
311+
312+
buffer[ c ] = buffer[ c + 4 ] = l;
313+
buffer[ c + 1 ] = buffer[ c + 9 ] = b;
314+
buffer[ c + 8 ] = buffer[ c + 12] = old_r = x + wide;
315+
buffer[ c + 5 ] = buffer[ c + 13] = old_t = y - high;
316+
317+
// texture source position
318+
// l, b, 2,3
319+
// l, t, 6,7
320+
// r, b, 10,11
321+
// r, t, 14,15
322+
buffer[ c + 2 ] = buffer[ c + 6 ] = uvl; // l
323+
buffer[ c + 3 ] = buffer[ c + 11] = uvt; // t
324+
buffer[ c + 10] = buffer[ c + 14] = uvr; // r
325+
buffer[ c + 7 ] = buffer[ c + 15] = uvb; // b
326+
327+
c += 16;
251328
}
252329
}
253330
}
254331

332+
gl.bufferData( gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW );
333+
gl.vertexAttribPointer( shader.aPosition, 4, gl.FLOAT, false, 0, 0 );
334+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, len * 6 - 2);
255335
};
256336

257337

@@ -297,6 +377,7 @@ PIXI.Tilemap.prototype._renderLayer = function( _layer, renderSession )
297377
{
298378
var gl = renderSession.gl;
299379
var shader = renderSession.shaderManager.tilemapShader;
380+
var bi = 0;
300381

301382
var wide = _layer.width, high = _layer.height;
302383
for(var y = 0; y < high; y++)
@@ -307,15 +388,16 @@ PIXI.Tilemap.prototype._renderLayer = function( _layer, renderSession )
307388
var tile = layerRow[x].index;
308389
if ( tile >= 0 )
309390
{
310-
this._renderTile(gl, shader, x * this.tileWide, y * this.tileHigh, tile);
391+
this._renderTile(gl, shader, bi, x * this.tileWide, y * this.tileHigh, tile);
392+
bi += 16;
311393
}
312394
}
313395
}
314396

315397
};
316398

317399

318-
PIXI.Tilemap.prototype._renderTile = function(gl, shader, x, y, tile)
400+
PIXI.Tilemap.prototype._renderTile = function(gl, shader, bufferIndex, x, y, tile)
319401
{
320402
// if repeating same tile, skip almost everything...
321403
if ( tile != this.lastTile )

src/pixi/renderers/webgl/shaders/TilemapShader.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,30 @@ PIXI.TilemapShader = function(gl)
2929
*/
3030
this.program = null;
3131

32+
this.fragmentSrc = [
33+
" precision lowp float;",
34+
" uniform sampler2D uImageSampler;",
35+
" varying vec2 vTexCoord;",
36+
" void main(void) {",
37+
" gl_FragColor = texture2D(uImageSampler, vTexCoord);",
38+
" }"
39+
];
40+
41+
this.vertexSrc = [
42+
" precision lowp float;",
43+
" attribute vec4 aPosition;",
44+
" varying vec2 vTexCoord;",
45+
" void main(void) {",
46+
" gl_Position.zw = vec2(1, 1);",
47+
" gl_Position.xy = aPosition.xy;",
48+
" vTexCoord = aPosition.zw;",
49+
" }"
50+
];
51+
3252
/**
3353
* The fragment shader.
3454
* @property fragmentSrc
3555
* @type Array
36-
*/
3756
this.fragmentSrc = [
3857
" precision mediump float;",
3958
" uniform sampler2D uImageSampler;",
@@ -42,12 +61,12 @@ PIXI.TilemapShader = function(gl)
4261
" gl_FragColor = texture2D(uImageSampler, vTexCoord);",
4362
" }"
4463
];
64+
*/
4565

4666
/**
4767
* The vertex shader.
4868
* @property vertexSrc
4969
* @type Array
50-
*/
5170
this.vertexSrc = [
5271
" uniform vec2 uScreenPosition;",
5372
" attribute vec4 aPosition;",
@@ -59,6 +78,7 @@ PIXI.TilemapShader = function(gl)
5978
" vTexCoord = aPosition.zw;",
6079
" }"
6180
];
81+
*/
6282

6383
/**
6484
* A local texture counter for multi-texture shaders.
@@ -70,6 +90,7 @@ PIXI.TilemapShader = function(gl)
7090
this.init();
7191
};
7292

93+
7394
PIXI.TilemapShader.prototype.constructor = PIXI.TilemapShader;
7495

7596
/**
@@ -86,11 +107,12 @@ PIXI.TilemapShader.prototype.init = function()
86107

87108
// get and store the attributes
88109
this.aPosition = gl.getAttribLocation(program, 'aPosition');
89-
this.uProjectionMatrix = gl.getUniformLocation(program, 'uProjectionMatrix');
90-
this.uScreenPosition = gl.getUniformLocation(program, 'uScreenPosition');
110+
// this.uProjectionMatrix = gl.getUniformLocation(program, 'uProjectionMatrix');
111+
// this.uScreenPosition = gl.getUniformLocation(program, 'uScreenPosition');
91112
this.uSampler = gl.getUniformLocation(program, 'uImageSampler');
92113

93-
this.attributes = [this.aScreenPosition, this.aPosition, this.uProjectionMatrix, this.uSampler];
114+
// this.attributes = [this.aScreenPosition, this.aPosition, this.uProjectionMatrix, this.uSampler];
115+
this.attributes = [this.aPosition, this.uSampler];
94116

95117
this.program = program;
96118
};

0 commit comments

Comments
 (0)