Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DirectX11 Engine in C++ and Interface in C#

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);
    }
}
like image 680
Russell Trahan Avatar asked Nov 29 '14 17:11

Russell Trahan


1 Answers

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.

like image 182
jnovo Avatar answered Nov 12 '22 07:11

jnovo