-
Notifications
You must be signed in to change notification settings - Fork 548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SKGLElement to SkiaSharp.Views.WPF #2317
Changes from all commits
c724039
865315e
e592509
146b513
6f2310d
5397d91
0872c0f
fcc884b
6751f0c
acde6d2
cbd3d92
99b3c15
e9ef0d8
eee8114
fef2c4e
1eb5dfe
d595c1e
1dd3388
9eb5ea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows; | ||
using System.Windows.Controls; | ||
using System.Windows.Media; | ||
using System.Windows.Media.Imaging; | ||
using OpenTK.Graphics; | ||
using System.Windows.Media.Media3D; | ||
using SkiaSharp.Views.Desktop; | ||
using OpenTK.Wpf; | ||
using SkiaSharp; | ||
using OpenTK.Graphics.OpenGL; | ||
using OpenTK.Platform.Windows; | ||
using OpenTK; | ||
using System.Windows.Interop; | ||
using OpenTK.Platform; | ||
#if NETCOREAPP || NET | ||
using OpenTK.Mathematics; | ||
#endif | ||
|
||
namespace SkiaSharp.Views.WPF | ||
{ | ||
[DefaultEvent("PaintSurface")] | ||
[DefaultProperty("Name")] | ||
public class SKGLElement : GLWpfControl, IDisposable | ||
{ | ||
private const SKColorType colorType = SKColorType.Rgba8888; | ||
private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; | ||
|
||
private bool designMode; | ||
|
||
private GRContext grContext; | ||
private GRGlFramebufferInfo glInfo; | ||
private GRBackendRenderTarget renderTarget; | ||
private SKSurface surface; | ||
private SKCanvas canvas; | ||
|
||
private SKSizeI lastSize; | ||
|
||
public SKGLElement() | ||
: base() | ||
{ | ||
Initialize(); | ||
} | ||
|
||
private void Initialize() | ||
{ | ||
designMode = DesignerProperties.GetIsInDesignMode(this); | ||
var settings = new GLWpfControlSettings() { MajorVersion = 2, MinorVersion = 1, RenderContinuously = false }; | ||
|
||
this.Render += OnPaint; | ||
|
||
this.Loaded += SKGLElement_Loaded; | ||
this.Unloaded += SKGLElement_Unloaded; | ||
|
||
#if NETCOREAPP | ||
this.RegisterToEventsDirectly = false; | ||
#endif | ||
|
||
Start(settings); | ||
} | ||
|
||
private void SKGLElement_Unloaded(object sender, RoutedEventArgs e) | ||
{ | ||
Release(); | ||
} | ||
private void SKGLElement_Loaded(object sender, RoutedEventArgs e) | ||
{ | ||
InvalidateVisual(); | ||
} | ||
|
||
public SKSize CanvasSize => lastSize; | ||
|
||
public GRContext GRContext => grContext; | ||
|
||
[Category("Appearance")] | ||
public event EventHandler<SKPaintGLSurfaceEventArgs> PaintSurface; | ||
|
||
private SKSizeI GetSize() | ||
{ | ||
var currentWidth = ActualWidth; | ||
var currentHeight = ActualHeight; | ||
|
||
if (currentWidth < 0 || | ||
currentHeight < 0) | ||
{ | ||
currentWidth = 0; | ||
currentHeight = 0; | ||
} | ||
|
||
PresentationSource source = PresentationSource.FromVisual(this); | ||
|
||
double dpiX = 1.0; | ||
double dpiY = 1.0; | ||
if (source != null) | ||
{ | ||
dpiX = source.CompositionTarget.TransformToDevice.M11; | ||
dpiY = source.CompositionTarget.TransformToDevice.M22; | ||
} | ||
|
||
return new SKSizeI((int)(currentWidth * dpiX), (int)(currentHeight * dpiY)); | ||
} | ||
|
||
protected override void OnRender(DrawingContext drawingContext) | ||
{ | ||
if (grContext != null) | ||
{ | ||
grContext.ResetContext(); | ||
} | ||
mattleibow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
base.OnRender(drawingContext); | ||
} | ||
|
||
protected virtual void OnPaint(TimeSpan e) | ||
{ | ||
if (disposed) | ||
{ | ||
return; | ||
} | ||
if (designMode) | ||
{ | ||
return; | ||
} | ||
|
||
// create the contexts if not done already | ||
if (grContext == null) | ||
{ | ||
var glInterface = GRGlInterface.Create(); | ||
grContext = GRContext.CreateGl(glInterface); | ||
} | ||
|
||
// get the new surface size | ||
var newSize = GetSize(); | ||
|
||
GL.ClearColor(Color4.Transparent); | ||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); | ||
|
||
// manage the drawing surface | ||
if (renderTarget == null || lastSize != newSize || !renderTarget.IsValid) | ||
{ | ||
|
||
// create or update the dimensions | ||
lastSize = newSize; | ||
|
||
GL.GetInteger(GetPName.FramebufferBinding, out var framebuffer); | ||
GL.GetInteger(GetPName.StencilBits, out var stencil); | ||
GL.GetInteger(GetPName.Samples, out var samples); | ||
var maxSamples = grContext.GetMaxSurfaceSampleCount(colorType); | ||
if (samples > maxSamples) | ||
samples = maxSamples; | ||
glInfo = new GRGlFramebufferInfo((uint)framebuffer, colorType.ToGlSizedFormat()); | ||
|
||
// destroy the old surface | ||
surface?.Dispose(); | ||
surface = null; | ||
canvas = null; | ||
|
||
// re-create the render target | ||
renderTarget?.Dispose(); | ||
renderTarget = new GRBackendRenderTarget(newSize.Width, newSize.Height, samples, stencil, glInfo); | ||
} | ||
|
||
// create the surface | ||
if (surface == null) | ||
{ | ||
surface = SKSurface.Create(grContext, renderTarget, surfaceOrigin, colorType); | ||
canvas = surface.Canvas; | ||
} | ||
|
||
using (new SKAutoCanvasRestore(canvas, true)) | ||
{ | ||
// start drawing | ||
OnPaintSurface(new SKPaintGLSurfaceEventArgs(surface, renderTarget, surfaceOrigin, colorType)); | ||
} | ||
|
||
// update the control | ||
canvas.Flush(); | ||
} | ||
|
||
protected virtual void OnPaintSurface(SKPaintGLSurfaceEventArgs e) | ||
{ | ||
// invoke the event | ||
PaintSurface?.Invoke(this, e); | ||
} | ||
|
||
private bool disposed = false; | ||
|
||
|
||
protected virtual void Dispose(bool disposing) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WPF UI worlds don't really use Dispose as a mechanism for doing things. Unload is resetting already? I am not 100% confident of how things work in the worlds of heavy graphics objects. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My recollection is that I was having an issue if the window was closed before the OpenTK stuff was disposed, which is why I was trying to hook the window close... But I haven't been able to repro revisiting it here. Maybe it had to do with me forcing OpenTK to create more than one glcontext associated with the window. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its conceivable that opentk manages any resources associated with the shared context of the shared window well enough that this code isn't necessary if I'm not spinning up other contexts.... although, now that I'm typing this, I wonder if I was prompted by any multi window wpf scenarios. Been a while since I was first putting this together at this point. |
||
{ | ||
if (disposed) | ||
{ | ||
return; | ||
} | ||
|
||
Release(); | ||
|
||
disposed = true; | ||
} | ||
|
||
private void Release() | ||
{ | ||
canvas = null; | ||
surface?.Dispose(); | ||
surface = null; | ||
renderTarget?.Dispose(); | ||
renderTarget = null; | ||
grContext?.Dispose(); | ||
grContext = null; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am thinking about this one... GL has a "make current" concept... I wonder if the draw method is supposed to make the context current before drawing.
I see this which looks familiar, but I have no idea how to get access to this one: https://github.com/opentk/GLWpfControl/blob/516bf697e9c786ab3432156d75e1dfca67d3a18c/src/GLWpfControl/DXGLContext.cs#L127
In the FAQ:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I tried before was to take a variant of that code you linked from inside OpenTK (that creates the shared context) to create additional contexts, and one per SKGLElement, Then each would make current before rendering the frame. IIRC that worked. But it required essentially recreating the context creation outside of OpenTK. I can't remember if I hit another limitation with it. This would probably work, I'm guessing, but would mean we are creating one GL context per control, and I'm not sure if that is desirable? This may be what the WF is doing under the covers though due to each being a separate HWND presumably? Not sure.
Anyhow, using the shared gl context from each SKGLElement was not working though without resetting the graphics context, since there were left over bits of state (maybe clipping stages?) that were crosstalking between the SKElements.
I'll see if I can find the old code that I was trying with the multiple gl contexts. I believe I posted it to discord in the past when I was asking for advice. The GL context creation was some variant of what is happening in that code you linked though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can merge this with the release for now, and then in a separate PR benchmark and see if switching context is better. I don't think we need to make a new context, but somehow we need to make sure the one for the control is make current before drawing.
When you create a new GRContext, it calls make current internally, so this is what is probably making it work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe there is a bug in the GL WPF Control, so we can also investigate that path. But, that can be done later.