So I've got a rendering engine where in certain applications, it makes sense to use a vertex buffer, while in other applications it makes sense to use a triangle buffer. I'll briefly explain the difference so it is clear:
Vertex Buffer: This approach means that triangles are stored using 2 arrays. A vertex buffer and an index buffer. The vertex buffer contains all of the unique vertex information, while the index buffer contains all of the face definitions as indices of the vertex buffer. So for example, to obtain the 2nd vertex in a triangle with index
idx
you would call:vertex_buffer[index_buffer[3*idx+1]]
(assuming both vertex_buffer and index_buffer are just 1d arrays).Triangle Buffer: This approach means that each triangle is represented by a struct containing all of its necessary data. So to access the first element of the 2nd vertex in a triangle with index
idx
you would call:triangle_buffer[idx].vertex1[0]
.
The benefit of the triangle buffer approach is that it requires fewer reads from memory for each individual triangle at the expense of potentially duplicating data (as is the case if you have a continuous geometry and so triangles share vertices). The benefit of the vertex buffer approach is that it does not duplicate any memory at the expense of needing more reads from memory to get both the index and subsequent vertex. For a continuous geometry processed coherently though, this is less of an issue as previously loaded triangle vertices will still be in cache.
There are times when it makes more sense to use one of these approaches over the other, and I'd like that to be configurable at compile time. My first gut thought on how to do that would be to create a wrapper function for the this process of fetching triangle data, and inlining that where needed. So the function(s) might look like this:
template <typename Scalar>
inline Vertex<Scalar> get_triangle_vertex0(size_t triangle_idx) {
#if defined(TRIANGLE_BUFFER)
return triangle_buffer[idx].vertex0
#else
return vertex_buffer[index_buffer[3*idx+1]]
#endif
}
template <typename Scalar>
inline Vertex<Scalar> get_triangle_vertex1(size_t triangle_idx) {
#if defined(TRIANGLE_BUFFER)
return triangle_buffer[idx].vertex1
#else
return vertex_buffer[index_buffer[3*idx+1] + 1]
#endif
}
NOTE: For this is just rough pseudo code just meant to outline the rough concept of my initial plan, which is to wrap each of the indexing methods in an inlined function so that at compile time you can decide between each method.
Is this the correct way of handling this sort of thing? Is there a performance penalty for this? Or is there a more sensible way to achieve the same result?
The only alternative I can think of is to go through and manually put the preprocessor directives everywhere I need to access triangle data. Which is why I thought an inline function like this might help consolidate that and keep things clean.