Refactoring the model instance management system. -- Owen Meyers

     At the present moment, through playtests, it may be painfully obvious that even though we can support skinned meshes and animation (arguably the hardest type of model to support for the requirements of our game), there are zero static meshes, despite them being simpler and easier to process. There is a reason for this, and that reason is incredibly dumb when stated aloud. The system as it works right now has to reshuffle matrices whenever an enemy dies. This is to be expected, as the matrices are all in a singular buffer that every enemy refers to in order to keep rendering fast. The problem with that is when static meshes are introduced in order to add visual variety to the world, they are placed behind the skinned mesh matrices in that buffer. This leads to an issue where the buffer and a set of structs that index it desync from each other, and lead to rogue mesh instances placed out of bounds (this is rarely, but occasionally visible in the current build of the game, which has zero static meshes). That issue gets exponentially worse for each new model that has 1 or more world matrices in the buffer at a time. Obviously, the system as it stands must be rebuilt and fixed in order to prevent that from happening.

    There are multiple approaches I am planning on utilizing to resolve this, each has their own benefits and downsides. The first approach is to make a new management system that holds all of the matrix data on its own, similar to the animation manager. This would have the benefit of keeping complexity within the Central Processing system down, and allowing for world matrices to be organized rather quickly, likely through the use of a hash map. The downside of this is that it would require a whole new system to be built separate from Central Processing, and as a result, could cause conflict on the GIT repo as well as simply being more time consuming to build. An alternative approach is to simply keep every thing separated from each other. What I mean by this is to simply have 2 buffers on the CPU-side that contain world matrices. The first buffer contains all of the world matrices for the skinned meshes, and the other for all of the static meshes. These buffers are then concatenated to each other before being sent to the GPU, with each struct that maintains indexing for static meshes having an implied offset that is the length of the skinned mesh buffer. This has the benefit of being relatively simple to write on top of the current system, and allows for the buffers to be kept separate from each other during processing of the scene, but then put together during rendering, keeping rendering speed fast. The downside for this is that every frame, 2 separate arrays would need to be written into one, and two sets of the indexing structs would also need to be copied into a buffer of one, with offsets written into them. This would greatly increase the time needed to process the scene, as the complexity of such an algorithm would almost certainly be O(n), with worst case climbing as high as O(n^2) if there is a) a high volume of matrices and b) a large amount of changes that need to be applied to the buffers that frame anyway (i.e. a large amount of enemies die simultaneously, a room is loaded (the entire static mesh buffer is invalidated), etc. etc.). I am experimenting with both, as I believe that the first solution will likely require some form of the second solution, however, the first solution abstracts it away and can even be placed on its own thread to allow for it to execute the organization concurrently while more time-consuming tasks (such as AI) are being processed.

Relation between Central Processing and the Instance Manager in solution 1. Central Processing sends commands and data (i.e. pop a matrix at n index or create a new instance of x) and the Instance Manager sends back indices for entities to utilize when indexing.


The proposed solution 2. The 2 lists of matrices visibly feeding into m_AllMatrices will be written to on an as-needed basis. For m_SkinnedMeshMatrices, that buffer will be invalidated every frame, and rewritten to ensure that any data to be removed is already cleared. It will also be organized by the offsets lists below. m_StaticMeshMatrices won't have any changes to its structure until a room transition, at which point it will be invalidated and replaced with data supplied by the level constructor. All skinned mesh related buffers get invalidated on room load. Those 2 buffers will be concatenated into m_AllMatrices, which will then be written to the buffer each frame. m_SkinnedMeshOffsets will be changed by enemy deaths or newly spawned enemies, while m_StaticMeshOffsets has its offsets informed by the offsets of skinned mesh offsets. Both of those are sent to the render layer once via RecieveMeshOffsets which will save a pointer to those lists. Those lists will always be invalidated by a room change and a floor transition.

Comments

Popular posts from this blog

Adding assets to every enemy -- Owen Meyers

Sphere to AABB collision