Ускорение рендерера L-System в C#/WPF
lsys - это быстрый и быстрый L-System рендер, написанный на CoffeeScript.
Ниже приведен простой рендер в C# и WPF. Этот пример жестко запрограммирован. Результат при запуске выглядит следующим образом:
Щелчок мышью в окне отрегулирует angleGrowth
переменная. Пересчет GeometryGroup
а также строительство Canvas
обычно занимает гораздо меньше десятой доли секунды. Тем не менее, фактическое обновление экрана, кажется, занимает гораздо больше времени.
Любые предложения о том, как сделать это быстрее или эффективнее? В настоящее время он намного медленнее, чем версия CoffeeScript/JavaScript...:-)
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
namespace WpfLsysRender
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State) this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str)
{
var sb = new StringBuilder();
foreach (var elt in str)
{
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow()
{
InitializeComponent();
Width = 800;
Height = 800;
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var canvas = new Canvas();
Content = canvas;
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometryGroup = new GeometryGroup();
Action buildGeometry = () =>
{
state = new State()
{
x = 0,
y = 0,
dir = 0,
size = 14.11,
angle = -3963.7485
};
geometryGroup = new GeometryGroup();
foreach (var elt in str)
{
if (elt == 'F')
{
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
geometryGroup.Children.Add(
new LineGeometry(
new Point(state.x, state.y),
new Point(new_x, new_y)));
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
};
Action populateCanvas = () =>
{
var drawingVisualElement = new DrawingVisualElement();
Console.WriteLine(".");
canvas.Children.Clear();
canvas.RenderTransform = new TranslateTransform(400.0, 400.0);
using (var dc = drawingVisualElement.visual.RenderOpen())
dc.DrawGeometry(null, pen, geometryGroup);
canvas.Children.Add(drawingVisualElement);
};
MouseDown += (s, e) =>
{
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildGeometry();
populateCanvas();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildGeometry();
populateCanvas();
}
}
}
4 ответа
Визуализация геометрии WPF просто медленная. Если вы хотите быстро, выполните рендеринг с использованием другой технологии и разместите результат в WPF. Например, вы можете выполнить рендеринг с использованием Direct3D и разместить цель рендеринга внутри D3DImage. Вот пример использования Direct2D вместо. Или вы можете рисовать, вручную устанавливая значения байтов в буфере RGB и копируя их в WriteableBitmap.
РЕДАКТИРОВАТЬ: как выяснил ОП, есть также бесплатная библиотека для рисования внутри WriteableBitmap под названием WriteableBitmapEx.
Ниже приведена версия, которая использует WritableBitmap
как предложил Asik. Я использовал библиотеку методов расширения WriteableBitmapEx для DrawLine
метод.
Это смехотворно быстро сейчас. Спасибо Асик!
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Diagnostics;
namespace WpfLsysRender
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State) this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str)
{
var sb = new StringBuilder();
foreach (var elt in str)
{
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow()
{
InitializeComponent();
Width = 800;
Height = 800;
var bitmap = BitmapFactory.New(800, 800);
Content = new Image() { Source = bitmap };
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var lines = new List<Point>();
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometryGroup = new GeometryGroup();
Action buildLines = () =>
{
lines.Clear();
state = new State()
{
x = 400,
y = 400,
dir = 0,
size = 14.11,
angle = -3963.7485
};
foreach (var elt in str)
{
if (elt == 'F')
{
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
lines.Add(new Point(state.x, state.y));
lines.Add(new Point(new_x, new_y));
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
};
Action updateBitmap = () =>
{
using (bitmap.GetBitmapContext())
{
bitmap.Clear();
for (var i = 0; i < lines.Count; i += 2)
{
var a = lines[i];
var b = lines[i+1];
bitmap.DrawLine(
(int) a.X, (int) a.Y, (int) b.X, (int) b.Y,
Colors.Black);
}
}
};
MouseDown += (s, e) =>
{
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildLines();
updateBitmap();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildLines();
updateBitmap();
}
}
}
Я не проверял WriteableBitmapEx
версия, так что я не знаю, как это сравнить, но я смог существенно ускорить родную версию WPF с помощью StreamGeometry
а также Freeze()
, который является способом оптимизации, когда нет анимации. (Хотя это все еще не так быстро, как версия javascript)
- Время опубликованной версии ~0.15с
- Время версии StreamGeometry составляет ~ 0,029 с
Я не думаю, что таймер включает в себя фактическое время рендеринга, просто время для заполнения команд рендеринга. Тем не менее, он также чувствует себя намного быстрее. Этот тест производительности WPF демонстрирует способ получения фактического времени рендеринга.
Я также удалил Canvas
а также FrameworkElement
, но это было переключение на StreamGeometry, который сделал ускорение.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Diagnostics;
using System.Windows.Media.Imaging;
// https://stackru.com/q/22599806/519568
namespace WpfLsysRender
{
class UpdatableUIElement : UIElement {
DrawingGroup backingStore = new DrawingGroup();
public UpdatableUIElement() {
}
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
drawingContext.DrawDrawing(backingStore);
}
public void Redraw(Action<DrawingContext> fn) {
var vis = backingStore.Open();
fn(vis);
vis.Close();
}
}
class State
{
public double size;
public double angle;
public double x;
public double y;
public double dir;
public State Clone() { return (State)this.MemberwiseClone(); }
}
public partial class MainWindow : Window
{
static string Rewrite(Dictionary<char, string> tbl, string str) {
var sb = new StringBuilder();
foreach (var elt in str) {
if (tbl.ContainsKey(elt))
sb.Append(tbl[elt]);
else
sb.Append(elt);
}
return sb.ToString();
}
public MainWindow() {
// InitializeComponent();
Width = 800;
Height = 800;
var states = new Stack<State>();
var str = "L";
{
var tbl = new Dictionary<char, string>();
tbl.Add('L', "|-S!L!Y");
tbl.Add('S', "[F[FF-YS]F)G]+");
tbl.Add('Y', "--[F-)<F-FG]-");
tbl.Add('G', "FGF[Y+>F]+Y");
for (var i = 0; i < 12; i++) str = Rewrite(tbl, str);
}
var lsystem_view = new UpdatableUIElement();
Content = lsystem_view;
var sizeGrowth = -1.359672;
var angleGrowth = -0.138235;
State state;
var pen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
var geometry = new StreamGeometry();
Action buildGeometry = () => {
state = new State() {
x = 0,
y = 0,
dir = 0,
size = 14.11,
angle = -3963.7485
};
geometry = new StreamGeometry();
var gc = geometry.Open();
foreach (var elt in str) {
if (elt == 'F') {
var new_x = state.x + state.size * Math.Cos(state.dir * Math.PI / 180.0);
var new_y = state.y + state.size * Math.Sin(state.dir * Math.PI / 180.0);
var p1 = new Point(state.x, state.y);
var p2 = new Point(new_x, new_y);
gc.BeginFigure(p1,false,false);
gc.LineTo(p2,true,true);
state.x = new_x;
state.y = new_y;
}
else if (elt == '+') state.dir += state.angle;
else if (elt == '-') state.dir -= state.angle;
else if (elt == '>') state.size *= (1.0 - sizeGrowth);
else if (elt == '<') state.size *= (1.0 + sizeGrowth);
else if (elt == ')') state.angle *= (1 + angleGrowth);
else if (elt == '(') state.angle *= (1 - angleGrowth);
else if (elt == '[') states.Push(state.Clone());
else if (elt == ']') state = states.Pop();
else if (elt == '!') state.angle *= -1.0;
else if (elt == '|') state.dir += 180.0;
}
gc.Close();
geometry.Freeze();
};
Action populateCanvas = () => {
Console.WriteLine(".");
lsystem_view.RenderTransform = new TranslateTransform(400,400);
lsystem_view.Redraw((dc) => {
dc.DrawGeometry(null, pen, geometry);
});
};
MouseDown += (s, e) => {
angleGrowth += 0.001;
Console.WriteLine("angleGrowth: {0}", angleGrowth);
var sw = Stopwatch.StartNew();
buildGeometry();
populateCanvas();
sw.Stop();
Console.WriteLine(sw.Elapsed);
};
buildGeometry();
populateCanvas();
}
}
}