Улучшение производительности DrawingContext рендеринга геометрии (полигоны и полилинии)

Это мой первый вопрос, однако я давно скрываюсь. Я разделю это на две части, одна из которых объясняет, что я делаю, и почему я думаю, что это правильный путь, а вторая - актуальный вопрос, который я не могу решить для себя.

Что я делаю? В настоящее время я разрабатываю платформу для рендеринга двухмерных объектов, предназначенных для отображения в режиме реального времени. Вы можете подумать о приложении, подобном Google Maps, в своем браузере, однако эта платформа предназначена для отображения всех видов географических данных (не только растровых данных с выравниванием по оси, как, например, Google Tiles).

Фреймворк должен быть интегрирован в наш новейший продукт компании (WPF) для настольных компьютеров и ноутбуков.

Поэтому я выбрал WPF только для рендеринга геометрии; Выборку видимости и окклюзии выполняю сам, а также обработку ввода (выбор мыши), перемещение камеры и т. Д.

Будучи приложением в режиме реального времени, оно должно достигать не менее 30 FPS. Фреймворк работает адекватно при рендеринге изображений: я могу нарисовать несколько тысяч растровых изображений на кадр без проблем, однако многоугольные данные оказываются серьезной проблемой.

Фактический вопрос: я отрисовываю изрядное количество данных полилинии и многоугольника, используя WPF, в частности, используя DrawingContext и StreamGeometry. Насколько я понимаю, до сих пор таков путь, если мне нужна производительность. Однако я не могу добиться ожидаемых результатов от этого.

Вот как я заполняю StreamGeometry фактическими данными:

using (StreamGeometryContext ctx = Geometry.Open())
{
        foreach (var segment in segments)
    {
        var first = ToWpf(segment[0]);
        ctx.BeginFigure(first, false, false);

        // Skip the first point, obviously
        List<Point> points = segment.Skip(1).Select(ToWpf).ToList();
        ctx.PolyLineTo(points, true, false);
    }
}
    Geometry.Freeze();

И вот как я рисую свою геометрию:

_dc.PushTransform(_mercatorToView);
_dc.DrawGeometry(null, _pen, polyline);
_dc.Pop();

В качестве теста я загрузил формы ESRI из OpenStreetMap в свое приложение, чтобы проверить его производительность, однако я совсем не удовлетворен: мои тестовые данные состоят из ~3500 линейных сегментов с общим количеством ~20 тыс. Строк.

Сопоставление каждого сегмента с его собственной StreamGeometry выполнялось крайне плохо, но я ожидал, что уже: рендеринг занимает около 14 секунд.

Затем я попытался упаковать больше сегментов в одну и ту же StreamGeometry, используя несколько фигур: 80 StreamGeometry, рендеринг занимает около 50 мс.

Однако я не могу получить лучшие результаты, чем этот. Увеличение количества строк примерно до 100 тыс. Делает мое приложение практически непригодным: рендеринг занимает более 100 мс. Что еще я могу сделать, кроме замораживания геометрии и пера при рендеринге векторных данных?

Сейчас я предпочитаю использовать DirectX сам, чем полагаться на WPF, потому что что-то идет ужасно неправильно.

редактировать

Чтобы уточнить, что я делаю: Приложение визуализирует географические данные в режиме реального времени, очень похоже на приложение, такое как Google Maps, в браузере: однако оно должно визуализировать намного больше данных. Как вы, возможно, знаете, Карты Google позволяют выполнять масштабирование и панорамирование, для чего требуется более 25 кадров в секунду, чтобы он отображался как плавная анимация; что-то меньшее не чувствует себя свободно.

* Извините, но я не должен загружать видео об этом до выпуска самого продукта. Тем не менее, вы можете представить что-то вроде Google Maps, однако с тоннами векторных данных (полигоны и полилинии). *

Есть два решения, одно из которых очень часто заявляется:

Кэшировать тяжелые рисунки в растровое изображение

Реализация кажется довольно простой, однако я вижу некоторые проблемы с этим подходом: чтобы правильно реализовать панорамирование, мне нужно избегать рисования тяжелого материала в каждом кадре, и поэтому у меня есть выбор: не обновлять кэшированное растровое изображение во время панорамирования камера, или создание растрового изображения, которое покрывает еще большую область, чем область просмотра, так что мне нужно только периодически обновлять кэшированное растровое изображение.

Вторая "проблема" связана с масштабированием. Однако это скорее визуальный артефакт, чем реальная проблема: поскольку кэшированное растровое изображение не может быть должным образом обновлено при 30 FPS, мне нужно избегать этого и при масштабировании. Я могу очень хорошо масштабировать растровое изображение при масштабировании, создавая новое растровое изображение только после завершения масштабирования, однако ширина полилиний не будет иметь постоянной толщины, хотя они должны.

Этот подход, похоже, используется MapInfo, но я не могу сказать, что мне это слишком нравится. Это, кажется, проще всего реализовать, хотя.

Разделить геометрию на различные графические элементы

Этот подход, похоже, решает проблему по-другому. Я не уверен, работает ли этот подход вообще: это зависит от того, правильно ли я понял, как WPF должен работать в этой области. Вместо того, чтобы использовать один DrawingVisual для всего, что нужно рисовать, я должен использовать несколько, так что не каждый должен быть RenderOpened(). Я мог бы просто изменить параметры, например матрицу в примере выше, чтобы отразить как панорамирование, так и перемещение камеры. Однако я вижу некоторые проблемы и с этим подходом: панорамирование камеры неизбежно приведет к появлению новой геометрии в окне просмотра, поэтому мне нужно будет выполнить нечто подобное, чем при первом подходе, фактически визуализировать вещи, которые в данный момент не видны, но могут стать видимыми из-за смещения камеры; Рисовать все не может быть и речи, так как для довольно небольшого количества данных может потребоваться смешное количество раз.

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

Мои мысли до сих пор

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

Я очень рад за помощь, однако не могу сказать, что пока впечатлен производительностью рендеринга WPF.

1 ответ

Чтобы улучшить производительность 2D-рендеринга WPF, вы можете взглянуть на RenderTargetBitmap (для WPF >= 3.5) или класс BitmapCache (для WPF >= 4).

Эти классы используются для Cached Composition

Из MSDN:

Используя новые классы BitmapCache и BitmapCacheBrush, вы можете кэшировать сложную часть визуального дерева в виде растрового изображения и значительно сократить время рендеринга. Растровое изображение остается чувствительным к пользовательскому вводу, такому как щелчки мыши, и вы можете рисовать его на других элементах, как любая кисть.

Другие вопросы по тегам