如何在处理slimdx时降低cpu影响

时间:2016-07-14 05:19:23

标签: c# directx-9 slimdx

我正在创建一个覆盖其他d3d游戏的应用程序,
该应用程序工作正常,除了它对CPU产生巨大的性能影响 enter image description here
只渲染一行时占用cpu的21.4%! 我在c#上使用slimdx库,这是我的完整代码

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())
            {

            }
        }
    }
}

请注意:
我已经看到了这个:Very high CPU usage directx 9

但我使用MessagePump.Run并且不知道如何应用答案。

2 个答案:

答案 0 :(得分:1)

高CPU的原因是SlimDX使用PeekMessage而不是更常见的GetMessage(大多数Windows应用程序使用)。前者不等待消息显示在消息泵中,与后者不同。换句话说,GetMessage()阻止当前线程可能减少CPU负载,这是您希望表现良好的Windows桌面应用程序执行的操作。

MSDN:

  

从调用线程的消息队列中检索消息。该函数调度传入的已发送消息,直到发布的消息可供检索。   与GetMessage不同,PeekMessage函数不会在返回More...

之前等待发布消息

现在,典型的优雅Windows消息泵如下所示:

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

...但是SlimDX使用了一些人称为动作游戏循环的内容:

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();
    }
}

没有任何东西可以吸引你,你会经历一个非常紧密的PeekMessage循环而没有等待!

我的建议是你要么使用其中一个MessagePump.Run重载进行空闲,要么按照以下方式添加一个睡眠:

改变这个:

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

......对此:

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

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

请注意Thread.Sleep(0)的使用。这暂停了最少的时间,同时仍允许将线程放弃到操作系统。

MSDN:

  

线程被挂起的毫秒数。如果millisecondsTimeout参数的值为,则线程将其剩余的时间片放弃到准备运行的任何具有相同优先级的线程。如果没有其他具有相同优先级的线程准备好运行,则不会暂停执行当前线程。

我在你的回答中看到你已经有了Thread.Sleep(50),但现在很高兴知道为什么SlimDX首先要求Sleep并且50可能值太高了

的GetMessage

OP:

  

我不需要非常活跃的渲染机制,因为我只会使用它来显示我的音乐播放器关于当前歌曲播放,下一首歌等的叠加效果

考虑到这种情况,最节省CPU的方法是使用GetMessage()而不是PeekMessage()将操作循环替换为回合制游戏循环。然后将渲染放入应用程序的OnIdle()回调中。

Nick Dandoulakis中的says Windows Game Loop 50% CPU on Dual Core

尼克:

  

这是动作游戏的标准游戏循环,您必须更新对象位置/游戏世界。   如果您正在制作棋盘游戏 GetMessage将是更好的选择。   这真的取决于你正在制作什么游戏。 More...

不需要Sleep()

答案 1 :(得分:-1)

问题是即使不渲染我也使用了cpu的全部功能,所以添加

Thread.Sleep(50);
在dxThread方法结束时将其降低到仅 enter image description here