有没有一种快速的方法来操作和缓冲Windows窗体中的屏幕?

时间:2015-08-07 09:50:36

标签: c# winforms buffer

我正在开发一款用于学习目的的游戏,我想只使用.NET-Framework和C#中的Windows窗体项目。

我希望将{screen'(可以在窗口上显示的内容)作为int[]。修改数组并以缓冲方式将更改后的数组重新应用到“屏幕”(这样它就不会闪烁)。

我目前正在使用Panel,我使用Bitmap绘制GraphicsBitmap转换为int[]然后我可以修改并重新应用Bitmap并重新绘制。它很有效,但速度很慢,特别是因为我必须每帧都放大图像因为我的游戏只有300x160而屏幕是900x500。

建立:

    // Renders 1 frame
    private void Render()
    {    
        // Buffer setup
        _bufferedContext = BufferedGraphicsManager.Current;
        _buffer = _bufferedContext.Allocate(panel_canvas.CreateGraphics(), new Rectangle(0, 0, _scaledWidth, _scaledHeight));

        _screen.clear();

        // Get position of player on map
        _xScroll = _player._xMap - _screen._width / 2;
        _yScroll = _player._yMap - _screen._height / 2;

        // Indirectly modifies the int[] '_pixels'
        _level.render(_xScroll, _yScroll, _screen);
        _player.render(_screen);

        // Converts the int[] into a Bitmap (unsafe method is faster)
        unsafe
        {
            fixed (int* intPtr = &_screen._pixels[0])
            {
                _screenImage = new Bitmap(_trueWidth, _trueHeight, _trueWidth * 4, PixelFormat.Format32bppRgb, new IntPtr(intPtr));
            }
        }

        // Draw generated image on buffer
        Graphics g = _buffer.Graphics;
        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

        g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506));

        // Update panel buffered
        _buffer.Render();
    }

没有外部库可以更快地实现这项工作吗?

3 个答案:

答案 0 :(得分:1)

我不确定不安全的代码,但我知道缓冲的图形管理器。我认为你应该为它创建一个类,而不是每次都创建一个新类。除了在负载上确定所有精灵的宽度和高度而不是缩放它们。这加快了我的小型游戏引擎。

class Spritebatch
{
    private Graphics Gfx;
    private BufferedGraphics bfgfx;
    private BufferedGraphicsContext cntxt = BufferedGraphicsManager.Current;

    public Spritebatch(Size clientsize, Graphics gfx)
    {
        cntxt.MaximumBuffer = new Size(clientsize.Width + 1, clientsize.Height + 1);
        bfgfx = cntxt.Allocate(gfx, new Rectangle(Point.Empty, clientsize));
        Gfx = gfx;
    }

    public void Begin()
    {
        bfgfx.Graphics.Clear(Color.Black);
    }

    public void Draw(Sprite s)
    {
        bfgfx.Graphics.DrawImageUnscaled(s.Texture, new Rectangle(s.toRec.X - s.rotationOffset.Width,s.toRec.Y - s.rotationOffset.Height,s.toRec.Width,s.toRec.Height));
    }

    public void drawImage(Bitmap b, Rectangle rec)
    {
        bfgfx.Graphics.DrawImageUnscaled(b, rec);
    }

    public void drawImageClipped(Bitmap b, Rectangle rec)
    {
        bfgfx.Graphics.DrawImageUnscaledAndClipped(b, rec);
    }

    public void drawRectangle(Pen p, Rectangle rec)
    {
        bfgfx.Graphics.DrawRectangle(p, rec);
    }

    public void End()
    {
        bfgfx.Render(Gfx);
    }

}

这是我使用的一个例子。它的设置是为了模仿Xna中的Spritebatch。绘制图像Unscaled将真正提高它的速度。另外,创建一个缓冲图形和Context的实例将比每次渲染时创建一个新实例更快。所以我建议你改变g.DrawImage行(_screenImage,new Rectangle(0,0,900,506)); to DrawImageUnscaled(_screenImage,new Rectangle(0,0,900,506));

