Material-Pipeline-Mesh Architecture

Started by
2 comments, last by KarimIO 6 years, 8 months ago

Hey guys, so as I'm revamping my entire rendering system, I decided to go by an object-centric draw cycle to a pipeline-centric one. So basically I bind pipelines, then materials, then the mesh, rather than the buffers for the entire model and then go looking for the pipeline. I'm having issues designing the architecture for this. I figured I'd have:


MaterialSystem:
	vector<RenderPassContainer>
	map<string, Pipeline *>
	map<string, Material *>

RenderPassContainer:
	RenderPassObject
	vector<PipelineContainer>

PipelineContainer:
	PipelineObject
	vector<Material>
     
Material:
	TextureBinding (group of textures)
	vector<Mesh *>
    
Mesh:
	BaseVertix
	BaseIndex
	NumIndices
	Material *

So the issue here is that when I create new materials, the map and all pointers are pointing to the wrong area in memory. Any ideas for a better architecture? Keep in mind I need to: Add Materials, Add Pipelines, Add meshes Remove Meshes (and materials and pipelines if there are no instances left), and Update materials and pipelines at runtime. Thanks!

Advertisement

If I'm understanding the problem, anytime you mess with the vectors of RenderPassContainer, PipelineContainer or Material, you are going to have a bunch of invalid pointers correct?  I.e. this is the standard problem of iterator invalidation after modification in most stl containers.

There are a number of ways around the issue but you need to clarify your intentions.  The first and easiest, if you don't need the content to be contiguous in memory, is to remove ownership from the containers and use pointers instead.  The extra indirection solves the problem for the most part at a minimal cost, given that this sort of thing is a couple hundred times a frame, it should have limited to no noticeable overhead.

A second solution which is a bit more complicated but maintains the linear memory layout would be not using pointers but instead use indexes into the arrays.  Adding new items will work without problems, removing items just means walking through all the vectors looking for indices >= the removed item and erasing it or subtracting one.  This means that you need to move the add/remove interface to a top level owner of all the vectors so it can iterate them, but that's generally a good idea to centralize the API anyway.

The third solution is even a little more complicated but has properties which I needed in my system.  I extend the index idea by adding a version tag to each index.  Since I'm promising myself I will never have more than 65k materials in the system at any given time, this handle is a simple 32 bit value, 16 bits of index and 16 bits of version.  Now, when I go to get the material via the handle, I first check that the version stored in the handle and the version in the slot match, if not I return nullptr.  If the callers gets a nullptr they look up the material by hash and assuming is still exists they fix their internal handle.  The reason for this solution is that I only ever add/remove things dynamically in tools or debug builds and the whole check and re-fetch thing compiles out to nothing in release, but it is still fast enough that debug builds are not hobbled by a bunch of overhead.

Again though, this all circles back to what are the requirements for you.  I'd personally start with the second solution as it is easy, fast and leaves the important properties of your layout in place.  The third solution is not suggested unless you start doing a lot of hot reloading, which is why I wanted it.

2 hours ago, Hiwas said:

If I'm understanding the problem, anytime you mess with the vectors of RenderPassContainer, PipelineContainer or Material, you are going to have a bunch of invalid pointers correct?  I.e. this is the standard problem of iterator invalidation after modification in most stl containers.

There are a number of ways around the issue but you need to clarify your intentions.  The first and easiest, if you don't need the content to be contiguous in memory, is to remove ownership from the containers and use pointers instead.  The extra indirection solves the problem for the most part at a minimal cost, given that this sort of thing is a couple hundred times a frame, it should have limited to no noticeable overhead.

A second solution which is a bit more complicated but maintains the linear memory layout would be not using pointers but instead use indexes into the arrays.  Adding new items will work without problems, removing items just means walking through all the vectors looking for indices >= the removed item and erasing it or subtracting one.  This means that you need to move the add/remove interface to a top level owner of all the vectors so it can iterate them, but that's generally a good idea to centralize the API anyway.

The third solution is even a little more complicated but has properties which I needed in my system.  I extend the index idea by adding a version tag to each index.  Since I'm promising myself I will never have more than 65k materials in the system at any given time, this handle is a simple 32 bit value, 16 bits of index and 16 bits of version.  Now, when I go to get the material via the handle, I first check that the version stored in the handle and the version in the slot match, if not I return nullptr.  If the callers gets a nullptr they look up the material by hash and assuming is still exists they fix their internal handle.  The reason for this solution is that I only ever add/remove things dynamically in tools or debug builds and the whole check and re-fetch thing compiles out to nothing in release, but it is still fast enough that debug builds are not hobbled by a bunch of overhead.

Again though, this all circles back to what are the requirements for you.  I'd personally start with the second solution as it is easy, fast and leaves the important properties of your layout in place.  The third solution is not suggested unless you start doing a lot of hot reloading, which is why I wanted it.

Thanks for the input. I was afraid that'd be the answer though. The first is impractical due to cache misses and the second is a lot of processing. I hadn't thought of the third but it sounds quite a lot like the second. I do plan on hot swapping but it's not a big priority. It can take even a couple seconds without it being a problem since it should happen rarely.

 

Anyways, after reading your post I came up with a better solution. Like two except when removing a material, I just swap it with the last one. This means I'll need to store the pipeline and render pass id's as well, but c'est la programming.

This topic is closed to new replies.

Advertisement