6

I have my DirectX11 Engine written in C++, a wrapper in C++ with CLR, and an interface in C#.

1) I am curious about where the bottleneck is in this structure, and I'm wondering if there is a more efficient way to let me host the DirectX11 rendering in a WinForms Control.

2) Is there a way to render on a thread other than the owner of the WinForms control? I doubt it but figured I'd ask.

3) Is there a way to render multiple frames without going through the wrapper layer on each frame but keep the application responsive?

I have compared this setup to SlimDX and am actually getting slightly slower FPS when simply clearing the screen and not doing any other API calls. SlimDX ~ 3000 FPS, My Engine ~ 2000 FPS. This isn't a big deal but I am wondering where this 33% difference is coming from as it will probably make a difference later when comparing 20 fps to 30.

I will walk through the current setup and describe as much as I can. I am sure along the way people will ask for more info and I'll update as needed.

My WinForms GraphicsPanel Control is below. It passes the system messages to the wrapper layer.

public class GraphicsPanel : Control
{
    EngineWrapper Engine;

    public GraphicsPanel()
    {
        this.SetStyle(ControlStyles.Selectable, true);
        this.SetStyle(ControlStyles.UserMouse, true);
        this.SetStyle(ControlStyles.UserPaint, true);
        this.TabStop = true;
    }
    public void SetEngine(EngineWrapper Engine)
    {
        this.Engine = Engine;

        Application.Idle += OnApplicationIdle;
    }

    ~GraphicsPanel()
    {
        System.Windows.Forms.Application.Idle -= OnApplicationIdle;
    }
    void PassMessage(Message m)
    {
        Engine.ProcessWindowMessage(m.Msg, m.WParam, m.LParam);
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        PassMessage(m);
    }
    private void OnApplicationIdle(object sender, EventArgs e)
    {
        while (AppStillIdle)
            if (Engine != null)
                Engine.ProcessWindowMessage(0, IntPtr.Zero, IntPtr.Zero);
    }
    public bool AppStillIdle
    {
        get
        {
            NativeMethods.PeekMsg msg;
            return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
        }
    }
    internal class NativeMethods
    {
        private NativeMethods() { }

        [StructLayout(LayoutKind.Sequential)]
        public struct PeekMsg
        {
            public IntPtr hWnd;
            public Message msg;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public System.Drawing.Point p;
        }

        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        public static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
    }
}

Within the Engine Wrapper I have this function to pass the message from the WinForms control to the native C++ layer.

void EngineWrapper::ProcessWindowMessage(int msg, System::IntPtr wParam, System::IntPtr lParam)
{
    m_Engine->ProcessWindowMessage(msg, (void*)wParam, (void*)lParam);
}

Finally, the Native C++ Engine processes the messages as such:

void Input::ProcessWindowMessage(int msg, void* wParam, void* lParam)
{
    if (msg == 0 || msg == WM_PAINT)
    {
        DrawFrame();
    }
    else if (msg == WM_SIZING || msg == WM_SIZE)
    {
        DoResize();
        DrawFrame();
    }
    else if (msg >= WM_MOUSEFIRST && msg <= WM_MOUSEWHEEL)
    {
        ProcessMouseMessage(msg, wParam, lParam);
    }
}
Russell Trahan
  • 783
  • 4
  • 34
  • There are 1000 page books on the subject, I don't know if I could write a useful (or correct) answer in a couple of paragraphs. – BlamKiwi Dec 03 '14 at 01:40
  • Any insight or critique is appreciated. – Russell Trahan Dec 03 '14 at 06:59
  • I'll put something together over the weekend. – BlamKiwi Dec 04 '14 at 00:25
  • 1
    There's a very informative chapter in "Game coding complete, 4th edition" about using native C++/DX11 game engine in a C#/winforms UI editor. I think that'd help you a lot. – rashmatash Dec 04 '14 at 12:44
  • Its my experience of comparing rendering engines for windows that a 2000 vs 3000 difference when rendering nothing never translates into a 20 vs 30 split in the "real world" scenarios. On some occasions, the difference completely vanishes. I'd recommend rendering some geometry with both systems, perhaps adding a shader effect, etc. and see what you difference comes out as then. – Steve Lillis Dec 05 '14 at 08:53
  • I stretched the truth when I said rendering nothing. Both engine's have all states set and are rendering a basic scene of my editor "studio." It's not many vertices (<1000) but it is something going through all stages with all states set. Basically a grid for a floor and a background. Both are doing the exact same tasks. – Russell Trahan Dec 05 '14 at 16:47
  • I'd recommend you actually stress test this before you read too much into what's going on here. It could just be something minor that you are doing that slows this down and only happens once per frame. The fact that you have 2000 frames per second would massively exaggerate the issue. Stress the system until you get it down to your worst acceptable FPS and then compare it to SlimDX. Also, I recommend that you use Seconds Per Frame rather than FPS for performance profiling, the results make more sense and when you optimize and compare successive optimizations. – Grant Peters Dec 08 '14 at 00:33
  • @rashmatash I like your choice in books. 8D – BlamKiwi Dec 08 '14 at 01:13

1 Answers1

4

1) I am curious about where the bottleneck is in this structure, and I'm wondering if there is a more efficient way to let me host the DirectX11 rendering in a WinForms Control.

Disregarding the difference due to SlimDX being implemented in C++/CLI (which should be negligible), the only difference I see in your code compared to the SlimDX implementation is that you are bothering your engine with message processing:

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
    PassMessage(m);
} 

I'd rather simplify and keep message concerns away from your engine. Handle the message in your WndProc override and invoke whatever operations you need on Engine (eg. Resize, MouseMove, MouseClick, or other sorts of input processing) and call DrawFrame directly whenever idle:

private void OnApplicationIdle(object sender, EventArgs e)
{
    while (AppStillIdle)
        if (Engine != null)
            Engine.DrawFrame();
}

I wouldn't expect this to account for a ~33% performance difference but it may be worth looking into it.

2) Is there a way to render on a thread other than the owner of the WinForms control? I doubt it but figured I'd ask.

Yes, you can use off-screen surfaces. However, then the issue becomes how to update your window content. For instance, you can use a picture viewer and set the image obtained from the off-screen surface but this would yield a worse performance.

3) Is there a way to render multiple frames without going through the wrapper layer on each frame but keep the application responsive?

Given the approach you are using, frames are rendered on-demand from your application idle event handler, so no. You can do so using an off-screen rendered as explained in the previous point.

jnovo
  • 5,659
  • 2
  • 38
  • 56
  • 1
    It's common to have message processing to be part of your game loop in a single threaded game to ensure that messages are always processed before another frame begins. It helps with reducing input latency from Win32 and Windows detecting if your application has actually crashed or not. – BlamKiwi Dec 03 '14 at 23:35