Graphics 2

Oct 19, 2019 • openjge-dev

Alright, so I’ve just been able to finalize the design plans for the new graphics module, which I hope to begin working on sometime soon. While most of the content for this module will consist of new additions to the engine, I’ll also have to make a couple of modifications to the existing codebase. As mentioned in the previous blog post, the main source of these changes will be made to the Core and how it handles IState/IComponent updates. This because I’ve revamped the actual function of IRenderComponents and how they interact with the graphics system as a whole. In the current setup, each IRenderComponent is responsible for calling the Graphics.Module.drawModel method in its update method, which then immediately draws that component. However, in the new design, the draw calls for each IRenderComponent are deferred until all other IRenderComponents are collectively ready to be rendered. Doing so allows us to implement much more intricate method of sorting, as data can be pulled from each IRenderComponent in its update method, which can then be used to sort every simulated IRenderComponent holistically instead of being limited to just sorting by shader or translucency (as is the current situation). So far, so good, right? But, if the components aren’t drawing themselves, then what is? Remember that modules are essentially just tools to be used by components, so it’s not like the Renderer itself can just draw everything at some arbitrary point - something needs to incite it to draw each specific IRenderComponent. Since we already maintain an internal list of each IRenderComponent’s data (passed to the Graphics module in the component’s update method), my solution to this was to define a custom IState implementation that would be responsible for dispatching the draw commands for each component. The issue with this, though, is that, with the current state of the engine, there’s no way to ensure that this “dispatcher” state is updated after every IRenderComponent has been updated. The only way to add IState objects to the Core is through adding IScene objects to the stack, which can be manipulated by the end user (through popping the stack without having a key or the actual IScene object being removed). This is bad! Which brings me back to the modifications I was talking about earlier - I’ll need to add the ability for modules to add and remove custom IState objects to the Core that are not visible to the end user.

IRenderComponents themselves are going to look something like this:

Whoops - no image :(

As for how they’re going to be sorted, the data from each component, such as its mesh, texture, OpenGL draw command, and position, will all be passed into the Graphics module when its update method is called. This data will then be used to generate an integer key which is deposited into a specific render bucket depending on the key’s target render pass. Once this process is complete, the render buckets, which are represented by custom IComponent objects in the “dispatcher” state discussed above, will be sorted and then draw calls will be issued. The sorting hierarchy can be seen below.

Whoops - no image :(

The only problem I currently see with this design is that when performing occlusion culling, each individual object must be checked in its update method, rather than having only the objects within the view frustum being updated. However, with respect to the scale of the engine, I doubt this should have any meaningful impact on performance.

Alright, to cap it all off, here’s the to-do list of all the things I’ll need to do:

  • Add support for module-defined IState objects in Core

  • Add custom Dispatcher state to (new) Graphics module

  • Add custom RenderBucket component to Graphics module

  • Create RenderKey class

  • Create OGLCommands class with CommandData inner class