Skip to content

[wip] Added Buffer2 class#1414

Closed
lucaswoj wants to merge 2 commits intov9from
v8-dds-buffer2
Closed

[wip] Added Buffer2 class#1414
lucaswoj wants to merge 2 commits intov9from
v8-dds-buffer2

Conversation

@lucaswoj
Copy link
Contributor

@lucaswoj lucaswoj commented Aug 7, 2015

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 TriangleElementBuffer class

module.exports = TriangleElementBuffer;

function TriangleElementBuffer(buffer) {
    Buffer.call(this, buffer);
}

TriangleElementBuffer.prototype = util.inherit(Buffer, {
    itemSize: 6, // bytes per triangle (3 * unsigned short == 6 bytes)
    arrayType: 'ELEMENT_ARRAY_BUFFER',

    add: function(a, b, c) {
        var pos2 = this.pos / 2;

        this.resize();

        this.ushorts[pos2 + 0] = a;
        this.ushorts[pos2 + 1] = b;
        this.ushorts[pos2 + 2] = c;

        this.pos += this.itemSize;
    }
});

Post-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. Using Buffer2, We could write the same TriangleElementBuffer class as

module.exports = function() {
    Buffer2.apply(this, {
        type: Buffer2.BufferType.ELEMENT,
        attributes: {
            verticies: {
                components: 3,
                type: Buffer2.AttributeType.UNSIGNED_SHORT
            }
        }
    });
}

... 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 Buffer2 class will know how to construct, write to, and read from GL buffers but have no semantic understanding of their contents.

Instances of Buffer2 can be (destructively) serialized and transferred across threads.

cc @tmcw @mourner @kkaefer

Lucas Wojciechowski added 2 commits July 30, 2015 16:36
This is dead code right now but a step towards data driven styling
nirvana.
@lucaswoj
Copy link
Contributor Author

lucaswoj commented Aug 7, 2015

@mourner
Copy link
Member

mourner commented Aug 11, 2015

Reviewed the class, generally I think this is great. There's quite a bit of complexity introduced here but I guess we can't get away from it if we want generic data-driven styling with dynamically generated buffer layouts.

What I'd love to see is an example of a heavy buffer such as LineVertexBuffer and GlyphVertexBuffer rewritten using Buffer2, and some benchmarks/profiling. buffer.add(...) has a shitton of calls in the worker and takes a considerable amount of time, so the previous code was kept deliberately super-simple to make it really fast. Can we achieve comparable performance with a generic implementation?

I also think that to make this PR mergeable, we need to rewrite everything to use Buffer2 instead of Buffer and remove the old implementation, even if we know that this is eventually going away with the Bucket/Layer2 stuff. It seems relatively easy, and this way we won't have any code without use floating around, and will be able to catch any remaining issues.

@ansis
Copy link
Contributor

ansis commented Aug 11, 2015

This looks great!

I have the same question as @mourner about whether this is as fast as explicitly written versions.

Which methods are called in the worker? which are called in the main thread? If there is a clean split it might be good to separate Buffer2 into two parts.

@lucaswoj
Copy link
Contributor Author

What I'd love to see is an example of a heavy buffer such as LineVertexBuffer and GlyphVertexBuffer rewritten using Buffer2, and some benchmarks/profiling... Can we achieve comparable performance with a generic implementation?

I have done experiments with circle layers and found some performance degredation (ballpark ~15% slower). I would like to implement a more complex layer type, such as "line" or "glyph" and see how the performance scales but haven't gotten to that yet (more on this below)

Chances are we will have to choose between the increased flexibility of a more-generic-but-less-performant-infrastructure or a less-generic-but-more-performant-infrastructure.

My gut feeling is that there's nothing agregiously slow in the hot path (just a couple function calls and some loops) and we should be able to make this work.

Also, this refatoring DRYs up our buffer generation code. This will better position us to implement more complex performance optimzations in the future.

I also think that to make this PR mergeable, we need to rewrite everything to use Buffer2 instead of Buffer and remove the old implementation...

I started this project thinking I could create one PR that implemented Buffer2 and completely removed the old Buffer class. But it felt bad to sink so much time into refactoring the Bucket classes while planning to remove them in the very near future. So I started cramming the Layer2 class into this PR too and then things got way too complicated for a single PR.

My feeling is that allowing dead code in an isolated branch is a reasonable price to pay for a bunch of standalone ~500 line PRs instead of one XX000 line PR.

Which methods are called in the worker? which are called in the main thread? If there is a clean split it might be good to separate Buffer2 into two parts.

All methods are called in the worker (except get -- that's purely for debugging).

All methods except push, set, setAttribute, serialize are called in the main thread (that's most of them).

I do feel desire to split this class into two and have spent some time thinking about it. But I don't see any immediate practical benefit to doing so -- we'd end up with two small tightly-coupled classes instead of one nicely-sized class. Keeping the logic for understanding buffer layouts alongside the logic for writing to those buffers makes sense to me.

@mourner
Copy link
Member

mourner commented Aug 12, 2015

Chances are we will have to choose between the increased flexibility of a more-generic-but-less-performant-infrastructure or a less-generic-but-more-performant-infrastructure.

Sure. Something like a 15% slowdown sounds perfectly fine. But let's make sure on a heavy buffer.

But it felt bad to sink so much time into refactoring the Bucket classes while planning to remove them in the very near future.

Instead of refactoring buckets, could we just rewrite all *Buffer classes to extend Buffer2? That sounds like it could take way less time.

we'd end up with two small tightly-coupled classes instead of one nicely-sized class. Keeping the logic for understanding buffer layouts alongside the logic for writing to those buffers makes sense to me.

We could also have those two classes alongside each other in the same file (maybe one inheriting another). It could make the separation more clear — e.g. I'm not a fan of stuff like this (in the constructor here):

if (worker) {
  // huge chunk of code
} else {
  // something completely different
}

@lucaswoj
Copy link
Contributor Author

Instead of refactoring buckets, could we just rewrite all *Buffer classes to extend Buffer2? That sounds like it could take way less time.

That sounds like a good way to get this in the door. What do you think @jfirebaugh @ansis?

I'm not a fan of stuff like this...

To be fair, the fork in the constructor is splitting on whether we're unpacking a serialized instance or creating a new instance. I think that we should split this class into two if/when it gets unwieldy to share all code between the worker and main thread but it looks pretty clean right now imo. We might even want to mutate the buffer in the main thread in some cases.

@tmcw tmcw changed the title Added Buffer2 class [wip] Added Buffer2 class Sep 3, 2015
@lucaswoj lucaswoj force-pushed the v8-dds-buffer2 branch 2 times, most recently from e25a5e2 to 1fa273a Compare September 22, 2015 23:46
@lucaswoj
Copy link
Contributor Author

Going to recut this PR... Hold on to your 🎩 👒

@lucaswoj lucaswoj closed this Sep 22, 2015
@lucaswoj lucaswoj mentioned this pull request Sep 22, 2015
@jfirebaugh jfirebaugh deleted the v8-dds-buffer2 branch October 17, 2015 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants