0

i am making an app that overlays other d3d games,
the app is working perfectly except it has a huge performance impact on the cpu
enter image description here
taking 21.4 % of the cpu when rendering only a single line ! i am using slimdx library on c# and here is my full code

OverLay.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SlimDX.Direct3D11;
using SlimDX.Windows;
using System.Runtime.InteropServices;
using System.Security;
using SlimDX;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D9.Device;
using Resource = SlimDX.Direct3D9.Resource;
using System.Threading;
using D3D = SlimDX.Direct3D9;

namespace OverlayForm
{
    public partial class OverLay : RenderForm
    {
        RenderForm form;

        Device device;
        // D3D.Sprite sprite;

        public OverLay()
        {
            InitializeComponent();

            Paint += OverLay_Paint;
            FormBorderStyle = FormBorderStyle.None;
            ShowIcon = false;
            ShowInTaskbar = false;
            TopMost = true;
            WindowState = FormWindowState.Maximized;



            //Make the window's border completely transparant
            //SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) ^ WS_EX_LAYERED ^ WS_EX_TRANSPARENT));
            SetWindowLong(Handle , GWL_EXSTYLE , (IntPtr)(GetWindowLong(Handle , GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT));

            //Set the Alpha on the Whole Window to 255 (solid)
            SetLayeredWindowAttributes(Handle , 0 , 255 , LWA_ALPHA);



            form = this;
            form.FormClosing += Form_FormClosing;
            //Init DirectX
            //This initializes the DirectX device. It needs to be done once.
            //The alpha channel in the backbuffer is critical.
            D3D.PresentParameters presentParameters = new D3D.PresentParameters();
            presentParameters.Windowed = true;
            presentParameters.SwapEffect = D3D.SwapEffect.Discard;
            presentParameters.BackBufferFormat = D3D.Format.A8R8G8B8;


            device = new Device(new D3D.Direct3D() , 0 , D3D.DeviceType.Hardware , Handle ,
            D3D.CreateFlags.HardwareVertexProcessing , presentParameters);

            //sprite = new D3D.Sprite(device);

            font = new D3D.Font(device , new Font("Arial" , 9 , FontStyle.Regular));
            line = new D3D.Line(this.device);

            MessagePump.Run(form , new MainLoop(dxThread));
        }

        private void Form_FormClosing(object sender , FormClosingEventArgs e)
        {
            device.Dispose();
        }

        int centerx = Screen.PrimaryScreen.WorkingArea.Width / 2;
        int centery = Screen.PrimaryScreen.WorkingArea.Height / 2;
        private void OverLay_Paint(object sender , PaintEventArgs e)
        {
            //Create a margin (the whole form)
            marg.Left = 0;
            marg.Top = 0;
            marg.Right = Width;
            marg.Bottom = Height;

            //Expand the Aero Glass Effect Border to the WHOLE form.
            // since we have already had the border invisible we now
            // have a completely invisible window - apart from the DirectX
            // renders NOT in black.
            DwmExtendFrameIntoClientArea(Handle , ref marg);
        }
        private static D3D.Font font;
        private static D3D.Line line;
        private void dxThread()
        {
            form.TopMost = true;
            device.SetRenderState(D3D.RenderState.ZEnable , false);
            device.SetRenderState(D3D.RenderState.Lighting , false);
            device.SetRenderState(D3D.RenderState.CullMode , D3D.Cull.None);
            device.SetTransform(D3D.TransformState.Projection , Matrix.OrthoOffCenterLH(0 , Width , Height , 0 , 0 , 1));
            device.BeginScene();



            //DrawFilledBox(0 , 0 , 100 , 100 , Color.White);
            //font.DrawString( null, "Swag" , 10, 10 , new Color4(Color.White));            
            //DrawBox(0 , 0 , 10 , 10 , 1 , Color.Green);
            DrawLine(0 , 0 , Screen.PrimaryScreen.Bounds.Width , Screen.PrimaryScreen.Bounds.Height , 2 , Color.Pink);


            device.EndScene();
            device.Present();
        }

        public static void DrawFilledBox(float x , float y , float w , float h , Color Color)
        {
            Vector2[] vLine = new Vector2[2];

            line.GLLines = true;
            line.Antialias = false;
            line.Width = w;

            vLine[0].X = x + w / 2;
            vLine[0].Y = y;
            vLine[1].X = x + w / 2;
            vLine[1].Y = y + h;

            line.Begin();
            line.Draw(vLine , new Color4(Color));
            line.End();
        }
        public static void DrawLine(float x1 , float y1 , float x2 , float y2 , float w , Color Color)
        {
            Vector2[] vLine = new Vector2[2] { new Vector2(x1 , y1) , new Vector2(x2 , y2) };

            line.GLLines = true;
            line.Antialias = false;
            line.Width = w;

            line.Begin();
            line.Draw(vLine , new Color4(Color));
            line.End();

        }
        public static void DrawBox(float x , float y , float w , float h , float px , System.Drawing.Color Color)
        {
            DrawFilledBox(x , y + h , w , px , Color);
            DrawFilledBox(x - px , y , px , h , Color);
            DrawFilledBox(x , y - px , w , px , Color);
            DrawFilledBox(x + w , y , px , h , Color);
        }

        #region Extras
        private Margins marg;
        //this is used to specify the boundaries of the transparent area
        internal struct Margins
        {
            public int Left, Right, Top, Bottom;
        }

        [DllImport("user32.dll" , SetLastError = true)]

        private static extern UInt32 GetWindowLong(IntPtr hWnd , int nIndex);

        [DllImport("user32.dll")]

        static extern int SetWindowLong(IntPtr hWnd , int nIndex , IntPtr dwNewLong);

        [DllImport("user32.dll")]

        static extern bool SetLayeredWindowAttributes(IntPtr hwnd , uint crKey , byte bAlpha , uint dwFlags);

        public const int GWL_EXSTYLE = -20;

        public const int WS_EX_LAYERED = 0x80000;

        public const int WS_EX_TRANSPARENT = 0x20;

        public const int LWA_ALPHA = 0x2;

        public const int LWA_COLORKEY = 0x1;

        [DllImport("dwmapi.dll")]
        static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd , ref Margins pMargins);

        #endregion



    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace OverlayForm
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>        
        static void Main()
        {
            using (OverLay x = new OverLay())
            {

            }
        }
    }
}

Please note :
i already saw this : Very high CPU usage directx 9

but i am using MessagePump.Run and don't know how to apply the answer.

Community
  • 1
  • 1
Ahmed Fwela
  • 955
  • 1
  • 13
  • 32
  • it's probably because you are using C# managed code to try to intercept frames created by high-performance native games. – Chuck Walbourn Jul 14 '16 at 05:44
  • What is the CPU load if you comment-out **all** your drawing? –  Jul 14 '16 at 06:00
  • @MickyD the exact same .. – Ahmed Fwela Jul 14 '16 at 06:03
  • @ChuckWalbourn cpu usage is high even without opening a game – Ahmed Fwela Jul 14 '16 at 06:04
  • That link is probably not relevant. –  Jul 14 '16 at 06:07
  • What sort of GPU do you have? Is it a laptop? Is it a integrated GPU on the laptop's motherboard? –  Jul 14 '16 at 06:08
  • it's a very high performance desktop gpu AMD radeon 7750 HD with 2 gb of ram and Q9950 processor – Ahmed Fwela Jul 14 '16 at 06:12
  • Your quote looks pretty good. It follows the `MessagePump.Run(form, RenderFrame)` guidelines from https://slimdx.org/tutorials/BasicWindow.php :) –  Jul 14 '16 at 06:12
  • i used vs diagnostics tool to monitor cpu usage and got this http://puu.sh/q188w/de67330158.png don't know if it's useful – Ahmed Fwela Jul 14 '16 at 06:14
  • Using immediate mode APIs per frame are expensive as the instructions have to be uploaded to the GPU each frame before it can draw anything. This makes it GPU inefficient –  Jul 14 '16 at 07:35

2 Answers2

1

The reason for the high CPU is that SlimDX is using PeekMessage rather than the more usual GetMessage (that the majority of Windows apps use). The former does not wait for a message to appear in the message pump unlike the latter. In other words, GetMessage() will block the current thread possibly reducing the CPU load which is what you want a well-behaved Windows desktop application to do.

MSDN:

Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval. Unlike GetMessage, the PeekMessage function does not wait for a message to be posted before returning More...

Now a typical graceful Windows message pump looks like this:

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
} 

...however SlimDX uses what some people refer to as an action game loop:

static bool AppStillIdle
{
    get
    {
        Message msg;
        return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
    }
}

public void MainLoop()
{
    // hook the application's idle event
    Application.Idle += new EventHandler(OnApplicationIdle);
    Application.Run(form);
}

void OnApplicationIdle(object sender, EventArgs e)
{
    while (AppStillIdle)
    {
        // Render a frame during idle time (no messages are waiting)
        RenderFrame();
    }
}

With nothing to draw you will experience a very tight loop of PeekMessage with no waiting in between!

My suggestion is that you either use one of the MessagePump.Run overloads for idle, or add a sleep as per below:

Change this:

void OnApplicationIdle(object sender, EventArgs e)
{
    while (AppStillIdle)
    {
        // Render a frame during idle time (no messages are waiting)
        RenderFrame();
    }
}

...to this:

void OnApplicationIdle(object sender, EventArgs e)
{
    while (AppStillIdle)
    {
        // Render a frame during idle time (no messages are waiting)
        RenderFrame();

        Thread.Sleep(0); // <------------- be graceful
    }
}

Note the use of Thread.Sleep(0). This pauses for the minimal amount of time whilst still allowing the thread to be relinquished to the OS.

MSDN:

The number of milliseconds for which the thread is suspended. If the value of the millisecondsTimeout argument is zero, the thread relinquishes the remainder of its time slice to any thread of equal priority that is ready to run. If there are no other threads of equal priority that are ready to run, execution of the current thread is not suspended.

I see in your answer you already had a Thread.Sleep(50) but now it is good to know why SlimDX requires a Sleep in the first place and that 50 is perhaps too high a value.

GetMessage

OP:

i won't need a very active rendering mechanism, because i will only use this to show overlay from my music player about current song playing , next song , etc

Considering this is the case, the most CPU-efficient means is to replace your action loop with a turn-based game loop using GetMessage() instead of PeekMessage(). Then put your rendering in your application's OnIdle() callback.

As Nick Dandoulakis says in Windows Game Loop 50% CPU on Dual Core:

Nick:

That's a standard game loop for action games, where you must update objects positions / game world. If you are making a board game GetMessage would be a better choice. It really depends on what game you are making. More...

No Sleep() required.

Community
  • 1
  • 1
0

the problem was that i was using full power of cpu even when not rendering, so adding

Thread.Sleep(50);
at the end of the dxThread method lowered it to only enter image description here

Ahmed Fwela
  • 955
  • 1
  • 13
  • 32
  • i won't need a very active rendering mechanism, because i will only use this to show overlay from my music player about current song playing , next song , etc ... – Ahmed Fwela Jul 14 '16 at 07:28
  • That doesn't prove anything. Excessive sleeping is never a good strategy for reducing CPU load. Your sleep of 50 milliseconds is the equivalent time of rendering 3 frames at 60 frames per second when you consider one frame takes 16.6 milliseconds. This equates to a 5% loss in performance. My suggestion on this page would reduce the ~20 % CPU load without sacrificing frames measurably –  Jul 14 '16 at 13:41