В этом посте рассказано о том, как я использовал двойную буферизацию в простом графическом редакторе, написанном на 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;
}
Комментариев нет:
Отправить комментарий