Skip to content
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

Update InterleavedBufferAttribute API to allow shared WebGL buffer across different parameters #21656

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from

Conversation

takahirox
Copy link
Collaborator

@takahirox takahirox commented Apr 15, 2021

Related issue: #17089

Description

In #17089 a problem was raised that the current InterleavedBuffer and InterleavedBufferAttribute doesn't allow shared WebGL buffer across different parameters like type.

We discussed it in that thread and it would be worth to enable shared WebGL buffer. But we need to change interleaved buffer APIs. So I opened this draft PR to get feedbacks from other devs if it's ok to change the API for the improvement.

New API suggestion:

  • Shared InterleavedBuffer which is just a buffer containing the contents
  • Customizable gl.vertexAttribPointer() parameters in InterleavedBufferAttribute.
InterleavedBuffer(buffer: ArrayBuffer);
InterleavedBufferAttribute(
  buffer: InterleavedBuuffer,
  itemSize: number,
  type: TypedArray constructor,
  normalized: boolean,
  stride: number, // in bytes
  offset: number, // in bytes
  count: number
);
// Currently InstancedInterleavedBuffer but 
// InstancedInterleavedBufferAttribute may be more natural
// in the new API and it would be consistent with
// BufferAttribute and InterleavedBufferAttribute relationship
InstancedInterleavedBufferAttribute(
  buffer: InterleavedBuuffer,
  itemSize: number,
  type: TypedArray constructor,
  normalized: boolean,
  stride: number, // in bytes
  offset: number, // in bytes
  count: number,
  meshPerAttribute: number
);

Example:

const arrayBuffer = new ArrayBuffer(bufferSize);
const positionArray = new Float32Array(arrayBuffer);
const colorArray = new Uint8Array(arrayBuffer);
// Position (x, y, z) in float32, Color (r, g, b, a) in uint8
for (let i = 0; i < count; i++) {
    positionArray[i * 4] = Math.random();
    positionArray[i * 4 + 1] = Math.random();
    positionArray[i * 4 + 2] = Math.random();
    colorArray[i * 16 + 12] = Math.random() * 255;
    colorArray[i * 16 + 13] = Math.random() * 255;
    colorArray[i * 16 + 14] = Math.random() * 255;
    colorArray[i * 16 + 15] = Math.random() * 255;
}
const interleavedBuffer = new InterleavedBuffer(arrayBuffer);
const positioniAttribute = new InterleavedBufferAttribute(
  interleavedBuuffer,
  3, // itemSize
  Float32Array, // type
  false, // normalized
  16, // stride in bytes
  0, // offset in bytes
  count
);
const colorAttribute = new InterleavedBufferAttribute(
  interleavedBuuffer,
  4, // itemSize
  Uint8Array, // type
  false, // normalized
  16, // stride in bytes
  12, // offset in bytes
  count
);

Pros:

  • With the shared WebGL buffer, loaders or libs can optimize WebGL memory consumption. Especially the standard 3D model format glTF specification allows a single WebGL buffer (=glTF.buffer) shared by all entities. So if we optimize GLTFLoader users would get benefits without changing their code. (GLTFLoader optimization is not in this PR. I would like to suggest to do that in another PR not to make this PR bigger.)

Cons:

  • Interleaved buffer API change. But we may warn and automatically handle the legacy API in a few next cycles. And the change affects GLTFLoader, IFCLoader, and some other example modules but not so many (see the diff of this PR). Users can directly use interleaved buffer API like webgl_buffergeometry_points_interleaved example but perhaps it isn't main stream use case so I hope this change doesn't make a big bad impact.

Changes:

  • In addition to API change, we need to manage reference counter to shared InterleavedBuffer because the corresponding WebGL buffer needs to be released when all the attributes referring to it are disposed.
  • InterleavedBuffer will be just a buffer and won't have type. So we may need to drop some methods relying on the type.

Note:

  • Because of the API change the serialization/deserialization and documents needs to be updated but I don't change them yet. I want to update them after I get a consensus that it's ok to change the API.
  • In this PR, stride and offset in the new InterleavedBufferAttribute are in bytes for flexibility. And I assume stride bytes is multiple of multiple of type.BYTES_PER_ELEMENT for the efficient array accessor methods. If stride isn't type.BYTES_PER_ELEMENT aligned, the accessor methods may need to create data view for each call. Is this limitation good?

Advanced topics:

