Как напрямую рисовать в буфере формы (используя указатели)

#c# #winforms

#c# #winforms

Вопрос:

У меня есть некоторый буфер визуальных данных, который довольно быстро меняется. И мне нужно нарисовать его в форме, со скоростью не менее 60 кадров в секунду.

Во всех других подобных вопросах, которые я нашел, было рекомендовано использовать Bitmap в качестве временного буфера, например:

 
        public unsafe TimeSpan CopyToFormBuffer(float* buff)
        {
            var b = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
            var bd = b.LockBits(this.ClientRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

            //this is more of a GPU work, but for simplicity i put it on CPU:
            var hy = bd.Scan0;
            for (int y = 0; y < this.ClientSize.Height;   y, hy  = bd.Stride)
            {
                var hx = hy;
                for (int x = 0; x < this.ClientSize.Width;   x, hx  = 4, buff  = 4)
                {
                    var p = (byte*)hx;
                    p[3] = Convert.ToByte(buff[0] * 255);
                    p[2] = Convert.ToByte(buff[1] * 255);
                    p[1] = Convert.ToByte(buff[2] * 255);
                    p[0] = Convert.ToByte(buff[3] * 255);
                }
            }

            b.UnlockBits(bd);
            var sw = Stopwatch.StartNew();
            this.CreateGraphics().DrawImage(b, 0, 0);
            sw.Stop();
            return sw.Elapsed;
        }
 
         private unsafe void Form1_Load(object sender, EventArgs e)
        {

            var buff = (float*)Marshal.AllocHGlobal(this.ClientSize.Width * this.ClientSize.Height * 4 * sizeof(float));

            for (var i = 100; i > 0; i--)
                MessageBox.Show(CopyToFormBuffer(buff).ToString());

        }
 

Но таким образом, строка:

             this.CreateGraphics().DrawImage(b, 0, 0);
 

выполняет дополнительную операцию копирования, что недопустимо медленно (80-90 мс на моем ПК в полноэкранном режиме).

Итак, есть ли способ рисовать в буфере формы напрямую, как я делаю с Bitmap ‘s Scan0 ?

Обратите внимание, что, хотя в примере буфер визуальных данных представляет собой просто массив растровых цветов 4xFloat, мне нужно то же самое для более сложных форматов, поэтому в любом случае необходимо выполнить какое-то преобразование цвета.

Комментарии:

1. CreateGraphics плохо. Это this окна Form , на которых вы рисуете? Если это так, вам действительно нужно использовать Paint событие формы и его графический элемент.

2. Чтобы дать нам некоторую перспективу, не могли бы вы предоставить размер растрового изображения, для обработки которого потребовалось 80-90 мс.

3. @TnTinMn 1920×1080

Ответ №1:

Вот реализация того, чего вы пытаетесь достичь, используя событие Forms Paint() . Это будет намного быстрее, чем выделять экранный буфер для каждого кадра. Вы можете изменить буфер пикселей «buff» в любом месте вашей формы, и он будет перерисован в форме. Вам просто нужно вызвать это.Invalidate() для принудительного вызова Paint . Каждый раз, когда вы меняете buff , вызывайте Invalidate в самой форме.

  public partial class Form1 : Form
    {
        private unsafe float* buff;

        public unsafe Form1()
        {
            InitializeComponent();
            buff = (float*)Marshal.AllocHGlobal(this.ClientSize.Width * this.ClientSize.Height * 4 * sizeof(float));
        }

        private unsafe void Form1_Paint(object sender, PaintEventArgs e)
        {
            var sw = Stopwatch.StartNew();

            var b = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
            var bd = b.LockBits(this.ClientRectangle, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

            //this is more of a GPU work, but for simplicity i put it on CPU:
            var hy = bd.Scan0;
            for (int y = 0; y < this.ClientSize.Height;   y, hy  = bd.Stride)
            {
                var hx = hy;
                for (int x = 0; x < this.ClientSize.Width;   x, hx  = 4, buff  = 4)
                {
                    var p = (byte*)hx;
                    p[3] = Convert.ToByte(buff[0] * 255);
                    p[2] = Convert.ToByte(buff[1] * 255);
                    p[1] = Convert.ToByte(buff[2] * 255);
                    p[0] = Convert.ToByte(buff[3] * 255);
                }
            }

            b.UnlockBits(bd);
            e.Graphics.DrawImage(b, 0, 0);
            sw.Stop();

            Debug.WriteLine($"Frame took {sw.Elapsed}ms to draw");
        }
    }
 

Комментарии:

1. Я вижу, что использование CreateGraphics делает это медленнее. Но для выполнения вашей строки пути DrawImage требуется 50-55 мс. Который по-прежнему составляет <20 кадров в секунду и действительно далек от 60 кадров в секунду.

2. Вы выполняете манипуляцию на уровне пикселей, которая равна O (N ^ 2). Это не будет хорошо для процессора, такие вещи нужно делать на графическом процессоре. Вы захотите взглянуть на фреймворки DirectX или OpenGL.

3. для преобразования пикселей я собираюсь использовать OpenCL, но копирование буфера не должно занимать 50 мс для обработки. Buffer.BlockCopy , например, копирует тот же объем байта за 8-9 мс, что приемлемо.

4. О, извините, на самом деле это 2-3 мс, я тестировал на массиве байтов в 4 раза больше.

5. Значит, вы настроены?