Abstracting graphics libraries from the game engine

Started by
21 comments, last by Dave Eberly 14 years, 7 months ago
Quote:Original post by RobMaddison
I've been trying to think of a way to reduce the build configurations (as mentioned by Dave Eberly in his post) but I've yet to figure that out.


I did not mention in my previous post what I actually did to reduce the build configurations. My "graphics" library in WM4 includes both the scene graph management and the Renderer abstract interface. I have "renderer" libraries for Direct3D and OpenGL (with flavors WGL, AGL, and GLX). For the sake of the discussion, let me just use Manager to refer to the scene graph management. Graphics resources (vertex buffers, index buffers, textures, render targets, shaders, etc.) are accessed both by Manager and Renderer. Manager and Renderer communicate through the Renderer interface. This dependency causes the DLL problem I mentioned for graphics/renderers. Making Renderer calls virtual avoids the DLL problem, but has the performance issue I also mentioned.

In WM5, I factored the Manager stuff into a single library (still my "graphics" library) that no longer has the Renderer class. All graphics resources are backed by system memory (*** see below). When Manager modifies graphics resources (updates vertex buffers or textures, for example), messages are posted to a RendererMessageQueue. The queue class has no dependency on Renderer. The Renderer class, implemented in the render libraries for each platform of interest (and having no virtual functions), processes messages before any drawing is done. To summarize, WM4 Manager "passes messages" by directly calling Renderer functions. WM5 Manager "passes messages" to a queue and Renderer reads that queue. Thus, RendererMessageQueue is the factored component that solves the DLL dependency problem.

This introduces some new worries--synchronization. You have to ensure that the message queue is processed and VRAM resources are updated before using them. These days with multicore processors and multithreading, you have to worry about such issues anyway. So I do not consider this a worry. In fact, it starts forcing the application programmer to think more in terms of concurrency and synchronization rather than sequentially.

My physics library could now depend on the graphics library for its data without also depending on the renderer libraries. For example, building a bounding-volume tree for a triangle mesh requires processing the VertexBuffer and IndexBuffer that are part of the TriMesh (all proper names are classes in the graphics library). The tree also needs Bound objects (the bounding volumes) that the graphics system uses for culling. The issue for Manager-Physics communications is the same as for Manager-Renderer communications: direct communication through class interfaces. However, in this case I broke the dependency by analyzing what the Physics system wants. For example, the bounding-volume tree construction is templatized using typenames of TMesh and TBound. TMesh and TBound are required to have minimum interfaces to support the tree construction. In a physics application, the classes that satisfy the minimum interface requirement are defined in the application itself; the classes are simple wrappers around graphics library data. Someone else could use the physics template classes in a completely different environment and wrap his own data structures to feed the physics system.


*** This supports the lost-device/reset-device semantics necessary for Direct3D so I can "refresh" VRAM myself and let Direct3D use the default pool for resources. OpenGL hides a lot of this during a context switch. But these are stories for another day. On a game console, say, PS3 or Xbox360, there is no need to waste memory to back VRAM--in fact you don't want to waste precious memory. What I do for desktop systems is for desktops alone...

Advertisement
I think another way to relieve the large number of build configurations would be to link to the underlying graphics dll explicitly rather than implicitly. As long as both flavours of graphics dll offer the same method interface, you'd only need debug and release configurations because everything uses late-binding. The only thing going in or out of either graphics dlls will be types native to the engine. I think this might be another fairly elegant solution.
Quote:Original post by RobMaddison
I think another way to relieve the large number of build configurations would be to link to the underlying graphics dll explicitly rather than implicitly. As long as both flavours of graphics dll offer the same method interface, you'd only need debug and release configurations because everything uses late-binding. The only thing going in or out of either graphics dlls will be types native to the engine. I think this might be another fairly elegant solution.


This is certainly a solution. Be prepared to deal with C++ decorated names and the associated pain when using GetProcAddress. Also, if the list of DLL exported functions will change regularly during development, then the client code that has to fetch all the function pointers with GetProcAddress must be updated (as compared to the import lib approach where relinking is fast).

My libraries are designed to be used together, so the import lib approach is satisfactory. That is, a client need not take action during the run-time linking when GetProcAddress fails. If for some reason he wants to select the DirectX or OpenGL renderer at run-time, then he is on his own...

At any rate, writing the wrapper on the client side for the LoadLibrary and all the GetProcAddress calls is my second favorite hobby, the first hobby writing OpenGL extension wrappers ;)

This topic is closed to new replies.

Advertisement