0

I want my C# engine to give the user the choice between using Direct3D or OpenGL. My engine internally is designed like this abstracted example for a shader class:

internal class Shader
{
    private IShaderImplementation _implementation;

    internal Shader(ShaderType type, string code)
    {
        switch (Engine.GraphicsInterface)
        {
            case GraphicsInterface.Direct3D:
                _implementation = new Direct3DShader(type, code);
                break;
            case GraphicsInterface.OpenGL:
                _implementation = new OpenGLShader(type, code);
                break;
            default:
                throw new UnsupportedInterfaceException("Shader");
                break;
        }
    }

    internal void Compile()
    {
        _implementation.Compile();
    }
}

I thought this is a good design pattern. (I also prefer calling constructors over factory methods as you might have noticed, but that's just a side thing).

However:

  • I don't want to stuff OpenTK (for OpenGL stuff) assembly references together with SharpDX (for Direct3D stuff) assembly references in one class library. I don't even know if that is a good idea. From the current design this would be required as the Direct3DShader and OpenGLShader classes reference D3D or OGL methods directly. Wouldn't I need to ship out both OpenTK / SharpDX assemblies even if the user only wishes to use OpenGL for example?
  • I'd prefer to have one main assembly containing only the general interface stuff as seen above (let's say "Awful.Engine.dll", and another one for only OpenGL stuff ("Awful.Engine.OpenGL.dll"), another one for Direct3D stuff ("Awful.Engine.Direct3D"). But how can this be referenced in the main assembly? As you can see I have an enumeration of interfaces, this wouldn't work anymore.

Asking in a more general form, how can my main assembly interfaces and classes get implemented and extended in sub assemblies?

PS: I've heard about reflection about which I know a cent or two, and then the managed extensibility framework, but I have not figured out how it can fit into my design.

Ray
  • 7,940
  • 7
  • 58
  • 90
  • do you want it switchable at compile time, or run time? – thumbmunkeys Aug 20 '14 at 20:05
  • @thumbmunkeys: At compile time would be enough. The user implements a property "GraphicsInterface" in which he returns the desired enum value. If that is possible. – Ray Aug 20 '14 at 20:17

1 Answers1

1

Design it as 3 Class Libraries for your graphics and sound layers.

Class Library 1: The interface layer

  • Contains all of the wrappers and interfaces that your wrappers will implement.
  • Design the interfaces to work in their own application domain and pass out interfaces for everything.

Class Library 2 (OpenTK):

  • References Class Library 1 and OpenTK.
  • Add a sound API like OpenAL.
  • Implement your interfaces that wrap calls to OpenTK and playing sounds.

Class Library 3 (SharpDX):

  • References Class Library 1 and Sharp DX.
  • Implement your interfaces that wrap calls to SharpDX and play sounds through SharpDX.

Game Code

  • Add a hard reference to Class Library 1, so you have all the interface types, enumerations, etc.
  • Dynamic Load Class Library 2 or Class Library 3 with reflection.
  • Create a new application domain for your hardware layer.
  • Use AppDomain.Load to load either Class Library 2 or 3.
  • Once the hardware assembly is loaded and you have the Assembly object, use reflection to get references to your interfaces, maybe something like this:

    // Making up this pseudo code
    GameApi gameApi = (GameApi)hardwareAsm.GetType(GameApi).GetMethod("Initialize").Invoke();
    
    TheGame gameWorld = gameApi.CreateGame(...);
    gameWorld.ResolutionChanged += Resolution_Changed(...);
    gameWorld.Terminated += Terminated(...);
    gameWorld.KeyDown += Key_Down(...);
    // etc....
    gameWorld.Start();
    

The benefit's of having the entire layer run in it's own application domain are numerous:

  • Application Domains can be unloaded, causing any assemblies they loaded to also be unloaded. (This will be required if a user can switch from DirectX to OpenGL at runtime, which would be neat and possible, and something I've never seen anyone do before).
  • Application domains can be secured with their own security descriptors, ACE's, etc. E.g. you can prevent code running in an application domain from having access to create files, make internet requests, etc. (Very useful if you are designing a plugin layer for game addons).
  • Application domains can be written to be able to crash gracefully, log their crashes, and restart themselves without compromising the root process.
Ray
  • 7,940
  • 7
  • 58
  • 90
Ryan Mann
  • 5,178
  • 32
  • 42
  • This sounds really good, however, can you think of a possibility to hide the reflection "stuff" from the user who is writing the game code? I need to make it as easy as possible for the user. I guess it's not avoidable this way though, then it's not a problem. – Ray Aug 21 '14 at 06:59
  • Yeah, easy. In the Interface DLL (which is what you'll have your end users reference) just have a main entry point to everything, e.g. 1 static class. Here's a pseudo example GameSettings settings = GameApi.CreateSettings(); settings.GFXBacker = GFXBacker.OpenGL3; GameApi.CreateGame(settings); CreateGame would use the settings to determine what hardware layer to use. It does all the dynamic loading, app domain creation, reflection to load all the interfaces that TheGame class hooks to etc. Also, while your at it, I recommend being compatible with mono so users can port to linux, osx, etc. – Ryan Mann Aug 21 '14 at 15:48
  • Basically, just abstract the relflection layer away using something like the GameApi static class idea. Also, keep in mind that if done right, the reflection code is only initialization code, meaning it would only run once. Once the interfaces are all obtained the TheGame class (or w/e you end up building) would have direct references to them without needing to continue to use reflection. Reflection is expensive, you don't want to use it a lot, especially not in a draw loop, it would kill the performance of the loop. – Ryan Mann Aug 21 '14 at 15:51
  • I'm also curious as to what exactly your building. Not sure if you are aware, but Mono-Project picked up XNA Game Studio and it's now called MonoGame, which allows you to build games in c# just like XNA did and they are portable and MonoGame uses OpenTK, they also have a build that uses SharpDX. I started getting into game development a while back, but stopped when I hit an annoying problem. So now I'm working on a Custom Font Rasterizer that works with True Type Fonts. – Ryan Mann Aug 21 '14 at 15:57
  • Thanks for the comments! I'll try to make that reality soon. PS: I don't like the XNA/MonoGame content management. – Ray Aug 21 '14 at 15:58
  • Well, if you make an engine that supports multiple game windows, e.g. like being able to open my inventory and move it to my second monitor. I'll be a fan :) lawl – Ryan Mann Aug 21 '14 at 16:00
  • 1
    My current SharpDX only engine allows creation of multiple game class instances with their own window, which are like 2 instances of the engine. But it can't do more than just some colored triangles right now. – Ray Aug 21 '14 at 16:09
  • My current goal is to build a UI engine that sits on top of OpenTK. I want something that works like XAML, but when I got to thinking about it more I'm not leaning towards HTML5. So I basically want to create a OpenTK renderer for a Custom Version of HTML5 with new element support for a Game UI. Then I can build a UI with something like Inventory Where MainScreen is like an HtmlBody, the game renders in the body and the content overlays it. Ambitious project. – Ryan Mann Aug 21 '14 at 16:23
  • But the end goal is to have an easily customizable UI that's as easy to mess with as a website front end. It'll use standard Javascript to CLR usign something like http://javascriptdotnet.codeplex.com/ but to support font rendering like HTML I need a font rasterizer :). Also to expose hardware accelerated graphics the javascript layer, I'll make my HTML customizations abstract WebGL overtop my engines graphics layer which will allow users to do custom rendering with WebGL and the WebGL will talk directly to my backend. I can do the same thing with WebSockets. – Ryan Mann Aug 21 '14 at 16:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59748/discussion-between-debugerr-and-ryios). – Ray Aug 21 '14 at 17:10