在C#/ WPF中加速L-System渲染器

lsys是一个用CoffeeScript编写的超快 L-System渲染器。

下面是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 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(); var str = "L"; { var tbl = new Dictionary(); tbl.Add('L', "|-S!L!Y"); tbl.Add('S', "[F[FF-YS]F)G]+"); tbl.Add('Y', "--[F-)F]+Y"); for (var i = 0; i  { 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 == ' { 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(); } } } 

WPF的几何渲染速度很慢 。 如果您想要快速,请使用其他技术进行渲染,并将结果托管在WPF中。 例如,您可以使用Direct3D渲染并在D3DImage中托管渲染目标。 这是使用Direct2D的示例 。 或者您可以通过在RGB缓冲区中手动设置字节值并将其复制到WriteableBitmap中来绘制。

编辑:正如OP发现的那样,还有一个免费的库来帮助绘制一个名为WriteableBitmapEx的WriteableBitmap 。

下面是一个使用WritableBitmap作为Asik建议的版本。 我为DrawLine方法使用了WriteableBitmapEx扩展方法库。

现在它快得离谱了。 谢谢阿西克!

 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 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(); var str = "L"; { var tbl = new Dictionary(); tbl.Add('L', "|-S!L!Y"); tbl.Add('S', "[F[FF-YS]F)G]+"); tbl.Add('Y', "--[F-)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(); 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) aX, (int) aY, (int) bX, (int) bY, 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版本,所以我不知道它是如何比较的,但我能够通过使用StreamGeometryFreeze()来大幅加速WPF本机版本,这是一种在没有动画时进行优化的方法。 (虽然它仍然感觉不如javascript版本快)

  • 发布的版本时间约为0.15秒
  • StreamGeometry版本的时间约为0.029秒

我不认为计时器包括实际的渲染时间,只是填充渲染命令的时间。 但是,它也感觉更快。 此WPF性能测试演示了获取实际渲染时间的方法。

我还删除了CanvasFrameworkElement ,但它正在切换到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://stackoverflow.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 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 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(); var str = "L"; { var tbl = new Dictionary(); tbl.Add('L', "|-S!L!Y"); tbl.Add('S', "[F[FF-YS]F)G]+"); tbl.Add('Y', "--[F-)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(); } } } 

这是使用SlimDX 的DirectX版本 。