GR Graphics

This page is a work in progress, about the GR-Graphics project. It is a summary and some key overview points of the library.

You can follow its progress or view specific Graphics details encountered during development in the post of the main page of the blog.

At the time GR Graphics began, I already had some experience in OpenGL, but had not touched DirectX yet.

My venerable, tutorial - born gllibs.dll pretty much only rendered flat-shaded simple meshes. It worked excellently to support GR-Collider, but its lifecycle had come to an end.

What I wanted to develop was framework that would allow me to implement various graphics effects and components on programmable shaders, which would also allow me to explore the Graphics Pipeline better, as it was seen through both DirectX and OpenGL.

The result that I wanted to achieve was an (almost) API - Agnostic framework using which I could  showcase various different shaders and techniques in both APIs. 

One of the best ways to feel in your bones what is really an API quirk and what is actually the Hardware, is actually producing the exact same effect with both frameworks, and comparing the needed steps with each framework. Chances are good (though of course that is not binding) that something shared between DirectX and OpenGL will probably have a hardware requirement behind it, and something that is different is probably  a design choice of the API and not necessarily a hardware requirement.

The actual result of this attempt is functional, and in progress. 

(C++ WARNING) The rest of the page describes the overall project structure and some interesting C++ points addressed while developing the library.

There are actually 3 components plus a supporting lib. For several reasons, at this point in development, I chose to keep them header-only.

GRLibs  is my supporting library comprised of a collection of several helpers
I have used in many of my personal projects, in order to provides all used Data Structures (where the STL does not help or needs to be specialized), all Vector and Matrix related math, the console interface and others.
DirectX already provides Vector and Matrix math, but this wouldn't suit my purposes since I was going API-Agnostic.

The grWin32 framework is a library that handles "system" side of things.
It is a set of header files with various basic classes, the most important of which is the ApplicationTemplate. These classes handle window creation/destruction, the windows message pump, API context creation and destruction, input, and provide the developer with the needed function hooks where he should place code :  init(), render(), updateLogic(), processInput(), keydown(), dispose() etc.

Most classes, especially the ones expected to be directly instantiated by the user and the ones that change between DirectX and OpenGL, are templated between the graphics frameworks.

So, in order to actually instantiate an application, the developer should write:

//main
{
gwin32::Application app = new grwin32::Application<grwin32::GraphicsApi::DirectX11>();
grwin32::Application<grwin32::GraphicsApi::DirectX11>::_ApplicationConfiguration config;
config.title = "My Title"
config.initialHeight = MyHeight;
config.something = something; et.c ....
app.init(config);
app.run();
exit(0);
}

Of course, this class is abstract, and you would actually need to implement its pure-virtual methods in order implement at least some of the aforementioned render(), reshape(), keyup() methods, a-la XNA style.


On a completely different abstraction level is the grGraphics library.
This framework is also a shared between DirectX and OpenGL, but works on a completely different level from grWin32.
It generally revolves around actual Graphics and Models, not system stuff, although some system entities can be found (but always in a completely abstract manner).
In general, it doesn't have to do with "programming" entities, like windows or contexts, but with "domain" entities, like Cameras (various versions like PolarCamera or ChaseCamera), the all-important TriangleMesh, the Light struct, the Material struct, the EntityManager and other framework-independent classes.

Then, you have the actual implementations:
grGraphicsGl and grGraphicsDX11 (there is also a hook for grGraphicsDX9 but I have not implemented it at all, and I probably will not - I like to look ahead).
Here you have another kind of fun: you have classes for dxTextures vs glTextures, glslProgram vs hlslProgram (update : now templated a xlslProgram<GraphicsApi>), glFrameBufferObject and dxRenderBuffer... and templates. Lots of templates.
Did I mention I really love templates?

Some of the classes are unrelated for now, but most (probably all) of these are going to be refactored to API - templated versions.
The rationale here is this:
Imagine writing a grwin32::Application<DirectX11>.
This would necessarily contain several "direct" members that "could" be either DirectX or OpenGL versions.
If these are templated, the user would, instead of having a TextureManager<GraphicsApi::DirectX11>, he would have a TextureManager<_myApi>. Same with the shader classes and others.
The plan is that if, at some point, the declaration changed to grwin32::Application<OpenGL>, this should propagate down the class members, and the class should continue functioning.

Mind you, this is not yet the case, but it is reasonably close and surely doable.

One class that deserves special mention as the king of these templated classes is the the xlslModularProgram,
- what I found to be the best way to make pluggable, configurable shaders without rewriting the entire library each time, or writing a class for every single possible permutation of lights, matrices, tangents, materials, passes, postprocessing configurations etc.

This class is the abstraction of a shader, with an interface to initialize, activate, reshape, and run.
It is in fact a very strange collection of plugins that, dependent on template parameters, activates and deactivates various graphics features, uploads any needed data to the GPU as needed, is notified and takes action when objects are added or removed, the screen reshapes and others. Impressive?

The idea behind it is this:
There are some things that the xlslModularProgram will need to know in order to init and run a shader. For example the shader itself will need a reference to the EntityManager (so that it can retrieve and draw the objects), the Matrix Plugin will need a reference to the MatrixStack class, shaders using textures will need the TextureManager, and so on.
Now, the beauty with templates is that all of this configuration is compile-time defined through the use of template parameters. The principle is that the xlslModularProgram defines boolean or enum template parameters for, for example, texture use, material use, light use, normal use, tangent use, shadowmap use etc.
It also has Plugin members, for example MatrixPlugin, VertexPosPlugin, ShadowmapPlugin and others, that share these parameters.
It also typedef's a Configuration object, which also shares these parameters and is composed by several configuration object specifics to each feature.

Long story short(er), an xlslModularProgram<true, false, false ...> has, among others:

  • a MatrixPlugin<true>, which contains several members relevant to matrix use and expects several configuration parameter, and expects a MatrixConfiguration<true> during init
  • a VertexPosPlugin<false> which, (same as all the other <false> plugins), defines the same interface as the VertexPosPlugin<true> but with empty implementation, and expects a VertexPosConfiguration<false> which it does nothing with.


For some code on how this works, visit this post

This way, when the developer instantiates xlslModularProgram<GraphicsApi::DirectX11, true, false, false...> program(), he can then get a configuration object that only contains the fields that need to be configured for this specific shader setup:

auto config = program.getConfigurationObject() (or even xlslModularProgram<GraphicsApi::DirectX11, true, false, false...>::ConfigurationObjectType config;)


config.core_EntityManager = &app.entityManager;

config.vertexposition_ShaderSemantic = "IA_INPUT_POSITION";
... plus whatever else is needed, and only that.


At the moment, no direct link is provided as the engine is largely a work in progress, and I have not yet integrated it with the scripting engine which means that scenarios I now write are more or less static (of course you still control camera and object position, but that's pretty much it...), but the work done can be followed in the main blog page.

No comments:

Post a Comment