AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.38k stars 2.2k forks source link

How to create a custom rendering control ? #2176

Closed na2axl closed 1 year ago

na2axl commented 5 years ago

Hi,

I'm currently working on a game engine for my company, and with a lot of research, we found that Avalonia is the best choice for our cross platform game editor. However, I'm always new to Avalonia so excuse me if my problem is not really a problem or if I'm wrong somewhere...

I've understand that Avalonia use Skia + Direct2D or OpenGL backend for rendering the whole window, so its not very difficult to create a custom renderer for a whole window rendering. But what I want is not to render onto the whole window but just somewhere in it (like the scene manager of Unity3D for example).

I've tried to create an offscreen game rendering thread to render the game, and exporting the framebuffer texture to an image stream. Into the MainWindow, we have an Image control, which at each render passes of the game background thread, changes its Source property with the exported image stream.

This process is working, but we have a - unsupportable - performance issue because the rendered image seems to skip too much frames. We have tried to solve this issue but we got nothing satisfying our needs...

So my question is to know if I'm doing things wrong ? Avalonia has already a solution for this case ? Is there a better way to implement this rendering control ?

Thanks in advance.

kekekeks commented 5 years ago

Unfortunately, there is no easy solution.

WritableBitmap would be faster than saving and loading the image to stream, but that will still involve a transfer from video memory to the main memory and back.

For more performance we need a way to interact with native framebuffer texture like WPFs D3DImage does. The problem here is the variety of rendering APIs that can be actually used. Skia has support for OpenGL, OpenGL ES and Vulkan

Win32:

Linux:

Mac OS X:

We have a set of abstractions for OpenGL/OpenGLES that allows us to manipulate OpenGL contexts in somewhat unified way: GlInterface, IGlContext and IGlDisplay.

One can obtain glGetProcAddress from said abstraction, but that will most likely be not sufficient for a game engine that probably wants to initialize OpenGL itself. Another problem is that on some platforms we are using OpenGLES, on other desktop OpenGL gets initialized.

We could expose a way to provide your own SkImage as a bitmap, but it has to be created by the same GL context.

Another way would be to implement our windowing platform interfaces on top of your game engine and render Avalonia UI to a texture that gets later composed by the game engine itself.

kekekeks commented 5 years ago

