Closed predatorgor closed 5 years ago
@predatorgor Using shapes is not most efficient way of rendering many elements. To get access to low level rendering system in Avalonia
you can create custom control by deriving from for example from Canvas
and ovvriding public override void Render(DrawingContext context)
method. Than use DrawingContext
for drawing shapes. Here is example of such control: https://github.com/wieslawsoltes/SpiroNet/blob/master/src/SpiroNet.Editor.Avalonia/Renderer/CanvasRenderer.cs
@predatorgor 👍 to what @wieslawsoltes said, but also you might want to try out the Avalonia scenegraph
branch which should improve drawing performance (but it still a work in progress currently).
@predatorgor Try this code (create new Avalonia
app named DrawCanvasDemo
):
EllipseShape.cs
namespace DrawCanvasDemo
{
public class EllipseShape
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public EllipseShape(double x, double y, double width, double height)
{
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
}
}
DrawCanvas.cs
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
namespace DrawCanvasDemo
{
public class DrawCanvas : Canvas
{
private double _width;
private double _height;
private SolidColorBrush _brush;
private IList<EllipseShape> _ellipses;
private IDictionary<EllipseShape, EllipseGeometry> _ellipsesCache;
public DrawCanvas()
{
_width = 10;
_height = 10;
_brush = new SolidColorBrush(Color.Parse("red"));
_ellipses = new List<EllipseShape>();
_ellipsesCache = new Dictionary<EllipseShape, EllipseGeometry>();
this.PointerMoved += (sender, args) =>
{
var p = args.GetPosition(this);
var ellipse = new EllipseShape(p.X - _width / 2, p.Y - _height / 2, _width, _height);
_ellipses.Add(ellipse);
var geometry = new EllipseGeometry(new Rect(ellipse.X, ellipse.Y, ellipse.Width, ellipse.Height));
_ellipsesCache.Add(ellipse, geometry);
Debug.WriteLine(_ellipses.Count);
this.InvalidateVisual();
};
}
public override void Render(DrawingContext context)
{
base.Render(context);
foreach (var ellipse in _ellipses)
{
var geometry = _ellipsesCache[ellipse];
context.DrawGeometry(_brush, null, geometry);
}
}
}
}
MainWindow.xaml
<Window x:Class="DrawCanvasDemo.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DrawCanvasDemo;assembly=DrawCanvasDemo"
Title="DrawCanvasDemo" Height="700" Width="1000"
UseLayoutRounding="True">
<Grid>
<local:DrawCanvas Background="LightGray" Width="600" Height="600"/>
</Grid>
</Window>
MainWindow.xaml.cs
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace DrawCanvasDemo
{
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
App.xaml
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles>
</Application>
App.xaml.cs
using Avalonia;
using Avalonia.Markup.Xaml;
namespace DrawCanvasDemo
{
class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
base.Initialize();
}
static void Main(string[] args)
{
var app = new App();
AppBuilder.Configure<App>()
.UsePlatformDetect()
.Start<MainWindow>();
}
}
}
Full source code: https://github.com/wieslawsoltes/DrawCanvasDemo
@wieslawsoltes it is an improvement and it is faster due to caching geometry. However, it still renders every geometry that makes rendering time drift with every new item. So with every tiny stroke in future the whole canvas has to be redrawn. Do I need to go Direct2d way and make rendering to a texture first and call that texture on render of my "FastCanvas" controle? Maybe there is something higher level in Avolonia to do this? Another way is bitmap cache, but I don't know how much the RAM bitmap cache helps... It will probably waste all the CPU time on moving pixels from RAM to GPU memory?
@predatorgor Do not know any better solution. Maybe someone else could help you.
@predatorgor have you been able to try it with the scenegraph
branch?
I have not tried the scenegraph branch yet. However I experimented with the Direct2D api itself. The fast and low latency approach is to render to shared Direct3D texture first and later show it on screen using second thread... The pointing devices like mouse, pen, touch can sample position at frequency up to 1000Hz so rendering 1000 fps on screen can be difficult because it needs to go through all the DWM and vertical synchronization to be efficient. I will give a try to scenegraph and check if it is possible to optimize it for texture rendering...
Logically it should be possible to optimize it even for master branch. Create a control with texture rendering in onRender method, and use other thread to capture mouse and render to texture.
Yep, on the scenegraph
branch the rendering is done on a separate rendering thread. See https://github.com/AvaloniaUI/Avalonia/pull/827 for more details.
@predatorgor can you try again on the current CI package?
Closing this due to inactivity and the fact that this should now be improved.
Hi, when I create more then 1500 ellipses on the canvas every new ellipse takes longer time to be created. It looks like every time ellipse is added to the canvas and positioned, whole canvas gets a compleate redraw and slows down. Is there a builtin machanism to draw many primitive shapes on the canvas or on some surface? I would like to create an ellipse on every mouse move event and position it on the canvas. What is the best aproach to do this using Avalonia UI? This is just a test I made with ellipse. Next step would be to make something similiar to an InkCanvas(WPF,UWP), so I would like to get some hint to take right aproach. For instance, create own caching machanism or use some low level drawing methods to draw on the surface without hit testing like a texture. Is there a way to create antialised shapes and rasterise them in realtime for fast drawing? `public class MainWindow : Window { private SolidColorBrush _brush; private Canvas _canvas; private int _count;