В этом посте рассказано о том, как я использовал двойную буферизацию в простом графическом редакторе, написанном на WTL. Приложение позволяет создавать и редактировать холст с тремя простейшими фигурами — прямоугольником, треугольником и эллипсом. Оно использует GDI+ для рисования фигур, а его исходный код выложен на github.
Даже при двойной буферизации останется неприятное явление под названием тиринг: система по-прежнему выводит картинку на монитор когда ей вздумается, и на экране возникают разрывы — половина экрана уже заполнена новой картинкой, вторая половина ещё держит старую, а где-то посреди экрана лежит резкая граница между частями двух разных кадров.
Эта статья не про борьбу с тирингом, а про двойную буферизацию ради устранения мерцания (flickering); тиринг пускай ещё поживёт.
В свой класс добавьте новое поле для хранения теневого буфера
Мерцание и тиринг
Вывод графики в типичном Windows-приложении, написанном на C++ с MFC или WTL, реализован очень грубо:- Системные и пользовательские контролы, а также пользовательская графика рисуются командами очень-очень древнего GDI или просто древнего GDI+
- В качестве холста для рисования выступает клиентская часть окна приложения или дочерние окна-контролы
- Команды рисования выполняются в immediate mode — безотлагательно, и изменения в формируемом изображении появляются сразу же. Обычно изображение сначала очищается, а затем заполняется текстом, картинками и геометрическими фигурами.
- Операционная система выводит картинку на монитор тогда, когда сочтёт нужным, даже если в этот момент рисунок из предыдущего кадра очищен, а новый нарисован ещё не полностью.
Даже при двойной буферизации останется неприятное явление под названием тиринг: система по-прежнему выводит картинку на монитор когда ей вздумается, и на экране возникают разрывы — половина экрана уже заполнена новой картинкой, вторая половина ещё держит старую, а где-то посреди экрана лежит резкая граница между частями двух разных кадров.
Эта статья не про борьбу с тирингом, а про двойную буферизацию ради устранения мерцания (flickering); тиринг пускай ещё поживёт.
Двойная буферизация средствами GDI+
Теневой буфер для рисования реализуется классом BackBuffer. Вот файл BackBuffer.h:class CBackBuffer { public: CBackBuffer(); ~CBackBuffer(); std::unique_ptr<gdiplus::graphics> StartRender(); void SetSize(const Gdiplus::Size &size); Gdiplus::Bitmap *GetBitmap() const; private: Gdiplus::Size m_size; std::unique_ptr<gdiplus::bitmap> m_bitmap; };А вот BackBuffer.cpp:
CBackBuffer::CBackBuffer() = default; CBackBuffer::~CBackBuffer() = default; std::unique_ptr<gdiplus::graphics> CBackBuffer::StartRender() { if (!m_bitmap) { m_bitmap.reset(new Gdiplus::Bitmap(m_size.Width, m_size.Height)); } const Gdiplus::Color WHITE_COLOR(0xff, 0xff, 0xff); std::unique_ptr<gdiplus::graphics> ret{ Gdiplus::Graphics::FromImage(m_bitmap.get()) }; ret->Clear(WHITE_COLOR); return ret; } void CBackBuffer::SetSize(const Gdiplus::Size &size) { if (m_size.Width != size.Width || m_size.Height != size.Height) { m_size = size; m_bitmap.reset(); } } Gdiplus::Bitmap *CBackBuffer::GetBitmap() const { return m_bitmap.get(); }
std::unique_ptrЗатем инициализируйте его в конструкторе MyView или в обработчике события WM_CREATE:m_backBuffer;
LRESULT MyGraphicsView::OnCreate(UINT, WPARAM, LPARAM, BOOL&) { [...] m_backBuffer.reset(new CBackBuffer); return 0; }И не забудьте установить размер теневого буфера при событии WM_SIZE:
LRESULT MyGraphicsView::OnResize(UINT, WPARAM, LPARAM lParam, BOOL&) { const int width = LOWORD(lParam); const int height = HIWORD(lParam); m_backBuffer->SetSize(Gdiplus::Size(width, height)); return 0; }Затем в обработчике WM_PAINT вы создаёте объект Gdiplus::Graphics для вывода в теневой буфер, рисуете всю графику и в конце выводите полученный Bitmap на экран:
LRESULT MyGraphicsView::OnPaint(UINT, WPARAM, LPARAM, BOOL&) { std::unique_ptr<gdiplus::graphics> backGraphics{ m_backBuffer->StartRender() }; backGraphics->SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); // [...] Draw content to backGraphics CPaintDC dc(m_hWnd); std::unique_ptr<gdiplus::graphics> mainGraphics{ Gdiplus::Graphics::FromHDC(dc.m_hDC) }; mainGraphics->DrawImage(m_backBuffer->GetBitmap(), 0, 0); mainGraphics.reset(); return 0; }
Комментариев нет:
Отправить комментарий