Как реализовать создание 500 кадров в секунду в приложении на C# с использованием Windows Forms?

У меня возникла проблема с реализацией обновления кадров на уровне примерно 500 кадров в секунду. В первой попытке я смог добиться максимум 60 кадров, однако во второй реализации, воспользовавшись апдейтом, мне удалось достичь 500-600 кадров, которые требуются. Однако при этом появляется мерцание. Я решил попробовать использовать двойной буфер для формы, но это ограничило меня до 120 кадров в секунду. 

Вот код, который я использую: 
<pre><code>public async void Timer()
{
    while (true)
    {
        //1
      //this.Invalidate(false);
      //await Task.Delay(1);
        
        //2
        //this.Update();
        //this.Invalidate(false);
    }
    
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        managementOverlay.Update(e.Graphics);
    }
}</code></pre>

Как мне устранить мерцание и достичь необходимых 500 кадров в секунду с использованием двойного буфера?
  • 21 ноября 2024 г. 19:33
Ответы на вопрос 2
Чтобы достичь 500 кадров в секунду в приложении на C# с использованием Windows Forms, и при этом избежать мерцания, вам нужно воспользоваться несколькими техниками, включая использование двойного буфера, грамотную обработку таймеров и оптимизацию кода отрисовки.

Вот пример, как можно реализовать эту концепцию:

1. **Включение двойного буфера:** Убедитесь, что ваш класс формы использует двойной буфер. Вы можете сделать это, переопределив метод `OnPaintBackground` и установив `DoubleBuffered` в `true`.

2. **Использование `Stopwatch`:** Вместо `Task.Delay` используйте `Stopwatch` для точного контроля времени между кадрами.

3. **Оптимизация отрисовки:** Убедитесь, что вы рисуете только то, что действительно нужно обновлять.

Вот пример кода, иллюстрирующий эти подходы:

```csharp
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

public class Form1 : Form
{
    private bool running;
    private Stopwatch stopwatch;
    private const int TARGET_FPS = 500;
    private const int TARGET_MILLISECONDS = 1000 / TARGET_FPS;

    public Form1()
    {
        this.DoubleBuffered = true; // активируем двойной буфер
        this.Paint += new PaintEventHandler(Form1_Paint);
    }

    public void StartTimer()
    {
        running = true;
        stopwatch = new Stopwatch();
        
        Task.Run(() =>
        {
            while (running)
            {
                stopwatch.Restart();
                this.Invalidate(); // запрашиваем перерисовку
                // Можно учитывать другие расчеты тут

                // Ждать для достижения цели FPS
                var elapsed = stopwatch.ElapsedMilliseconds;
                if (elapsed < TARGET_MILLISECONDS)
                {
                    Task.Delay(TARGET_MILLISECONDS - (int)elapsed).Wait();
                }
            }
        });
    }

    public void StopTimer()
    {
        running = false;
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        // Здесь будет ваш код отрисовки. Например:
        Graphics g = e.Graphics;
        // Рисуем что-то на графике, например:
        g.FillRectangle(Brushes.Red, 10, 10, 50, 50);
    }
    
    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        StopTimer();
        base.OnFormClosed(e);
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        // Переопределяем, чтобы предотвратить мерцание
        // Вы можете оставить это пустым или добавлять свою логику для фонового рисования
    }

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Form1 form = new Form1();
        form.StartTimer();
        Application.Run(form);
    }
}
```

### Объяснения:

1. **Двойной буфер:** Устанавливая `this.DoubleBuffered = true;`, мы уменьшаем/устраняем мерцание при отрисовке.

2. **Stopwatch:** Позволяет точно измерить время, прошедшее между кадрами. Это поможет поддерживать высокую производительность, корректно контролируя задержку между кадрами.

3. **Invalidate и перерисовка:** Метод `Invalidate()` запрашивает перерисовку окна приложения, что запускает событие `Paint`. Мы вызываем его в основном цикле, чтобы инициировать отрисовку.

4. **Отмена таймера:** Добавлен метод `StopTimer()` для безопасной остановки цикла рисования при закрытии формы.

Поскольку Windows Forms не предназначен для таких высоких частот обновления и может быть брошен в состоянии гонки при обилие графики, возможно, вам стоит также рассмотреть использование более подходящих для игр технологий, таких как WPF или библиотеки, специализированные для рисования на экране.
А вы какую задачу-то решаете? Если смотреть на задачу с общей точки зрения практичности и применимости, то фраза "ну нахер" описывает решение крайне точно. Если вам надо именно чисто в самих формах и именно в шарпе - чисто теоретически на мощной машине с банкой вазелина и другими нецензурными вещами вполне можно достичь такого показателя. Опять же - я говорю не про пустое окно в один пиксель, а про реальное приложение с кучей кнопок, элементов управления и всего остального на 4к экране. Проблема форм в том, что там используется легаси код GDI+ и в современных виндах там еще поверх чего-то накрутили, плюс еще всякое накручено в формах и дотнете и как результат - можно получить лаги на банальных дропбоксах в сотню элементов. Если же вам нужно получить именно результат - окно с требуемым фрейм-рейтом, то тут уже есть более практичные варианты: делаем стандартное окно и выводим в него нужное на сях или плюсах прямо в пиксели или через OpenGL для использования ресурсов видеоадаптера).
Похожие вопросы