已编辑:如何在精灵加载上缩放代码的示例

    public Sprite(Bitmap texture, float x, float y, int width, int height)
    {
        //texture is the image you originally start with.

        Bitmap b = new Bitmap(width, height);
        // Create a bitmap with the desired width and height
        using (Graphics g = Graphics.FromImage(b))
        {
            g.DrawImage(texture, 0, 0, width, height);
        }
        // get the graphics from the new image and draw the old image to it
        //scaling it to the proper width and height
        Texture = b;
        //set Texture which is the final picture to the sprite.
        //Uppercase Texture is different from lowercase

答案 1 :(得分:0)

即使在没有任何插值的情况下完成,图像的缩放也足够昂贵。为了加快速度,你应该最小化内存分配:当你每帧创建全新的Bitmap时,它会导致对象创建和pixmap缓冲区分配。这一事实否定了您从BufferedGraphics获得的所有好处。我建议你做以下事情:

  1. 在Render方法之外只创建一次所需大小(等于屏幕大小)的Bitmap实例。
  2. 通过LockBits方法直接访问位图数据,并尝试使用最近的像素实现缩放。
  3. 当然,使用某种硬件加速进行缩放操作是最优选的选项(例如,在opengl中,所有图像通常使用纹理矩形绘制,并且渲染此类矩形隐含地涉及"缩放&##的过程34;执行纹理采样时。)

答案 2 :(得分:0)

我想知道你为什么称之为“非常慢”,因为我做了一些测试并且性能似乎并不差。另外,您还要将渲染代码的性能测量到int[] '_pixels'(遗憾的是您没有提供该代码)与位图操作分开,因为它可能是缓慢的部分。
关于你的具体问题。正如其他人所提到的,使用预先分配的缓冲图形和位图对象会加快它的速度 但你真的需要那个int[]缓冲区吗? BufferedGraphics已经在内部使用位图进行了支持,所以真正发生的是:

(1)填写int[]缓冲区
(2)int[]缓冲区被复制到新的/预分配的Bitmap
(3)将步骤2中的Bitmap复制(应用比例)到BufferedGraphics内部位图(通过DrawImage
(4)将BufferedGraphics内部位图复制到屏幕上(通过Render

如您所见,有很多复制操作。 BufferedGraphics的预期用途是:

(1)您通过BufferedGraphics属性的绘图方法填充BufferedGraphics.Graphics内部位图。如果设置,Graphics将为您进行缩放(以及其他转换) (2)将BufferedGraphics内部位图复制到屏幕上(通过Render

我不知道您的绘图代码在做什么,但如果您能负担得起,这肯定应该提供最佳性能。

以下是我感兴趣的快速而肮脏的测试:

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;

namespace Test
{
    enum RenderMode { NewBitmap, PreallocatedBitmap, Graphics }
    class Screen
    {
        Control canvas;
        public Rectangle area;
        int[,] pixels;
        BitmapData info;
        Bitmap bitmap;
        BufferedGraphics buffer;
        float scaleX, scaleY;
        public RenderMode mode = RenderMode.NewBitmap;
        public Screen(Control canvas, Size size)
        {
            this.canvas = canvas;
            var bounds = canvas.DisplayRectangle;
            scaleX = (float)bounds.Width / size.Width;
            scaleY = (float)bounds.Height / size.Height;
            area.Size = size;
            info = new BitmapData { Width = size.Width, Height = size.Height, PixelFormat = PixelFormat.Format32bppRgb, Stride = size.Width * 4 };
            pixels = new int[size.Height, size.Width];
            bitmap = new Bitmap(size.Width, size.Height, info.PixelFormat);
            buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds);
            buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
            ApplyMode();
        }
        public void ApplyMode()
        {
            buffer.Graphics.ResetTransform();
            if (mode == RenderMode.Graphics)
                buffer.Graphics.ScaleTransform(scaleX, scaleY);
        }
        public void FillRectangle(Color color, Rectangle rect)
        {
            if (mode == RenderMode.Graphics)
            {
                using (var brush = new SolidBrush(color))
                    buffer.Graphics.FillRectangle(brush, rect);
            }
            else
            {
                rect.Intersect(area);
                if (rect.IsEmpty) return;
                int colorData = color.ToArgb();
                var pixels = this.pixels;
                for (int y = rect.Y; y < rect.Bottom; y++)
                    for (int x = rect.X; x < rect.Right; x++)
                        pixels[y, x] = colorData;
            }
        }
        public unsafe void Render()
        {
            if (mode == RenderMode.NewBitmap)
            {
                var bounds = canvas.DisplayRectangle;
                using (var buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds))
                {
                    Bitmap bitmap;
                    fixed (int* pixels = &this.pixels[0, 0])
                        bitmap = new Bitmap(info.Width, info.Height, info.Stride, info.PixelFormat, new IntPtr(pixels));
                    buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
                    buffer.Graphics.DrawImage(bitmap, bounds);
                    buffer.Render();
                }
            }
            else
            {
                if (mode == RenderMode.PreallocatedBitmap)
                {
                    fixed (int* pixels = &this.pixels[0, 0])
                    {
                        info.Scan0 = new IntPtr(pixels); info.Reserved = 0;
                        bitmap.LockBits(area, ImageLockMode.WriteOnly | ImageLockMode.UserInputBuffer, info.PixelFormat, info);
                        bitmap.UnlockBits(info);
                    }
                    buffer.Graphics.DrawImage(bitmap, canvas.DisplayRectangle);
                }
                buffer.Render();
            }
        }
    }
    class Game
    {
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var game = new Game();
            game.Run();
        }
        Form form;
        Control canvas;
        Screen screen;
        Level level;
        Player player;
        private Game()
        {
            form = new Form();
            canvas = new Control { Parent = form, Bounds = new Rectangle(0, 0, 900, 506) };
            form.ClientSize = canvas.Size;
            screen = new Screen(canvas, new Size(300, 160));
            level = new Level { game = this };
            player = new Player { game = this };
        }
        private void Run()
        {
            bool toggleModeRequest = false;
            canvas.MouseClick += (sender, e) => toggleModeRequest = true;
            var worker = new Thread(() =>
            {
                int frameCount = 0;
                Stopwatch drawT = new Stopwatch(), applyT = new Stopwatch(), advanceT = Stopwatch.StartNew(), renderT = Stopwatch.StartNew(), infoT = Stopwatch.StartNew();
                while (true)
                {
                    if (advanceT.ElapsedMilliseconds >= 3)
                    {
                        level.Advance(); player.Advance();
                        advanceT.Restart();
                    }
                    if (renderT.ElapsedMilliseconds >= 8)
                    {
                        frameCount++;
                        drawT.Start(); level.Render(); player.Render(); drawT.Stop();
                        applyT.Start(); screen.Render(); applyT.Stop();
                        renderT.Restart();
                    }
                    if (infoT.ElapsedMilliseconds >= 1000)
                    {
                        double drawS = drawT.ElapsedMilliseconds / 1000.0, applyS = applyT.ElapsedMilliseconds / 1000.0, totalS = drawS + applyS;
                        var info = string.Format("Render using {0} - Frames:{1:n0} FPS:{2:n0} Draw:{3:p2} Apply:{4:p2}",
                            screen.mode, frameCount, frameCount / totalS, drawS / totalS, applyS / totalS);
                        form.BeginInvoke(new Action(() => form.Text = info));
                        infoT.Restart();
                    }
                    if (toggleModeRequest)
                    {
                        toggleModeRequest = false;
                        screen.mode = (RenderMode)(((int)screen.mode + 1) % 3);
                        screen.ApplyMode();
                        frameCount = 0; drawT.Reset(); applyT.Reset();
                    }
                }
            });
            worker.IsBackground = true;
            worker.Start();
            Application.Run(form);
        }
        class Level
        {
            public Game game;
            public int pos = 0; bool right = true;
            public void Advance() { Game.Advance(ref pos, ref right, 0, game.screen.area.Right - 1); }
            public void Render()
            {
                game.screen.FillRectangle(Color.SaddleBrown, new Rectangle(0, 0, pos, game.screen.area.Height));
                game.screen.FillRectangle(Color.DarkGreen, new Rectangle(pos, 0, game.screen.area.Right, game.screen.area.Height));
            }
        }
        class Player
        {
            public Game game;
            public int x = 0, y = 0;
            public bool right = true, down = true;
            public void Advance()
            {
                Game.Advance(ref x, ref right, game.level.pos, game.screen.area.Right - 5, 2);
                Game.Advance(ref y, ref down, 0, game.screen.area.Bottom - 1, 2);
            }
            public void Render() { game.screen.FillRectangle(Color.Yellow, new Rectangle(x, y, 4, 4)); }
        }
        static void Advance(ref int pos, ref bool forward, int minPos, int maxPos, int delta = 1)
        {
            if (forward) { pos += delta; if (pos < minPos) pos = minPos; else if (pos > maxPos) { pos = maxPos; forward = false; } }
            else { pos -= delta; if (pos > maxPos) pos = maxPos; else if (pos < minPos) { pos = minPos; forward = true; } }
        }
    }
}