Another a bit hacky way would be to replace our IWindowingPlatformGlFeature in service locator (you also would need to replace rendering platform initializer, since it's consumed by Skia backend on initialization) and create a decorator for IWindowImpl that provides a different set of surfaces. Then you could use HWND/XID to initialize OpenGL context manually and provide us with our OpenGL abstraction interfaces. That would cover Windows and Linux. On OS X you could propbably use our opengl context since it's created via NSOpenGLContext anyway. We might want to provide a way to customize the pixel format though.

Gillibald commented 5 years ago

I like the idea of having a special Bitmap for interop purposes. For Direct2D that would mean someone has to produce a shared bitmap from a scene graph he wants to display. That could very much be a 3D scene. Refreshing will still involve some invalidate visual calls. This solution isn't high performance but should still perform well. Rendering the Avalonia scene graph to some existing surface would perform better.

na2axl commented 5 years ago

Thanks @kekekeks for your tips

WritableBitmap would be faster than saving and loading the image to stream, but that will still involve a transfer from video memory to the main memory and back.

Yes is true, with the use of WritableBitmap we have managed to run Avalonia from 3 - 7 FPS to 10 - 16 FPS (yes... It was up to 7 FPS before...).

Another way would be to implement our windowing platform interfaces on top of your game engine and render Avalonia UI to a texture that gets later composed by the game engine itself.

Another a bit hacky way would be to replace our IWindowingPlatformGlFeature in service locator (you also would need to replace rendering platform initializer, since it's consumed by Skia backend on initialization) and create a decorator for IWindowImpl that provides a different set of surfaces. Then you could use HWND/XID to initialize OpenGL context manually and provide us with our OpenGL abstraction interfaces.

Thats it's unfortunately not on what we want to focus in our team... Our game engine uses OpenGL, OpenGL ES, Vulkan, DirectX and Metal backends (not only OpenGL), depending on the executing platform and user settings. It will not easy for us to cover all these API for a proxy Avalonia renderer. And, if I understand well, we also have to create a proxy for Avalonia controls events, which means for us a lot of work that will not fit in our deadlines.

With a benchmark test, we can say that our game rendering loop (running with up to 26 FPS) is not synchronized to the Avalonia renderer loop (running with up to 16 FPS), this is caused by invalidating the Image control at every game rendering passes, but the control will be rerendered at each Avalonia rendering passes.

To bypass the Avalonia renderer (only for the Image control), after a - small - deep look at the Avalonia source code, we found a ImmediateRenderer class (Hallelujah!) which have a static Render(IVisual, IRenderTarget) method. In our window we have replaced the invalidate visual calls to ImmediateRenderer.Render(sceneViewer, _window.CreateRenderTarget()) and (Hallelujah!), the Avalonia renderer loop runs with up to 24 FPS, with a slight same average than the game rendering loop, but, again, we got a - incomprehensible - rendering issue, some times, the whole window rendering got freezed (nothing is refreshed), when not, the Image control is not rendered at the normal position and blink 4 - 6 times before disappearing totally and the window rendering got freezed again. This process restarts until we close the window.

There is a way to get the current IRenderTarget of the window ? (Sincerely, I don't think that Window.CreateRenderTarget() should be used here...) Or if it's possible, there is another way to bypass the Avalonia renderer for a control ?

If it may help, we are running tests in a PC with our minimal requirements target:

For now we are only testing on Linux platforms (due to the broken state of the DirectX backend on Windows).

Thanks again for your help.

kekekeks commented 5 years ago

If you are planning to use ImmediateRenderer, you need to switch to it when initializing windowing platform (UseWin32, UseGtk3, UseAvaloniaNative) calls, otherwise rendering would be mixed with DeferredRenderer, which isn't a good thing.

kekekeks commented 5 years ago

Another thing to consider is that DeferredRenderer's loop runs on 60FPS tick rate on a separate thread. We might want to add a way to hook to said renderer pass and update the bitmap. That would require to associate a layer to a control, but that should be doable. I'll try to figure something this month.

The API would look somewhat like:

class MyControl : Control, IThreadSafeRenderControl
{
  WritableBitmap _bitmap = new WritableBitmap(100, 100);
  bool IThreadSafeRenderControl.ThreadSafeRender(Func<DrawingContext> getContext, Size dimensions, bool alwaysProduceFrame)
  {
     using(var fb = bitmap.Lock())
     {
        // Do stuff
     }
     getContext().DrawBitmap(_bitmap);
     return true; // We have produced a new frame
  }
}

This method would be called on every frame. If there is something new, you'll need to draw the bitmap.

kekekeks commented 5 years ago

The API will be most likely changed a bit since we need something like that for playing GIF animations, but the general idea would be the same.

Gillibald commented 5 years ago

Casting your window to https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Visuals/Rendering/IRenderRoot.cs should give you the instance of the currently used renderer. Starting your App with UseDeferredRendering to false should produce a ImmediateRenderer. That instance has an overload of the Render method that accepts an IVisual and a DrawingContext. As far as I understand you can create a DrawingContext for the currently used RenderTarget. Maybe this helps a bit. Haven't checked this info.

kekekeks commented 5 years ago

@na2axl Please, check if this is sufficient for your needs: https://github.com/AvaloniaUI/Avalonia/pull/2185

You can test it by installing 0.7.1-build1014-beta build from our PR nuget feed

na2axl commented 5 years ago

Thanks for your work @kekekeks I'll will try this tonight !

kekekeks commented 5 years ago

Build 0.7.1-build1021-beta should fix the issue with (0, 0) being passed as logicalSize.

na2axl commented 5 years ago

:tada: :tada: :tada: Thanks very much @kekekeks for your PR, It's just awesome the window and the game loop are running at 60 FPS together ! No more graphics glitches and frame skips ! :tada: :tada: :tada:

Thanks again @kekekeks and @Gillibald for your help :smiley:

na2axl commented 5 years ago

This PR comes with new exceptions thrown and debug warnings, but these doesn't block the program execution. If it may help you I will left this debug log here:

-------------------------------------------------------------------
You may only use the Microsoft .NET Core Debugger (vsdbg) with
Visual Studio Code, Visual Studio or Visual Studio for Mac software
to help you develop and test your applications.
-------------------------------------------------------------------
[DEBUG] Service added: AlienEngine.Core.IoC.IoCService
[DEBUG] Service added: AlienEngine.Core.Scripting.ScriptingService
[DEBUG] Service added: AlienEngine.Core.Messaging.MessagingService
[DEBUG] Detecting platform...
[DEBUG] Detected platform: X11
[DEBUG] Starting AlienEngine Editor...
Property: Property '"Avalonia.Media.TranslateTransform"."X"' is not registered on '"Avalonia.Controls.Border"'.
Property: Property '"Avalonia.Media.TranslateTransform"."X"' is not registered on '"Avalonia.Controls.Border"'.
Property: Property '"Avalonia.Media.TranslateTransform"."Y"' is not registered on '"Avalonia.Controls.Border"'.
Property: Property '"Avalonia.Media.TranslateTransform"."Y"' is not registered on '"Avalonia.Controls.Border"'.
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
The thread 12494 has exited with code 0 (0x0).
The thread 12482 has exited with code 0 (0x0).
The thread 12486 has exited with code 0 (0x0).
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Not replacing existing, living, managed instance with new object.
Visual: Exception in render loop: "System.NullReferenceException: Object reference not set to an instance of an object.
   at Avalonia.Rendering.DeferredRenderer.Render(Boolean forceComposite) in D:\a\1\s\src\Avalonia.Visuals\Rendering\DeferredRenderer.cs:line 240
   at Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time) in D:\a\1\s\src\Avalonia.Visuals\Rendering\RenderLoop.cs:line 120"
[DEBUG] Shutting down AlienEngine Editor...

(dotnet:12438): GLib-GObject-CRITICAL **: 23:42:55.252: g_object_unref: assertion 'G_IS_OBJECT (object)' failed
The program '[12438] Editor.dll' has exited with code 0 (0x0).

Lines starting with [DEBUG] are from our game engine.

Borgleader commented 3 years ago

Hello, I've been meaning to work on a personal project which is of a very similar nature to the one OP describes. However, I notice the mentioned PR was never merged in. Is there an alternate solution to achieve the same result?

I saw in the samples that there was an 'OpenGlPage' demo (but that doesn't seem to work in 0.9 I'm assuming its > 0.10?) but what about other APIs like DirectX? And I assume hosting a win32 window would lead to 'airspace' issues like in WPF?

Apologie sin advance if I should have created a new issue.

kekekeks commented 3 years ago

Native opengl context sharing is available in 0.10. Unfortunately on Windows it requires not-yet-stable WGL backend. There is planned support for DXGI surface sharing, but it it expected to land somewhere next Spring.

Borgleader commented 3 years ago

Ok I will keep an eye out for that, thank you 👍

GF-Huang commented 3 years ago

In conclusion, is it not recommend to implements a custom drawing control at this time (real-time chart purpose for me)?

danwalmsley commented 3 years ago

@GF-Huang I think you can easily have realtime charting with normal rendering control.

https://github.com/AvaloniaCommunity/awesome-avalonia#chart--plot

GF-Huang commented 3 years ago

The ScottPlot has a lot of issue, and it is not good for MVVM, I give up it. See #1646.

The OxyPlot docs is a PDF, it's hard to read.

The Microcharts seems too crude and lacking the necessary docs.

danwalmsley commented 3 years ago

You should be able to implement your own charting without the need for special rendering.. just implement a control and override render...

obviously there is a lot more to it.. in terms of drawing the char... but I think it shouldnt need anything specialised.

GF-Huang commented 3 years ago

But why I get System.InvalidOperationException:“Call from invalid thread”?

image

ahopper commented 3 years ago

using .ToImmutable() when you create your pen may well fix it.

GF-Huang commented 3 years ago

using .ToImmutable() when you create your pen may well fix it.

It works, thank you.

Gillibald commented 3 years ago

I suggest porting https://github.com/dotnet-ad/Microcharts to Avalonia. This should only require one custom control that is implemented with a few lines of code.

GF-Huang commented 3 years ago

I suggest porting https://github.com/dotnet-ad/Microcharts to Avalonia. This should only require one custom control that is implemented with a few lines of code.

Microcharts seems not easy to draw the follow, note that the datapoints(signal) has different spaces.

image

kekekeks commented 3 years ago

But why I get System.InvalidOperationException:“Call from invalid thread”?

Use SKCanvas directly (see https://github.com/AvaloniaUI/Avalonia/blob/3c22ce5b1b9bd1dd66f9021696427992c11779b2/samples/RenderDemo/Pages/CustomSkiaPage.cs#L42 )

Just make sure that your drawing operation is actually thread safe.

GF-Huang commented 3 years ago

But why I get System.InvalidOperationException:“Call from invalid thread”?

Use SKCanvas directly (see

https://github.com/AvaloniaUI/Avalonia/blob/3c22ce5b1b9bd1dd66f9021696427992c11779b2/samples/RenderDemo/Pages/CustomSkiaPage.cs#L42

) Just make sure that your drawing operation is actually thread safe.

What difference to context.DrawXXX() directly?

maxkatz6 commented 1 year ago

Aside from custom drawing operation, there is an API for low level graphics interop https://github.com/AvaloniaUI/Avalonia/issues/9925