Conversation
|
buffer_shim stuff is temporary until you move over everything to Buffer2, right? |
js/data/buffer2.js
Outdated
There was a problem hiding this comment.
@lucaswoj wanna move this to the top of the function so we don't do the above work if we're just going to throw an error.
There was a problem hiding this comment.
well, if it we're actually throwing an error, performance doesn't matter much :)
There was a problem hiding this comment.
true, still, better to do validation before the work not after, if only for readability :)
There was a problem hiding this comment.
This is an overloaded constructor. Like Buffer1, it can either
- create an empty buffer from a configuration object
- rehydrate a serialized instance of
Buffer
The idea with the util.assert(this.type) line is that we allow each case to parse the constructor arguments independently and then perform a little validation on the resulting instance. It is not necessary to perform this assertion but I found that this.type had a tendency to end up as undefined and that resulted in hard-to-debug errors.
In a larger sense, I sorta want to rewrite this whole constructor to eliminate this branching altogether. It should be possible to combine these two cases into one.
There was a problem hiding this comment.
I'd also consider removing asserts once the code has been tested and finalized. It's kinda all or nothing — either we add asserts everywhere throughout the whole code bringing a lot of cruft with it, or don't add them and rely on tests and throwing specific errors in special cases instead.
|
@mourner Yes, the "shim" is a temporary measure to DRY up the code that wraps a My intention is for the shim to get merged into master temporarily so we can start testing |
|
Are they incompatible enough to write a shim? I see that it proxies most of the methods. Could you just make Buffer2 have things that other code relies on in Buffer1, and then remove any cruft after rewriting that code? |
da3266c to
fbe0a60
Compare
There was a problem hiding this comment.
could those move to the prototype below?
There was a problem hiding this comment.
Yes, the attributes could move to the prototype, but ideally they wouldn't move to the prototype. The raison-d'etre of Buffer2 is that it has a configurable buffer layout (vs Buffer1 which had a hardcoded buffer layout). Layout is intended to be passed as a constructor argument at runtime. This code represents an intermediary step towards that goal.
|
@lucaswoj looks great! Let's figure out the fill issues before proceeding. |
js/util/util.js
Outdated
There was a problem hiding this comment.
Could we just use console.assert instead of a custom function here? In addition to making it unnecessary, this has an advantage of being supported by https://github.com/twada/unassertify
There was a problem hiding this comment.
Looks like console.assert is not and will not be widely supported. util.assert is just a shorthand around if (blah) throw new Error(...). Validating preconditions is especially helpful because WebGL is bad at reporting errors. I can remove the util.assert statements before merging this code if you prefer.
There was a problem hiding this comment.
No, I'm convinced on assertions — let's keep, but looks like we'll use the assert module so that it works with unassertify.
There was a problem hiding this comment.
Just merged #1557, so if you rebase on master you can start using assert.
|
Latest commit addresses PR feedback, renames |
|
FPS benchmarks look 👍 so far |
|
I would expect performance effects to be largely confined to the Worker. The end result is the same on the GPU, so FPS wouldn't change, but the time taken to build the Buffer in the background could change. It would be nice to have some benchmarks for that. |
|
@jfirebaugh How about we add a benchmark that measures the total time spent creating buffers during a slow |
|
A very unscientific test using the new |
d714667 to
5287956
Compare
|
Thanks to @mcwhittemore, we now have all buffers rewritten using Buffer2 and... drumroll... Its about 10x slower than master I haven't even begun to think about performance optimizations, so I'm hopeful that there'll be some easy gains. |
|
Got the This is roughly 2x slower than |
|
Just as I predicted a while back. I could take a look at optimizing this more. In the worst case, if we can't make any more perf improvements and it's still too slow, there's an option of code generation, similar to what I did with layer filters. It can bring us back to master performance while being dynamically generated from attribute config. |
js/data/buffer.js
Outdated
There was a problem hiding this comment.
Performance optimization: eliminate this condition. Either:
- Always require an array, or
- Write
this.set = function() { ... }dynamically in the constructor, and make it require an array for multiple attributes, and require no array for single attributes. For multiple attributes, it may help to iterate overoptions.attributesrather thanitem(like, maybe the compiler will detect thatoptions.attributesis constant).
Writing this.set = function() { ... } dynamically in the constructor can hopefully give you some nice speed ups, while stopping just short of needing code-gen + eval.
There was a problem hiding this comment.
LineVertexBuffer::add is one of the hottest functions in the codebase. It looks like dropping this call to Math.round gives us a substantial performance boost but changes the output just enough to break test-suite. Can we afford to modify test-suite for this? Can we leverage this information in another way? cc @mourner @jfirebaugh
There was a problem hiding this comment.
cc @kkaefer -- the rounding here goes way back, at least as far as 5cf0c0b#diff-ede55fc20965ad828d7e5cf6eaa5c404R50.
|
Just wrote up a little code-gen prototype. With the removal of ... without the removal of I've openend a separate ticket for this discussion at #1573 |
|
@lucaswoj if the methods are generated now, why is it slower with |
|
One of the things that could cause slowdowns is the need to create new objects and arrays for every push. Now we have the following syntax: this.push({
pos: [
(point.x << 1) | tx,
(point.y << 1) | ty
],
data: [
EXTRUDE_SCALE * extrude.x,
EXTRUDE_SCALE * extrude.y,
Math.round(EXTRUDE_SCALE * extrude.x),
Math.round(EXTRUDE_SCALE * extrude.y)
]
});Can we change the generated code to turn this into e.g. just the following? this.push(
(point.x << 1) | tx,
(point.y << 1) | ty,
EXTRUDE_SCALE * extrude.x,
EXTRUDE_SCALE * extrude.y,
Math.round(EXTRUDE_SCALE * extrude.x),
Math.round(EXTRUDE_SCALE * extrude.y)); |
|
Yep, that seems to do it. Sketched out the idea in bea8d6d. On my machine, the bench produces: 539 ms, 128 samples for /dist/mapbox-gl.js # master
741 ms, 128 samples for /dist/mapbox-gl.js # buffer2
566 ms, 128 samples for /dist/mapbox-gl.js # flat-push
558 ms, 129 samples for /dist/mapbox-gl.js # flat-push, no Math.roundFor some reason removing Also note how the buffer code could be simplified further with this flat approach. |
|
On a side note, just noticed that |
There was a problem hiding this comment.
This should be SHORT rather than UNSIGNED_SHORT.
|
Also, Generally I see that we're in a great position to remove To eliminate add: function(x, y, extrudeX, extrudeY) {
this.push(
(x * 2) + ((extrudeX + 1) / 2),
(y * 2) + ((extrudeY + 1) / 2));
},
...
circleVertex.add(x, y, -1, -1);
circleVertex.add(x, y, 1, -1);Becomes: circleVertex.push(x * 2, y * 2);
circleVertex.push(x * 2 + 1, y * 2);We also may want to keep the multiply-floor packing logic in attribute configs so that things like this are not a bucket concern: this.push(
x, y,
Math.floor(ox * 64), // use 1/64 pixels for placement
Math.floor(oy * 64),
Math.floor(tx / 4), /* tex */
Math.floor(ty / 4), /* tex */To do this, we simply add a this.push(
x, y,
ox, oy,
tx, ty,
...
attributes: [{
name: 'pos',
components: 2,
type: Buffer.AttributeType.SHORT
}, {
name: 'extrude',
components: 2,
type: Buffer.AttributeType.SHORT,
multiplier: 64, // use 1/64 pixels for placement
}, {
name: 'data1',
components: 4,
type: Buffer.AttributeType.UNSIGNED_BYTE,
multiplier: 1 / 4
}, {I'm up for dropping To eliminate custom gl.vertexAttribPointer(shader.a_pos, 2, gl.SHORT, false, stride, offset + 0);
gl.vertexAttribPointer(shader.a_offset, 2, gl.SHORT, false, stride, offset + 4);
gl.vertexAttribPointer(shader.a_data1, 4, gl.UNSIGNED_BYTE, false, stride, offset + 8);
gl.vertexAttribPointer(shader.a_data2, 2, gl.UNSIGNED_BYTE, false, stride, offset + 12);To make things more confusing, vertexAttribPointer configuration happens in two places. For some buffer types like line vertex, it's inside |
|
Feel free to fast-forward |
|
Thanks for the 👀 @mourner! I appreciate your thoughtful feedback. Below, I've enumerated your proposed next steps and added my own thoughts. add a
|
|
@lucaswoj good plan, let's move with this!
Actually I think we can leave this for a separate PR. It probably doesn't affect performance too much, I was looking forward to it mostly because it removes unnecessary code and keeps all buffer logic in one neat place.
Also |
|
I think this is ready to 🚢! Any last words? |
js/data/buffer.js
Outdated
There was a problem hiding this comment.
NVM. Offset is what's changing here.
|
Going to 🚢 as soon as CI passes |
|
Not sure why you squashed everything into one commit, both me and John where not in favor of that... |
|
Oh, sorry, I see the problem now — it was hard to rebase in a way where each commit is in a good state, so squashing is good here. Fair enough. |
|
Sorry about the squash, @mourner. I tried to come up with a clean rebase but there were a bugs and accidental check-ins in the intermediate commits. |

recut from #1414
Up until now, GL JS has used hand-coded classes to specify buffer layouts and buffer manipulation operations. This worked well pre-data-driven-styling because there were < 10 possible buffer layouts. For example, here is the
TriangleElementBufferclassPost-data-driven styling, however, the number of possible buffer layouts is practically infinite because any subset of paint properties might be included in the buffer.
The first step towards proper data driven styling was to create a more general and powerful framework for buffers (temporarily called
Buffer2) which allows buffer layouts to be specified by configuration rather than code. UsingBuffer2, We could write the sameTriangleElementBufferclass as... but in practice we wouldn't write it that way. GL JS would choose the attributes for that buffer at runtime for the best performance.
The
Buffer2class will know how to construct, write to, and read from GL buffers but have no semantic understanding of their contents.Instances of
Buffer2can be (destructively) serialized and transferred across threads.cc @tmcw @mourner @kkaefer