C# 电脑游戏的简单快速实时图形 (WinForms)

Simple and fast real-time graphics for C# computer game (WinForms)

作为 C# winforms 项目的一部分,我试图使整数数组和位图协调工作,以实现快速 PictureBox 编辑(避免缓慢的 SetPixel 命令)。

我添加了一个 Button 和一个 PictureBox,在上述 Button 上添加了一个点击事件,在 Form 上添加了一个关闭事件。

表单的代码现在是这样的:

public partial class Form1 : Form
{
    uint[] _Pixels { get; set; }

    Bitmap _Bitmap { get; set; }

    GCHandle _Handle { get; set; }

    IntPtr _Addr { get; set; }


    public Form1()
    {
        InitializeComponent();

        int imageWidth = 100; //1920;

        int imageHeight = 100; // 1080;

        PixelFormat fmt = PixelFormat.Format32bppRgb;

        int pixelFormatSize = Image.GetPixelFormatSize(fmt);

        int stride = imageWidth * pixelFormatSize;

        int padding = 32 - (stride % 32);

        if (padding < 32)
        {
            stride += padding;
        }

        _Pixels = new uint[(stride / 32) * imageHeight + 1];

         _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);

        _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);

        _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);

        pictureBox1.Image = _Bitmap;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < _Pixels.Length; i++)
        {
            _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));

        }

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _Addr = IntPtr.Zero;

        if (_Handle.IsAllocated)
        {
            _Handle.Free();

        }

        _Bitmap.Dispose();

        _Bitmap = null;

        _Pixels = null;

    }

}

当运行时,开头出现预期的黑色图像。

单击按钮时图像应该变成白色,但我遗漏了一些东西。

我忘了做什么?

解决方案

更新 _Pixels 数组后添加 pictureBox1.Refresh()

此更新非常快,并且能够在高分辨率下呈现流畅的视频。

添加对 System.Runtime.InteropServices 的引用(可通过 nuget)

表单的代码现在是这样的:

public partial class Form1 : Form
{
    uint[] _Pixels { get; set; }

    Bitmap _Bitmap { get; set; }

    GCHandle _Handle { get; set; }

    IntPtr _Addr { get; set; }


    public Form1()
    {
        InitializeComponent();

        int imageWidth = 100; //1920;

        int imageHeight = 100; // 1080;

        PixelFormat fmt = PixelFormat.Format32bppRgb;

        int pixelFormatSize = Image.GetPixelFormatSize(fmt);

        int stride = imageWidth * pixelFormatSize;

        int padding = 32 - (stride % 32);

        if (padding < 32)
        {
            stride += padding;
        }

        _Pixels = new uint[(stride / 32) * imageHeight + 1];

         _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);

        _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);

        _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);

        pictureBox1.Image = _Bitmap;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < _Pixels.Length; i++)
        {
            _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));

        }

        pictureBox1.Refresh();

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _Addr = IntPtr.Zero;

        if (_Handle.IsAllocated)
        {
            _Handle.Free();

        }

        _Bitmap.Dispose();

        _Bitmap = null;

        _Pixels = null;

    }

}

测试

当然,我想测试一下这种方法的性能,看看它是否足够快来渲染流畅的视频。

用计时器滴答替换按钮点击

到目前为止,我的测试从一开始就很简单。我用每毫秒触发一次的 timer tick event 替换了 button click event,然后用随机颜色值填充 _Pixels 数组。

为了确保 PictureBox 不会在任何当前刷新完成之前尝试刷新,我使用了一个 bool IsRefreshing 变量。

每秒计算帧数

我还测量了每次刷新之间经过的时间,方法是递增一个 int HzCount 变量,并在每次 PictureBox 刷新开始时将该变量重置为零。就在我重置 HzCount* 之前,我在 TextBox.

中显示该值

Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz 2.60GHz 16.0 GB RAM 结果:

72Hz@2560x1440

*Hertz 或 Hz 表示每秒经过的帧数(或多少次)。推而广之,这意味着 MegaHertz 转换为每秒数百万次,而 GigaHertz 转换为每秒千万次(美国十亿)次。

WinForms 控件不会重绘自己 "just because",它们必须有这样做的理由。

一些数据源能够告诉它们的容器它们已经更新。位图 class 没有。它无法告诉包含 PictureBox(或您用来显示它的任何控件)它已更新。当您调用 SetPixel() 来设置单个像素,或在操作 BitmapData 实例后调用 UnlockBits() 时,它可以,但是当位图是使用您完全操作的固定数组构造时,它不能无论如何控制位图class。

因此位图 class 没有事件或其他方式来通知其容器更新。

这意味着您需要告诉包含控件其数据源已更新,以便控件可以重绘自身。

您可以按照 How to refresh PictureBox 中的说明进行操作,即使用 pictureBox.Refresh()。这会导致 PictureBox 控件自身失效,并在下一次(立即)重绘时重新读取现在更改的位图数据。

另见 MSDN Blogs: Whats the difference between Control.Invalidate, Control.Update and Control.Refresh?