Доброе утро,
Я немного учился программированию на Direct2D на C #, используя доступные доступные обертки (в настоящее время использую d2dSharp, но также пробовал SharpDX). Однако у меня возникают проблемы с эффективностью, когда базовые методы рисования Direct2D рисуют приблизительно 250 мс, чтобы нарисовать 45 000 основных полигонов. Производительность, которую я вижу, находится на одном уровне или даже ниже, чем у Windows GDI +. Я надеюсь, что кто-то может взглянуть на то, что я сделал, и предложить способ (-ы), с помощью которого я могу значительно сократить время, необходимое для рисования.
Основой этого является то, что у меня есть личный проект, в котором я занимаюсь разработкой базового, но функционального интерфейса САПР, способного выполнять различные задачи, включая двухмерный анализ методом конечных элементов. Чтобы сделать его вообще полезным, интерфейс должен иметь возможность отображать десятки тысяч примитивных элементов (многоугольники, круги, прямоугольники, точки, дуги и т. Д.).
Я изначально писал методы рисования, используя Windows GDI + (System.Drawing), и производительность довольно хорошая, пока я не достигну около 3000 элементов на экране в любой момент времени. Экран должен обновляться каждый раз, когда пользователь перемещает, масштабирует, рисует новые элементы, удаляет элементы, перемещает, поворачивает и т. Д. Теперь, чтобы повысить эффективность, я использую структуру данных четырехугольного дерева для хранения своих элементов, и я рисую только элементы, которые на самом деле попадают в границы окна управления. Это значительно помогло при увеличении, но очевидно, что при полном уменьшении и отображении всех элементов это не имеет значения. Я также использую события таймера и отметки для обновления экрана с частотой обновления (60 Гц), поэтому я не пытаюсь обновлять тысячи раз в секунду или при каждом событии мыши.
Это мой первый опыт программирования с DirectX и Direct2D, поэтому я определенно учусь здесь. При этом я провел дни, просматривая учебники, примеры и форумы, и не мог найти много, что помогло. Я пробовал дюжину различных методов рисования, предварительной обработки, многопоточности и т. Д. Мой код ниже
Код для прохода и рисования элементов
List<IDrawingElement> elementsInBounds = GetElementsInDraftingWindow();
_d2dContainer.Target.BeginDraw();
_d2dContainer.Target.Clear(ColorD2D.FromKnown(Colors.White, 1));
if (elementsInBounds.Count > 0)
{
Stopwatch watch = new Stopwatch();
watch.Start();
#region Using Drawing Element DrawDX Method
foreach (IDrawingElement elem in elementsInBounds)
{
elem.DrawDX(ref _d2dContainer.Target, ref _d2dContainer.Factory, ZeroPoint, DrawingScale, _selectedElementBrush, _selectedElementPointBrush);
}
#endregion
watch.Stop();
double drawingTime = watch.ElapsedMilliseconds;
Console.WriteLine("DirectX drawing time = " + drawingTime);
watch.Reset();
watch.Start();
Matrix3x2 scale = Matrix3x2.Scale(new SizeFD2D((float)DrawingScale, (float)DrawingScale), new PointFD2D(0, 0));
Matrix3x2 translate = Matrix3x2.Translation((float)ZeroPoint.X, (float)ZeroPoint.Y);
_d2dContainer.Target.Transform = scale * translate;
watch.Stop();
double transformTime = watch.ElapsedMilliseconds;
Console.WriteLine("DirectX transform time = " + transformTime);
}
Функция DrawDX для полигона
public override void DrawDX(ref WindowRenderTarget rt, ref Direct2DFactory fac, Point zeroPoint, double drawingScale, SolidColorBrush selectedLineBrush, SolidColorBrush selectedPointBrush)
{
if (_pathGeometry == null)
{
CreatePathGeometry(ref fac);
}
float brushWidth = (float)(Layer.Width / (drawingScale));
brushWidth = (float)(brushWidth * 2);
if (Selected == false)
{
rt.DrawGeometry(Layer.Direct2DBrush, brushWidth, _pathGeometry);
//Note that _pathGeometry is a PathGeometry
}
else
{
rt.DrawGeometry(selectedLineBrush, brushWidth, _pathGeometry);
}
}
Код для создания Direct2D Factory & Render Target
private void CreateD2DResources(float dpiX, float dpiY)
{
Factory = Direct2DFactory.CreateFactory(FactoryType.SingleThreaded, DebugLevel.None, FactoryVersion.Auto);
RenderTargetProperties props = new RenderTargetProperties(
RenderTargetType.Default, new PixelFormat(DxgiFormat.B8G8R8A8_UNORM,
AlphaMode.Premultiplied), dpiX, dpiY, RenderTargetUsage.None, FeatureLevel.Default);
Target = Factory.CreateWindowRenderTarget(_targetPanel, PresentOptions.None, props);
Target.AntialiasMode = AntialiasMode.Aliased;
if (_selectionBoxLeftStrokeStyle != null)
{
_selectionBoxLeftStrokeStyle.Dispose();
}
_selectionBoxLeftStrokeStyle = Factory.CreateStrokeStyle(new StrokeStyleProperties1(LineCapStyle.Flat,
LineCapStyle.Flat, LineCapStyle.Flat, LineJoin.Bevel, 10, DashStyle.Dash, 0, StrokeTransformType.Normal), null);
}
Я создаю фабрику Direct2D и визуализирую цель один раз и сохраняю ссылки на них все время (таким образом я не воссоздаю каждый раз). Я также создаю все кисти при создании слоя для рисования (который описывает цвет, ширину и т. Д.). Таким образом, я не создаю новую кисть каждый раз, когда рисую, просто ссылаюсь на уже существующую кисть. То же самое с геометрией, как можно увидеть во втором фрагменте кода. Я создаю геометрию один раз и обновляю геометрию только в том случае, если сам элемент перемещен или повернут. В противном случае я просто применяю преобразование к цели рендеринга после рисования.
Исходя из моих секундомеров, время, необходимое для прохождения цикла и вызова методов elem.DrawDX, составляет около 225-250 мс (для 45 000 полигонов). Время, необходимое для применения преобразования, составляет 0-1 мс, поэтому, похоже, узкое место находится в функции RenderTarget.DrawGeometry ().
Я провел те же тесты с RenderTarget.DrawEllipse () или RenderTarget.DrawRectangle (), поскольку я читал, что использование DrawGeometry медленнее, чем DrawRectangle или DrawEllipse, поскольку геометрия прямоугольника / эллипса известна заранее. Однако во всех моих тестах не имело значения, какую функцию рисования я использую, время для одного и того же числа элементов всегда примерно равно.
Я пытался создать многопоточную фабрику Direct2D и запускать функции рисования через задачи, но это намного медленнее (примерно в два раза медленнее). Методы Direct2D, по-видимому, используют мою видеокарту (аппаратное ускорение включено), так как когда я наблюдаю за использованием моей видеокарты, она обновляется при обновлении экрана (на моем ноутбуке установлена мобильная видеокарта NVIDIA Quadro).
Извинения за скучный пост.Я надеюсь, что это было достаточно предыстории и описания вещей, которые я попробовал.Заранее благодарен за любую помощь!
Edit # 1 Таким образом, код изменился с итерации по списку с использованием foreach на итерацию по массиву с использованием for, что сократило время рисования вдвое!Я не осознавал, насколько медленнее списки, чем массивы (я знал, что было некоторое преимущество в производительности, но не осознавал этого!).Однако на прорисовку уходит 125 мс.Это намного лучше, но все еще не гладко.Любые другие предложения?