-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
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
Improving BufferAttribute (maybe) #17089
Comments
Of the various threejs loaders, GLTFLoader is currently the only one creating InterleavedBuffers and InterleavedBufferAttributes. I think this makes a strong case that (1) the current interleaving API may not yet offer enough benefits, and (2) we can afford to make changes that are not backward-compatible there, or to remove IBA in favor of a more flexible BufferAttribute implementation. Ideally, I'd suggest that we evolve the IBA class in the direction you're suggesting: so that IBA can be used as a drop-in replacement for BufferAttribute initialized with a typed array, or can be initialized with an improved Buffer object. This would be backward-compatible with BufferAttribute, but not necessarily backward-compatible with today's IBA. I say that mainly because it sounds like a safer and more incremental upgrade path, even if the eventual goal is to have only one implementation. If the largest changes are all within the renderer (which is very possible; I'm not as familiar with that code) it may not really be as "incremental" as I'm hoping. I would defer to others on how manageable this sounds. Aside: There is some complexity here for the |
^I'd be interested in maintainers' preferences on this. Above, I'd suggested evolving InterleavedBufferAttribute rather than BufferAttribute, mostly because it is easier, but extending the BufferAttribute constructor...
... does not need to be a breaking change either. We have a lot of BufferAttribute types today ...
... and so the option of merging InterleavedBufferAttribute into BufferAttribute does sound kind of nice. I'm running into a problem that is described generally in @zeux's comments above, but I'll call it out here more specifically. Given a mesh with multiple vertex attributes, we might want to both (1) use lighter component types than |
I think WebGL buffer which can be shared across different types, counts, or other properties attributes is worth for Three.js. Especially it's nice if users get benefit without changing their code by letting glTF loader support it. (Of course, glTF assets needs to be optimized, too.) I made a prototype to figure out the change and to determine how we move it forward, like replacing the IBA, extending BA, or so on. dev...takahirox:InterleavedBufferAttribute2 In the prototype, I added two new classes so far,
The changes needed in the renderer are
And one concern about Sorry for the long comment and I still need to brush it up, but I would say the shared WebGL buffer feature can likely be introduced without so big change. |
I'm thinking of the upgrade path. And first I would like to know the reason why the current |
GLTFLoader is the only loader using |
At the time these were written, there probably weren't any loaders parsing in already-interleaved vertex data, so the interleaving was presumably happening on the fly in the browser, and writing into float32 was not a big deal. But in 2020 it is very easy to make a glTF file with interleaved vertex data using smaller (int8, int16, ...) types, and I think it does not make sense for |
I made a prototype for changing the existing interleaved buffers API. dev...takahirox:NewInterleavedBufferAttribute New API 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
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();
colorArray[i * 16 + 13] + Math.random();
colorArray[i * 16 + 14] + Math.random();
colorArray[i * 16 + 15] + Math.random();
}
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
true, // normalized
16, // stride in bytes
12, // offset in bytes
count
); Any thoughts?
Nit:
Users can directly use |
Thanks @takahirox, I agree with the direction of this. I wonder if we could go even further:
These seem like cases we could catch and warn about, or even handle automatically for some period of time, perhaps? I do agree that changing the existing interleaved attribute API is justified. |
I think it just works. No strong opinion about adding the final constructor parameter to
Personally combining
Yes, probably. |
I'm thinking of making a draft PR to get feedbacks from more many devs. |
Yes, if (1) we can maintain backwards-compatibility with the current BufferAttribute API, and (2) the code is not much more complex as a result, then I think this is worth considering. Otherwise I'm completely fine with just modifying the The current BufferAttribute constructor is: BufferAttribute( array : TypedArray, itemSize : Integer, normalized : Boolean ) Arguably this could be a backwards-compatible API for a BufferAttribute class supporting interleaved and/or instanced use cases: BufferAttribute(
array: TypedArray | InterleavedBuffer,
itemSize: number,
normalized: boolean = false,
type: ? = undefined,
stride: number = undefined,
offset: number = undefined,
count: number = undefined,
meshPerAttribute: number = undefined
) EDIT: Could also group things into options, depending on preferences... BufferAttribute(
array: TypedArray | InterleavedBuffer,
itemSize: number,
normalized: boolean = false,
options?: {
interleaved?: { stride: number, offset: number, count: number, type: ? },
instanced?: { meshPerAttribute: number }
}
) |
Thanks for the investigation! Yeah, it looks we can maintain backward compatibility. I haven't looked into the core code for extending |
Sounds good to me! We can wait to decide on whether to combine BufferAttribute and InterleavedBufferAttribute at the end. |
Opened the draft PR #21656 /cc @zeux @donmccurdy |
While working with GLTF loader a fair bit in the past few months I've hit a few issues that seem to be impossible to fix perfectly short of changing parts of the core library. This issue presents the issues and solicits feedback.
Ideally, GLTF loader should be able to create a WebGL buffer for each bufferView that is present in the file. The glTF spec is structured to make this possible through various limitations on alignment and colocated data in a single view. Given a good glTF processor like
gltfpack
you can then achieve a minimum number of WebGL buffers used for the entire scene - for example, one buffer for everything (this requires interleaving vertex data), or maybe one buffer per each unique stride (this is commonly how glTF files exported from Sketchfab seem to look). This is independent of the complexity of the scene.Doing so carries a few benefits - it minimizes the number of buffer objects used to represent the scene, which improves memory consumption, minimizes the cost of switching between geometry buffers during rendering, and in theory unlocks some future accelerated rendering scenarios using multi draw (unfortunately, WEBGL_multi_draw specifically isn't optimal for this, but maybe this can be improved in the future). Additionally, the loading logic can be more straightforward and can become faster for files with lots of meshes.
There are two important features that are required to make this work (see #16802 for what triggered all of this, and GLTFLoader.js for the current somewhat sad workarounds):
We need to be able to reference the same WebGL buffer from multiple buffer attribute objects, at different byte offsets. This is currently impossible with BufferAttribute. This seems possible with InterleavedBufferAttribute but it doesn't truly work because all IBAs that refer to the same IB share the same .count property which breaks various assumptions in various parts of three.js.
We need to be able to reference the same WebGL buffer from multiple buffer attribute objects with different component types. This is current impossible with BA or IBA - in both cases the backing array on the JS side is a typed array, not an array buffer.
It seems to me that InterleavedBuffer is not pulling its weight. It doesn't solve either of the two problems above - effectively it only works if you want to interleave data of the same component type within a buffer for a single mesh, but doesn't allow mixing component types or packing multiple meshes into a single buffer. WebGL is perfectly capable of all of these, but Three.JS lacks a good interface to this.
Additionally, the fact that there are two different constructs with two different interfaces to represent, conceptually, the exact same thing - an array of typed components used for vertex processing - seems problematic. This results in some number of type checks for which BufferAttribute implementation it is, and not all code can handle both.
Now, if we agree that these problems should be solved, there's still an option of continuing to evolve InterleavedBufferAttribute to support these use cases - it probably involves extending InterleavedBuffer to optionally contain an ArrayBuffer instead of a TypedArray, adding a .count property to InterleavedBufferAttribute that can be set independently, and fixing code that directly works with InterleavedBuffer. If this was done, you'd be able to create InterleavedBuffer objects exclusively in glTF loader.
However, it seems like there's another way to fix this problem - instead of fixing InterleavedBuffer, we can improve BufferAttribute and deprecate InterleavedBuffer instead (it could still potentially exist as a thin wrapper over BufferAttribute to maintain backwards compatibility).
Here's how this could work.
Right now BufferAttribute is created from a typed array. We would add a new Buffer object that can be created from an ArrayBuffer, and a way to create BufferAttribute from a Buffer object with a byte offset and a component type (which would create a typed array for a slice of the buffer object). Existing constructor from a typed array would create a private Buffer object, so existing code would work as is.
BufferAttribute would be extended with a stride; to keep everything working nicely we will require that the stride is divisible by the component size so that it can be expressed in terms of the type of the typed array - this isn't a significant limitation in practice because the largest usable type is 32-bit float, and for various legacy reasons offsets need to be aligned by 4 bytes both in glTF spec and in general use.
Code that directly accesses BufferAttribute.array and isn't aware of the stride will need to be taught about the stride; code that works with BufferAttribute through getX/etc. accessors will continue to work.
After this is done, glTF loader can forget that interleaved buffers exist and create Buffer from each bufferView and BufferAttribute from each geometry accessor. (in glTF specifically there's one more source of inefficiency atm which is the morph target buffer duplication, which I think should be solved at the core level as well but that's a separate discussion) We can then separately deprecate InterleavedBuffer and either just remove it, or reroute IBA & IB methods to work through BA & B - they might just extend these classes and add the relevant functions if necessary.
As a result, three.js can gain a Buffer object that is roughly equal in power to WebGL buffers, glTF loader can stop creating thousands of tiny buffers for scenes with a large number of meshes, and (eventually) the whole "there are two different types of buffer attributes" issue can disappear.
Thoughts?
The text was updated successfully, but these errors were encountered: