Closed freesig closed 6 years ago
So I'm trying to figure out the most efficient way to implement the mesh. There's two types of mesh. Static For example a unit square mesh. This will never change if the tessellation (resolution) changes. Dynamic For example a sphere. This will change as tessellation changes.
For the static mesh its faster to put the data on the stack as a const array.
Like this:
const UnitSquare: [f32; 12] = [-0.5, 0.5, 0.0, 0.5, 0.5, 0.0, 0.5, -0.5, 0.0, -0.5, -0.5, 0.0];
But this can't be done with the dynamic mesh that will be different if the tessellation changes. So that needs to be created on the heap using some thing like:
struct Vertex{
position: f32,
}
struct Mesh{
verticies: Vec<Vertex>,
}
How can I do this so for the person using the Mesh struct they have the same interface. Basically I want to be able to use a mesh struct and be unaware how its stored so that we can put static shapes on the stack where possible.
One idea I was thinking is to have a Vec<&Vertex> and then the Vertex can be either stored in a const array or in a dynamic Vec. But I think this leads to some complicated lifetimes and maybe we loose the advantage of having the it on the stack
Maybe something along these lines
use cgmath::*;
const UnitSquare: [Vertex; 12] = [Vertex::new(-0.5, 0.5, 0.0),
Vertex::new(0.5, 0.5, 0.0),
Vertex::new(0.5, -0.5, 0.0),
Vertex::new(-0.5, -0.5, 0.0)];
struct Vertex{
position: Vector3<f32>,
}
impl Vertex{
fn new(x: f32, y: f32, z:f32) -> Self {
Vertex{ position: Vector3(x, y, z) }
}
}
struct Mesh{
verticies: Vec<Vertex>,
}
Or Mesh could hold a slice. like &[Vertex] then we could pull that from either a Vec or a const array?
Hmmm maybe we could start at an even more basic level than the Mesh
type with this geom
module? I think the way you point out some shapes are inherently dynamic depending on the level of tesselation while others can be easily represented in a const array is maybe a good indication for this.
Maybe a nicer way to go about this is to write "the most efficient representation" for each individual kind of geometry (e.g. a const arrays for squares and triangles, Iterators for spheres, ovals, etc) and then once we have these to work with we can think about the simplest way to unify these representations with a Mesh
type?
I'm pretty sure that theoretically we should be able to have const arrays for spheres/circles if the given tesselation level is const (and calculated the number of verts needed at the const-level), but rust's support for const generic types and associated consts in fixed-size-arrays is super limited atm.
Or Mesh could hold a slice. like &[Vertex] then we could pull that from either a Vec or a const array?
Yeah I like the idea of Mesh
being generic in the sense that it can either borrow existing buffers or own its buffers :+1:
Yeh I agree, if I just make all the shapes in their most efficient way, then maybe it will become obvious how to package them in a generic Mesh type
Here's a mega rough sketch of where I was thinking of going with the Mesh
type (could be way off as I've barely used meshes much!):
struct Mesh<V, I, N=(), T=()>
where
V: Vertices,
I: Indices,
N: Normals,
T: TexCoords,
{
vertices: V,
indices: I,
normals: Option<N>,
tex_coords: Option<T>,
}
trait Vertices: std::ops::Index<usize, Output=Vertex> {}
trait Indices {
type Iter: Iterator<Item=usize>;
fn indices(self) -> Self::Iter;
}
// Not sure how normals/texcoords are normally referenced but maybe that could have similar traits?
// Auto implement the Vertices trait for Vec, slices and other types indexable by `usize`.
impl<T> Vertices for T where T: std::ops::Index<usize, Output=Vertex> {}
// Auto implement the `Indices` trait for `&Vec<usize>`, `&[usize]` and other types that may be
// borrowed to produce an iterator yielding `usize`s.
impl<'a, T> Indices for &'a T
where
&'a T: IntoIterator<Item=&'a usize>,
{
type Iter = std::iter::Cloned<Self::IntoIter>;
fn indices(self) -> Self::Iter {
self.into_iter().cloned()
}
}
Maybe we could use default type params and type aliases for Mesh
to simplify its in-practise use and remove some of the generic type noise?
Also I'm defs not sure if this is the best approach for handling normals and tex coords. Perhaps a better approach might be something like this?
trait MeshType {
type Vertices: Vertices;
type Indices: Indices;
fn vertices(&self) -> Self::Vertices;
fn indices(&self) -> Self::Indices;
}
struct Mesh<V, I>
where
V: Vertices,
I: Indices,
{
vertices: V,
indices: I,
}
struct Textured<M, T=Vec<TexCoord>>
where
M: MeshType,
T: TexCoords,
{
mesh: M
tex_coords: T,
}
struct WithNormals<M, N=Vec<Normal>>
where
M: MeshType,
N: Normals,
{
mesh: M,
normals: N,
}
impl MeshType for Mesh ...
impl MeshType for Textured ...
impl MeshType for WithNormals ...
This way you could maybe have stuff like Textured<Mesh<V, I>>
, WithNormals<Mesh<V, I>>
, or Textured<WithNormals<Mesh<V, I>>>
? You could maybe build these like:
Mesh::new(vertices, indices)
.with_normals(normals)
.textured(tex_coords)
I don't really know how practical this approach would actually be though in practise - at this point I'm just drafting a mess, feel free to disregard it! Just thought I'd get down some thoughts.
I think having Option
Usually a normal is just a vec3
I'm a little confused how the indicies trait would work. It's basically just an array of index positions that map across the verticies and tell opengl where to draw the each triangle. You usually pass this array to opengl along with the actual mesh data. I think it's better to store this data in memory over generating it on the fly because memory is less of a concern then add cpu cycles.
Kinda depends how you actually send the VBO to the gpu.
I think the fastest way is to send static mesh to the gpu. For animated you mesh, you can either modify it using transforms in the shader or update the gpu memory each frame. Either way you probably want the indicies with the mesh data so you can pass it together.
I think having Option doesn't really solve the problem because if Option is None it's still the size of a normal. Which means you don't save the memory. And if the mesh is large that's a lot of normals.
Remember this would only be the case for Option<[Normal; N]>
, but for Option<Vec<Normal>>
a None
would be very small (as a Vec
's size on the stack is very small - just a pointer, a length and a capacity).
Usually a normal is just a vec3 and a texture_coordinate would be a vec2 or something.
Aye true, just thought I'd juse Normal
and TexCoord
as aliases to make it a bit more obvious what I was talking about.
I'm a little confused how the indicies trait would work. It's basically just an array of index positions that map across the verticies and tell opengl where to draw the each triangle. You usually pass this array to opengl along with the actual mesh data. I think it's better to store this data in memory over generating it on the fly because memory is less of a concern then add cpu cycles.
True true, we could make the Indices
trait something like this instead:
trait Indices: std::ops::Deref<Target=[usize]> {}
// Auto-implement for arrays, Vec, slices or any type that deref's to a slice of indices.
impl<T> Indices for T where T: std::ops::Deref<Target=[usize]> {}
Kinda depends how you actually send the VBO to the gpu.
Yeah totally, we'll probably continue to guage what works better performance-wise as we go on - I imagine that in some cases it will be more efficient to collect loads of tiny primitives into a single re-usable mesh so that there's only one draw
call where the cpu has to sync with the gpu, whereas in other cases where we have a lot of really big meshes where some are textured and some aren't it might make sense to submit them individually rather than spend the cycles on moving them all into one giant mesh.
This makes me wonder if there's a reliable way we can setup a bunch of benchmarks for this... would be sweet if we could generate a big table which could demonstrate which methods are most efficient in which cases!
Closed via #67 - Supports creating meshes from both borrowed or owned memory, or a mixture. Channels can be added and removed in a type-safe manner and the API guarantes that the length of each channel is always the same (besides vertex index channels which may vary). A higher-level nannou::draw::Mesh
type has been added which is composed of lower-level components from the nannou::mesh
module. It would be nice to add an example of creating a custom mesh using the mesh
module someday!
Unit sized static mesh
const fixed size arrays Indicies array Variants