Graphics engines: what's more common; standard vertex structure or dynamic based on context?

Started by
14 comments, last by Hodgman 8 years, 10 months ago
Thanks everyone, some really good responses! I've actually designed my engine under the hood to allow for any type of vertex format (with the view that tooling could take care of everything, or you could just manually code your declarations if desired), but favouring a "convention-over-configuration" approach, I'd hidden the capability from the developer on the basis that I could expose it later on it if needed, and for now just exposed a standardized vertex layout. Based on all of this information, I'm going to expose it after all. I want to do it right and I'm new to game engine design, so some of the architectural techniques I'm used to applying in other fields don't always necessarily apply here.
Advertisement

With the interleaved layout (as kaiserJohn is suggesting)

He is suggesting the alternative to interleaved data: Multiple streams.
To answer his question regarding downsides: You should always use interleaved formats when possible; they perform better than using multiple streams primarily due to cache.

As Promit mentions, binding unused vertex data is not future-compatible. Metal already disallows it and I am expecting the same from Vulkan, Mantle, and Direct3D 12. In Metal, as he says, your vertex buffer must match byte-for-byte—literally inside a Metal shader you declare the input as an array of structures which must match your actual input, including padding.

How about the shadow pass? You would not need to send all vertex attributes, right? With a fat vertex structure I would assume you need to send all attributes no matter what.

No, you can declare a 2nd vertex layout that only has the position and a wide stride, so only the position data gets uploaded even though it is the same vertex buffer. This improves upload speed but is still not the best for cache (which would need to have the position data packed together in its own stream for maximization).

This is a plausible case for streams, although in all of the companies where I have worked they just suffer the cost of uploading the whole vertex buffer.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

How about the shadow pass? You would not need to send all vertex attributes, right? With a fat vertex structure I would assume you need to send all attributes no matter what. With the interleaved layout (as kaiserJohn is suggesting) you could just bind the needed data. Wouldn't that help?

It doesn't need to be one or the other.
You can have stream with position+texcoord nicely packed into a 16byte stride, and then have the rest (normal/tangent/binormal-sign/lightmap-coords/etc) in a second stream.

These don't have to all be individual buffers either!
You can bind a buffer with an offset, so when loading a model like this from disc, you could read stream0 and stream1 into different parts of a single buffer object.

For shadow passed I know a few games which used a stream specifically for that. (As Hodgman said, first stream is position + texcoord[if you have alpha testing/punchthrough], second is remaining attributes, both are interleaved inside.)

When it comes to high performance you don't really have a choice, any waste is bad, so tailor your vertex format to what your shader needs.

Often you'll end up with only a few different vertex format, but it doesn't really matter, it's easy enough to support any format anyway...

I can tell you for having tested it back then that having a stream for shadow buffers helped performance a lot (~30%).

You should support both and test on your targets what's giving you the best performance.

There's no magic in programming, it's also science, you suppose, then you test, they you draw conclusions and use whatever is the best trade-off in your case.

(Which might not be performance if it varies only slightly, but ease of use... everyone has different goals.)

-* So many things to do, so little time to spend. *-

My engine is data driven, and I'd expect the same from other big engines. In the game's data files, I declare "stream formats" (how vertex data is laid out on disk / in memory) and "vertex formats" (the input to vertex shaders) and then declare which ones are compatible with each other. Those declared compatible pairs describe the fixed-function config of the input-assembler stage -- a.k.a. vertex declaration / input layout / vertex attribute formats.

These input assembler layouts, and reflection data on the other structures, are compiled from human-readable text into an efficient binary format for the engine runtime.

How do you handle dynamic material assignment at run-time if the loaded stream format doesn't support the required vertex format?

My engine is data driven, and I'd expect the same from other big engines. In the game's data files, I declare "stream formats" (how vertex data is laid out on disk / in memory) and "vertex formats" (the input to vertex shaders) and then declare which ones are compatible with each other. Those declared compatible pairs describe the fixed-function config of the input-assembler stage -- a.k.a. vertex declaration / input layout / vertex attribute formats.
These input assembler layouts, and reflection data on the other structures, are compiled from human-readable text into an efficient binary format for the engine runtime.


How do you handle dynamic material assignment at run-time if the loaded stream format doesn't support the required vertex format?

You make sure that the loaded stream format does support the required vertex format (by making your requirements known at data compile time).

This topic is closed to new replies.

Advertisement