@@ -91,91 +82,109 @@ class InterleavedBufferAttribute {

setX( index, x ) {

this.data.array[ index * this.data.stride + this.offset ] = x;
// Note: Assuming stride is multiple of type.BYTES_PER_ELEMENT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's require (i.e. not attempt to support anything else) the requirements of glTF here, they are there for a reason:

https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#data-alignment

Copy link
Collaborator Author

@takahirox takahirox Apr 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good to know that. I have overlooked that part in the spec. Yeah let's require unless other specs need.


this.name = '';

this.data = interleavedBuffer;
this.data = buffer;
this.array = new type( buffer.array.buffer, offset );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in addition to the requirement that stride be a multiple of component size, this also requires that offset be a multiple of 4 I believe. May need to document these alignment requirements.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me unless other specs need.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requirement of Float32Array constructor itself as well: https://stackoverflow.com/a/8638875/1314762

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I didn't know that.

@@ -5,27 +5,18 @@ const _vector = new /*@__PURE__*/ Vector3();

class InterleavedBufferAttribute {

constructor( interleavedBuffer, itemSize, offset, normalized ) {
constructor( buffer, itemSize, type, normalized, stride, offset, count ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that I would swap the order of type and normalized, to match existing BufferAtribute constructors. In this case normalized would be a required parameter; seems fine to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of parameters will be many so I thought making the order be same as gl.vertexAttribPointer() parameters help users understand the order, but no strong opinion. Swapping them sounds ok to me.

@donmccurdy
Copy link
Collaborator

I'm still on the fence about the need for a InstancedInterleavedBufferAttribute, or InstancedInterleavedBuffer for that matter. It seems like a lot of API and conversion overhead just for a meshPerAttribute property. But also not sure how to avoid this...

@takahirox
Copy link
Collaborator Author

I'm still on the fence about the need for a InstancedInterleavedBufferAttribute, or InstancedInterleavedBuffer for that matter.

No strong opinion about it. InstancedInterleavedBufferAttribute is just for consistency with BufferAttribute - InstancedBufferAttribute relation. But adding optional meshPerAttribute parameter to InstancedBufferAttribute and judging whether attribute is "instanced" or not by checking meshPerAttribute is set is also ok to me.

@takahirox
Copy link
Collaborator Author

Any other comments or feedbacks to this change? If no objection, I will switch this PR to "ready for review" after I reflect the review comments.

@christjt
Copy link

@takahirox Are you still awaiting comments or feedback on this? Seems like a great change that I would really like to see it being merged.

@takahirox
Copy link
Collaborator Author

Yeah, I still want comments. If it looks good, I want to move it forward.

@christjt
Copy link

@Mugen87 Can you take a look? 🙏

@Mugen87
Copy link
Collaborator

Mugen87 commented Nov 29, 2021

Sorry, but other issues and changes have a higher priority right now to me.

In context of buffer attributes and buffer geometry I also vote to focus on #22874 first.

@frericp
Copy link

frericp commented Apr 14, 2022

We recently found that using interleaved buffers with attributes of different types leads to the creation of multiple buffers in WebGL, which duplicates data on the GPU and defeats the purpose of interleaving data in the first place. @donmccurdy was so kind to point me to this proposal, and I really like it. It would allow us to continue using interleaved attributes instead of migrating to non-interleaved attributes.

@takahirox Is there any chance to pick this topic again anytime soon?

@takahirox
Copy link
Collaborator Author

@frericp I'm still waiting for comments. Please push @mrdoob or @Mugen87 if you want to raise the priority of this change.

@frericp
Copy link

frericp commented Apr 27, 2022

Sorry for the delay, I just came back from vacation.

@takahirox If I got it right, you're mostly waiting for feedback on the API changes, correct?

@Mugen87 Any idea about when this proposal could make it onto the roadmap? I checked the other issue that you referred to above (#22874) and it seems to be mostly figured out by now. I'm happy to assist with code reviews if that helps.

@jhurliman
Copy link
Contributor

I’m looking forward to this PR merging. We have a use case where point cloud data is received in an interleaved format with mixed types. Currently, we iterate over the structure and create separate position and color attributes on the CPU before uploading to the GPU. Ideally we could upload the data as-is to the GPU and access it using interleaved attributes.

@donmccurdy
Copy link
Collaborator

@snagy — given your comments in #24492, do you have thoughts on the practical impact of this PR (and #17089) for avoiding additional GL calls?

@snagy
Copy link
Contributor

snagy commented Aug 16, 2022

@donmccurdy I'm a fan of this change and think it will get some perf wins, but I'm not sure the scale of those wins. In the app I've been profiling lately, the per-frame cost of this on the JS side is entirely hidden by VAOs - it just remains a single call to set the VAO after it's been setup. The additional complexity of what's actually referenced in those VAOs certainly has some overhead, but I'd have to grab this change (and update the GLTFLoader) to be able to quantify that.

@xpl
Copy link

xpl commented Apr 10, 2024

I would love to have this PR merged, can it be made possible anytime soon?

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.

8 participants