Could mesh have update_{indices, uvs, colors, from_cpu_mesh}?
I made a line renderer that transforms a sequence of Vec3s to a long tubular CpuMesh. Right now, that means I have to do something like:
let mesh = Mesh::new(&ctx, &line.to_cpu_mesh());
every time something changes.
This feels bad: I couldn't judge the performance implications, but it means that if you have a struct that draws a bunch of lines, you also have to give it a copy of Context so it can create a new mesh every frame. It also feels inconsistent to have 'update_positions/normals' but not all the other attributes a mesh might have.
Secondly, would it make sense to have some kind of variable-length vertex buffer? That way, when the number of vertexes increases you can go on using the same buffer, keep track of the length, update it with buffer_sub_data_u8_slice.
Can make a PR if these things are things that you want.
https://github.com/user-attachments/assets/0036a3ea-7cd1-4362-8c3b-9b8170c9cee1
I'm not sure that I'm understanding you correctly, but as far as I understand, you're creating a new mesh containing a sequence of lines. If you're adding a new line to the sequence, you're constructing the entire mesh again. Is that correct?
If I understand you correctly, that's not super optimal no. However, adding the update functions that you request is not going to change much. Whether you create a whole new mesh or update all of the vertex buffers in an existing mesh is the same. You have to transfer the same amount of data to the graphics card.
I think you should take a look at InstancedMesh instead. Using that, you can send the vertex data for one line once and only update the instance buffers with new transformations every frame. Just to make it clear, one instance is then one line, not a sequence of lines. That means when you add a line to a sequence, you only have to send one transformation.
Secondly, would it make sense to have some kind of variable-length vertex buffer? That way, when the number of vertexes increases you can go on using the same buffer, keep track of the length, update it with buffer_sub_data_u8_slice.
You can already do that, just make too big a vertex buffer and fill the rest with 0's or something. I'm not sure it's better than just creating a new vertex buffer every time though, the graphics API implementation will most likely reuse the allocated memory.
Nice video btw 🙂
My first implementation used the instance based approach you described: the problem was mitering. I'm not good enough at maths to work out what kind of (I guess) projective matrix you need to transform a cylinder so it makes a nice mitered join, so I tried just using spheres. Then, the problem was I wanted a dotted line, or line animations, and I thought the way to do that would be to store the length of the line, and the spheres mess up the uvs.
In the end, I figured that passing line length + one vec3 for position is is 16 bytes, so if you have a triangular prism, you're sending 48 bytes per segment, whereas with an instance transformation, you're sending at least a Mat4, so 64 bytes. So it's roughly equivalent data but easier maths.
At this point there are two approaches: make a dedicated LineMesh class that holds vertex buffers, write vertex shaders, etc. The second is to use Mesh/InstancedMesh (say you want a thousand circles). I like the second one because Mesh is near-enough to what I want that the first approach seems like a lot of boilerplate for very limited gains in control.
PS: what I had in mind regarding Buffer:
pub fn fill_subset<T: BufferDataType>(&mut self, offset: usize, data: &[T]) {
assert!(T::size() == self.data_size, "extending a buffer with a different-sized type is not supported.");
self.attribute_count = (offset as u32 + data.len() as u32).max(self.attribute_count);
self.bind();
unsafe {
self.context.buffer_sub_data_u8_slice(
crate::context::ARRAY_BUFFER,
offset as i32,
to_byte_slice(data),
);
}
}
The fill_subset functionality makes sense, but requires quite a few changes. I did that in https://github.com/asny/three-d/pull/511.
I'm still not sure what exactly is the reason for your approach, it seems to me that it's a bit complex solution compared to just render each line with an overlap or make a sphere at each end point in addition to the cylinder. Anyway, I guess it makes sense to expose other attributes in addition to positions and normals, even though I don't think it will perform much better than just recreating the mesh. Fixed in https://github.com/asny/three-d/commit/0613bad63228885428ec16efb3922ad47623895f