GDI сейчас под Windows самый древний API и тем не менее его используют даже для рендеринга сложной графики. Недавно я делал графический эффект волны на GDI, и мне нужен был быстрый способ изменять растр. Быстроту я измерял через AQTime, ну и визуально. Идеологически для этого берут сам растр и копируют в память, далее по этой памяти "шарятся" применяя некий алгоритм, и потом возвращают обратно эту память на контекст. Это обычно происходит по таймеру. Если брать простой случай - контекст - это экран, а источник - bmp, то есть несколько способов работы с памятью растра:
1. Самый неэффективный по скорости способ (если операций много): GetPixel/SetPixel; О причине их медленной работы можно прочитать в книге Фень Юань "Программирование графики для Windows" глава 7.
2. GetBitmapBits, SetBitmapBits; Во-первых эти функции уже устаревшие (только для совместимости остались), и работают только с DDB. Во-вторых, для рендеринга через эти функции вам понадобится уже как минимум 2 копирования растра - для взятия буфера на изменения и его установку назад.
3. Альтернатива предыдущим - GetDIBits, SetDIBits. Они копируют растр между форматами DIB и DDB. Для рендеринга на экран это неоправданно сложно, т.к. конвертация нам не нужна, плюс сложность работы с DIB. Опять же - как минимум 2 копирования растра + возможны расходы на конвертацию. Так же учтите, что для DIB нет поддержки GDI в рисовании. DIB растр хранится в памяти на уровне приложения (размер картинки ограничивается только виртуальным адресным пространством приложения) и вы имеете прямой доступ к представлению. В DDB вы не имеете прямого доступа к представлению зато можете рисовать через GDI, растры хранится в системной памяти и поэтому для них еще есть и ограничения по размеру (48MB - примерно 3500х3500 при 32bit цвете).
4. Ввиду выше описанных способов были придуманы DIB-секции. Это DIB-растр в который можно писать как через GDI так и напрямую в буфер. Отсюда выгода - при работе с буфером не нужно копирование вообще.
Также хочу дать еще несколько советов по рисованию/рендерингу в GDI.
1. Используйте концепцию двойной (или более) буферизации перед выводом на экран (CreateCompatibleDC, в WTL есть CMemDC, и т.п.).
2. Рисуйте только один раз - не рисуйте в одном и том же пикселе дважды.
3. Не используйте WM_ERASEBKGND.
4. Используйте GetUpdateRgn, GetUpdateRect или BeginPaint/EndPaint - если перерисовать нужно только малую часть.
5. Стили CS_VREDRAW и СS_HREDRAW означают что содержимое окна будет полностью перерисовываться при изменении его размеров по вертикали или по горизонтали соответственно.
6. Стиль WS_CLIPCHILDREN - чтобы не было при рисовании своего содержимого, затирания дочерних окон своим изображением, а затем поверх него рисования самих дочерних окон.
7. Используйте ExcludeClipRect/SelectClipRgn - для предотвращения повторного рисования.
8. В рендерере используйте таблицы синусов/косинусов, вместо функций - макросы, все локальные переменные выносите выше, используйте многопоточность для сложных алгоритмов.
9. Используйте списки сохранения промежуточных сжатий изображений при ресайзе - для ускорения следующих ресайзов относительно них. Также полезен пассивный предресайзинг.
10. Выделяйте на стеке обработчика WM_PAINT/WM_TIMER поменьше памяти, в идеале вообще не выделять или использовать повторно.
Пока всё. Надеюсь все написал что хотел.
No comments:
Post a Comment