(Lviv community of .NET developers)

Render, Arrange и Measure механизмы в WPF

September 14, 2007 07:03 by rat

Есть в WPF пару забавных механизмов, которые не так уж и хорошо описаны в нете - это рендеринг, компоновка и измерение.
Про них и я буду писать некоторые свои познания. (думаю будет полезно почитать особенно тем, кого интересует вопрос производительности програмного обеспечения написанного на WPF)


Вопервых, что это всё такое.
Рендеринг вроде как понятно что такое - берём да и ресуем карандашиком по канве. Ну или так: берём да и рисуем векторное изображение пенами при помощи таких примитивов как линии, круги и тому подобное и заливаем их зафризанными или анимированными брашами. Вроде как и всё. На самом дее не всё, но про это позже.

Теперь про компоновку изображения из нескольких объектов.
Когда рисуется объект, наследуемый от Visual, то никакой компоновки нету - он просто рисуется в OnRender. А вот с FrameworkElement-ами всё намного сложнее. Такие елементы могут рисоваться сами, плюс могут содержать в своём визуальном дереве другие елементы. Так вот о компоновке именно этих элементов и идёт речь.

Стадия компоновки проходит в 2 этапа: на первом этапе просчитываются желаеммые размеры елементов. Тоесть если всё делать саммому, то нужно перекрыть MeasureOverride и в нём обязательно нужно вызвать Measure у всех своих визуальных чайлдов. Значение, которое возвращает MeasureOverride записывается в DesiredSize (конечно же с учётом Margin, Width и Heigth - они могут серъёзно изменить желаеммые значения). Далее елемент уже не сможет занять меньше места чем его DesiredSize, и это нужно учитывать при написании собственных механизмов компоновки.
Изначально этот этап все видимые елементы проходят как минимум один раз на старте - все они опрашиваются на предмет наличия желаемых размеров, что позже может быть учтено при разстановке элементов.
Повторно этот механизм вызывается в случаях если

  1. Вызван InvalidateMeasure
  2. Изменилось значение депенденси проперти (Dependency Property), у которой в метаданных включен флажок AffectsMeasure
  3. Что-либо из перечисленного выше произошло с парентом или выше.

После окончания первого этапа часто для многих элементов начинается второй - разстановка чайлдов внутри елементов. Реализовывается этот механизм путём перекрытия метода ArrangeOverride - именно его реализация должна вызывать методы Arrange для чайлдов и передавать им желаеммые координаы и размеры елементов. Желаемые они потому, что на реальные координаты и размеры могут повлиять несколько факторов, таких например как трансформации, налогаемые на объект, плюс объект может попросту непоместиться в заданные размеры - тогда он займёт столько места сколько ему надо.
Вот некоторые из ньюансов работы арренджа - повторный вызов метода Arrange с теми же координатами и размерами будет игнорироваться; если в очереди стоит Measure елемента, то при аррендже елемент заодно и померяется, и наоборот - если происходит процесс измерения, и в результате сообщаются размеры, отличные от предыдущего значения DesiredSize, то в очередь ставится реаррендж елемента; если в результате арренджа у елемента поменялись размеры, то в очередь ставится его прорисовка, потому если вы двигаете елемент по всей площади его парента не меняя размеров елемента, то он не будет перерисовывать свой контент (точнее не будет вызываться метод OnRender и будет использоваться прокешированный векторный рисунок).

Причины по которым вызывается ArrangeOverride могут быть такими:

  1. Парент в процессе своего арренджа вызвал елементу Arrange и передал ему Rect, отличный от прежнего. Причём именно любые координаты, а не только размеры - какова причина такого подхода я без руля.
  2. Изменилось значение депенденси проперти (Dependency Property), у которой в метаданных включен флажок AffectsArrange.
  3. Изменились размеры елемента в следствии вызова его межера.
  4. Вызван InvalidateArrange

Ну и после всего этого как раз прорисовка и происходит.
Вся прорисовка состоит из добавления в очередь примитивных команд для рисования векторной графики, таких как прорисовка линии, применение трансформации, отмена предыдущей трансформации, и т.д. За каждым елементом закрепляется его копия такого "скрипта", по которому елемент рисуется, потому все перемещения елемента без смены размеров, а так же любые трансормации объекта при помощи RenderTransform происходят без надобности в вызове метода OnRender. По той же причине не приходится заново проходить по OnRender елемента если у него изменился контент браша (но референс на него остался прежним), используемый при его прорисовке - браши передаются в подсистему прорисовки отдельно от списка команд, и все образения к ним идёт по референсу.

Вот некоторые из причин по которым OnRender может вызываться повторно:

  1. Изменились размеры елемента.
  2. Вызван InvalidateVisual
  3. Изменилось значение депенденси проперти (Dependency Property), у которой в метаданных включен флажок AffectsRender.

Кстати сама прорисовка на экран происходит уже после окончания компоновки и всех рендерингов в OnRender-ах, при этом многие комманды прорисовки транслируются в команды для подсисмемы DirectX, расторные изображения транслируются в текстуры и кешируются в видеопамяти, так же как копии многих визуальных елементов - это часто даёт неплохой прирост производительности, а иногда это просто необходимо, как в случае с установкой прозрачности, или маски прозрачности для елементов. Формирование и обработку некоторых текстур всё же приходится перенимать на себя подсистеме програмной прорисовки, как например в случае с применением эффектов - сначала видеокарта рисует елемент в заранее подготовленный буффер, после чего его тянут в оперативную память, там он попиксельно обрабатывается, и после этого обратно отправляется в видеопамять. Потому один из лутших способов проявить чудеса торможения ГУИ - это проанимировать параметры какого-либо еффекта, который вы имели счастье наложить на крупный объект, например размер тени на окне с полностью переделанной прорисовкой :)
После того как окно полностью прорисованно видеокартой в буффер, его отображение кешируется в виде одной текстуры, и при дальнейших перерисовках уже используется прокешированное изображение(конечно если изображение статическое и не меняется). Из-за такого подхода на некоторых старых (а иногда даже и на не совсем старых) видеокартах размер окна не может быть больше определённого размера (часто это около 1 млн точек если смотреть по площади) - тогда как повезёт (закономерность не всегда получалось проследить) - или же окно обрезается, или прорисовка переходит полностью в програмный режим.

Надеюсь эта куча текста кому-то интересна и полезна, позже постараюсь по возможности дополнить данную статью ещё чем-то интересным, а так же опубликовать её английский вариант. 


Currently rated 4.8 by 4 people

  • Currently 4.75/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags: , ,
Categories: Performance | WinFx
Actions: Permalink | Comments (2) | RSSRSS comment feed

Comments

July 24. 2010 13:53

Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with more information? It is extremely helpful for me.

Credit Suisse Gold Bars

July 26. 2010 00:47

A business that makes nothing but money is a poor kind of business.

cash loan 1500

Add comment


(Will show your Gravatar icon)