-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
_bindBuffer unnecessarily copies typed arrays #5739
Comments
Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. Thank you! |
Thanks for opening this! I can look through the code at some point in the near future and see if there are cases where we could be storing values in a typed array to improve performance (possibly in p5.Geometry?) In the mean time, it looks like your change will be necessary to accommodate this. I'll give it a review in a little bit after having taken a closer look at what our current buffers look like to get a fuller picture. |
Yeah, so a bit more background and context. Firstly the patch probably wants to be tested with more than just one type of typed array, but I wasn't sure what ones are relevant to p5, and the helper that exists in the file is quite broken and slow (5 or so checks, and doesn't | them together. I think what I wrote should work for all typed arrays. Secondly, what I was doing was animating medium numbers of identical in orientation and texture boxes - about 20K of them - which p5 really didn't like. Doing that with instancing would be great, but as p5 doesn't do that implicitly or explicitly I needed to poke around a bit under the hood. So what I did to make it all hang together is create a geometry object, populate it with all the vertices, faces, lines for stokes, and then draw that once to populate internal p5 caches, then for subsequent frames save and restore the lineVertices and vertices buffer So I would then iterate over the high level objects I'm working on, which were backed by this geometry, and mutate the coordinates, mark the two buffers involved dirty, and voila, done. At least until I needed a different number of boxes. Heh. Along the way I noticed a few things. The 'slow path' in flatten is very slow and not needed - the reason its needed is claimed to be stack exhaustion, but I think in actual use the nesting depth is only and always 1, and the children are homogeneous, so going directly to a fixed size typed array will be computationally cheaper, and combined with the _flatten -> _bindBuffer, both called by _prepareBuffer, double handle the same data: if conversion is required, better to do it just once. as mentioned above, the internal representation for geometries is not GPU friendly, which means that p5 pays a performance tax on every frame. Even without instanced rendering, this can be avoided if the underlying representation is tuned. e.g. the Vector in a geometry vertices list should be 3 floats in a Float32Array, and the Vector object store the base index of the first one and a reference to the array, permitting getters and setters to work. Or something - I don't plan to design or implement, but it seems very possible to keep the friendly education API but still perform well. |
Sorry it took so long to get back to this! Right now, if you make a That said, it looks like we'd be able to allow it and make it more efficient with minimal changes and without breaking sketches, especially because updating geometry isn't documented. If we restrict updates to geometry data so that they must keep the same topology as before (same number of vertices, same faces), we could convert the buffer data to a typed array after the geometry is initially created and allow people to update those efficiently. As a follow-up, we could optionally provide a p5.Vector-like API as @rbtcollins mentioned where each vector is backed by data in the typed array, but even without that, we'd be unblocking people from e.g. writing a gltf animated mesh importer library, as that would need to update geometry each frame. @stalgiag what do you think? This is probably also related to #5393 and figuring out level of complexity p5 wants to support, since this is definitely more of an advanced feature. |
When thinking about accessibility with the GL Renderer, I think about people who are coding 3D graphics for the first time and I also think of people who are contributing to a 3D graphics library for the first time. As you said @davepagurek, this PR doesn't break anything but it does add code that wouldn't make sense to a contributor unless it was understood that we support modification of p5.Geometry objects. If we want to do that, then I think we should commit fully and consider the design of the interface and document it well. It appears to me that contributors are interested in this functionality and there is some organic momentum to support it. This pathway offers the potential for the WEBGL renderer to become a more uniquely situated tool with a low friction approach for those wanting to work with custom geometry + shaders. The benefit in accessibility with this lies more with inspiring examples that may come as output. As long as those first two groups (new 3D coders and new 3D contributors) are considered, I will back any approach. I do think that it is better to support something with transparency and clarity than to leave it as a common workaround that the source code doesn't expect. But most importantly, @davepagurek I think you are the best situated to determine the aims for this phase of the WEBGL renderer so I'll trust your call. |
So the goal for this would be to:
A little while ago, @xrcyz helped collect a number of examples in the wild of p5.Geometry, which I've been looking at to research existing usage patterns:
Here are the patterns I see for different stages of the p5.Geometry lifecycle:
Initially I only had the first form of creating class p5.Geometry {
constructor(w, h, cb) {
// constructs vertices, edges, faces, etc
cb();
// store data in a more efficient format
this.vertexBuffer = Float32Array.from(p5.RendererGL.prototype._flatten(this.vertexBuffer));
// repeat for other buffers
}
// You can then update the values in `this.vertexBuffer` and call this method to update the geoemtry in a reasonably
// efficient manner. This would be how e.g. a library adding support for gltf animation imports would update each frame
updateVertices() {
this.dirtyFlags.vertexBuffer = true;
}
}
If we don't want to deprecate users' code that omits passing a callback and modifies data directly, we could opt instead for a lazy approach to making the typed arrays only on render. It likely would mean that in addition to the dirty flag from before (used by libraries and advanced users who edit the more efficient data structure), we would need load/update methods for the user-friendly array of class p5.Geometry {
get vertexBufferData() {
if (!this._vertexBufferData) {
this._vertexBufferData = Float32Array.from(p5.RendererGL.prototype._flatten(this.vertexBuffer);
}
return this._vertexBufferData;
}
// Libraries and advanced users can call this after updating `this.vertexBufferData` directly
updateVertexBufferData() {
this.dirtyFlags.vertexBuffer = true;
}
// Users call this before editing `this.vertexBuffer`
loadVertices() {
if (this._vertexBufferData) {
this.vertexBuffer = [];
for (let i = 0; i < this._vertexBufferData.length; i += 3) {
this.vertexBuffer.push(
createVector(
this._vertexBufferData[i],
this._vertexBufferData[i + 1],
this._vertexBufferData[i + 2]
)
);
}
}
}
// Users call this after editing `this.vertexBuffer`
updateVertices() {
// Delete the typed array; it will get lazily re-created when asked for
delete this._vertexBufferData;
// Mark the buffer as needing to be updated on the GPU
this.updateVertexBufferData();
}
}
I think if we're OK with the tradeoffs, my preference is to go with the former approach, as it leaves room for us to make friendly methods that are also efficient (e.g. we could support a syntax like Also, both methods allows us, in the future, to make helper functions that make the |
Some updates on this:
However, there are a few more cases where we currently still have programmers accessing these arrays directly, which we would need to replace if we want full freedom of a more optimized under-the-hood representation:
|
Most appropriate sub-area of p5.js?
p5.js version
main
Web browser and version
No response
Operating System
No response
Steps to reproduce this
I documented the behaviour in this PR that got closed because of some process you prefer. #5737
I don't have a lot of spare time, and while I'll happily do value-adding things to allow this to be merged, I have no plans to become a long term contributor, so someone with full awareness of the hoops you've got present might want to take this over.
The text was updated successfully, but these errors were encountered: