GUI Wrapper

I am a big fan of creating a game engine that can adapt not only in what it can do, but also in how it can handle the new code. Recently, for my graphics subsystem, I wrote a class to be overridden, which works as follows:

 class LowLevelGraphicsInterface { virtual bool setRenderTarget(const RenderTarget* renderTarget) = 0; virtual bool setStreamSource(const VertexBuffer* vertexBuffer) = 0; virtual bool setShader(const Shader* shader) = 0; virtual bool draw(void) = 0; //etc. }; 

My idea was to create a list of functions that are universal to most graphical APIs. Then for DirectX11, I would simply create a new child class element:

 class LGI_DX11 : public LowLevelGraphicsInterface { virtual bool setRenderTarget(const RenderTarget* renderTarget); virtual bool setStreamSource(const VertexBuffer* vertexBuffer); virtual bool setShader(const Shader* shader); virtual bool draw(void); //etc. }; 

Each of these features will interact directly with the DX11 . I understand that there is a layer of indirection. Are people disabled by this fact?

Is this a widely used method? Is there anything else that I could / should have done? It is possible to use a preprocessor but it seems messy to me. Someone also mentioned me patterns . What do you guys think?

+6
source share
3 answers

If virtual function calls become a problem, there is a compile-time method that removes virtual calls using a small amount of preprocessor and compiler optimization. One possible implementation is as follows:

Declare a basic rendering with pure virtual functions:

 class RendererBase { public: virtual bool Draw() = 0; }; 

Declare a specific implementation:

 #include <d3d11.h> class RendererDX11 : public RendererBase { public: bool Draw(); private: // D3D11 specific data }; 

Create a RendererTypes.h header to forward the declaration of your renderer based on the type you want to use with some preprocessor:

 #ifdef DX11_RENDERER class RendererDX11; typedef RendererDX11 Renderer; #else class RendererOGL; typedef RendererOGL Renderer; #endif 

Also create a Renderer.h header to include the appropriate headers for your renderer:

 #ifdef DX11_RENDERER #include "RendererDX11.h" #else #include "RendererOGL.h" #endif 

Now wherever you use the renderer, refer to it as a Renderer type, include RendererTypes.h in your header files and Renderer.h in your cpp files.

Each of the rendering implementations should be in different projects. Then create various layout configurations for compilation using whatever visualization you choose to use. For example, you do not want to include DirectX code for Linux configuration.

In debug builds, calls to virtual functions can still be made, but in releases they are optimized because you never call calls through the base class interface. It is only used to provide a common signature for your visualizer classes at compile time.

Although this method requires a little preprocessor, it is minimal and does not interfere with the readability of your code, since it is isolated and limited by some typedef and includes. The only drawback is that you cannot switch rendering implementations at runtime using this method, since each implementation will be built for a separate executable. However, there is really no need to switch configurations at runtime.

+7
source

I am using an abstract base class approach for a rendering device in my application. Works great and allows you to dynamically choose which rendering to use at runtime. (I use it to switch from DirectX10 to DirectX9, if the first is not supported, that is, in Windows XP).

I would like to point out that calling a virtual function is not the part that is worth the performance, but the type conversion of the arguments. To be truly general, the public interface with the visualizer uses its own set of parameter types, such as the custom IShader and the custom Matrix3D type. No type declared in the DirectX API is displayed in the rest of the application, since OpenGL will have different types of matrices and shader interfaces. The disadvantage of this is that I need to convert all the Matrix and Vector / Point types from my custom type to the one that the shader uses in implementing a particular rendering. This is much more expensive than the cost of a virtual function call.

If you are making a distinction using a preprocessor, you also need to map different types of interfaces like this. Many of them are the same between DirectX10 and DirectX11, but not between DirectX and OpenGL.

Edit: see answer in C ++ Having multiple graphical options for an example implementation.

0
source

So, I understand that this is an old question, but I can not help it. The desire to write such code is simply a side effect associated with object-oriented indoctrination.

The first question is whether you really need to exchange the reverse ends of the rendering, or just think it's cool. If the corresponding point in time can be determined during the build for this platform, then the problem is solved: use a simple, non-virtual interface with the implementation selected during build.

If you find that you really need to change it, still use a non-virtual interface, just load the implementations as shared libraries. With this kind of swapping, you will most likely need a code rendering code, as well as some high-performance game rendering code, depending on performance, and replaceable. That way, you can use the generic, high-level engine rendering interface for things that are mostly done by the engine, while still having access to the built-in specific code to avoid the conversion costs mentioned by PMF.

Now I must say that when exchanging with shared libraries, an indirect transfer is introduced: 1. You can easily get indirectness to be <before ~ โ€‹โ€‹= virtual calls and 2. This is a high-level indirect relation never to performance in any significant game / engine. The main advantage is that dead code is unloaded (and to the side), and also simplifies the API and the overall design of the project, increasing readability and understanding.

Beginners, as a rule, do not know about it, because nowadays there is so much blind OO, but this style of "OO first, never ask questions" is not without cost. This type of design has the cost of understanding the tax code and leads to a code (much lower level than this example), which is inherently slow. Of course, object orientation has its place, but (in games and other high-performance applications) the best development method I have found is to write applications as little as possible OO, only yielding when the problem forces you to hand. You will develop intuition in order to draw a line when you gain more experience.

0
source

Source: https://habr.com/ru/post/975975/


All Articles