Skip to content

Commit

Permalink
Fix memory leaks in MAUI views (#2955)
Browse files Browse the repository at this point in the history
Fixes #2923
  • Loading branch information
mattleibow authored Jul 26, 2024
1 parent d3a6ae9 commit ccde024
Show file tree
Hide file tree
Showing 13 changed files with 596 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#nullable enable

using System;

using System.ComponentModel;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
Expand All @@ -10,6 +10,9 @@ namespace SkiaSharp.Views.Maui.Controls
{
public partial class SKGLView : View, ISKGLView
{
private static readonly BindableProperty ProxyWindowProperty =
BindableProperty.Create("ProxyWindow", typeof(Window), typeof(SKGLView), propertyChanged: OnWindowChanged);

public static readonly BindableProperty IgnorePixelScalingProperty =
BindableProperty.Create(nameof(IgnorePixelScaling), typeof(bool), typeof(SKGLView), false);

Expand All @@ -22,6 +25,12 @@ public partial class SKGLView : View, ISKGLView
private SKSizeI lastCanvasSize;
private GRContext? lastGRContext;

public SKGLView()
{
var binding = new Binding(nameof(Window), source: this);
SetBinding(ProxyWindowProperty, binding);
}

public bool IgnorePixelScaling
{
get => (bool)GetValue(IgnorePixelScalingProperty);
Expand Down Expand Up @@ -63,6 +72,17 @@ protected virtual void OnTouch(SKTouchEventArgs e)
Touch?.Invoke(this, e);
}

private static void OnWindowChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is not SKGLView view)
return;

view.Handler?.UpdateValue(nameof(HasRenderLoop));
}

bool ISKGLView.HasRenderLoop =>
HasRenderLoop && Window is not null;

void ISKGLView.OnCanvasSizeChanged(SKSizeI size) =>
lastCanvasSize = size;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,27 @@ namespace SkiaSharp.Views.Maui.Handlers
{
public partial class SKCanvasViewHandler : ViewHandler<ISKCanvasView, SKCanvasView>
{
private SKSizeI lastCanvasSize;
private SKTouchHandler? touchHandler;
private PaintSurfaceProxy? paintSurfaceProxy;
private SKTouchHandlerProxy? touchProxy;

protected override SKCanvasView CreatePlatformView() => new SKCanvasView { BackgroundColor = UIColor.Clear };

protected override void ConnectHandler(SKCanvasView platformView)
{
platformView.PaintSurface += OnPaintSurface;
paintSurfaceProxy = new();
paintSurfaceProxy.Connect(VirtualView, platformView);
touchProxy = new();
touchProxy.Connect(VirtualView, platformView);

base.ConnectHandler(platformView);
}

protected override void DisconnectHandler(SKCanvasView platformView)
{
touchHandler?.Detach(platformView);
touchHandler = null;

platformView.PaintSurface -= OnPaintSurface;
paintSurfaceProxy?.Disconnect(platformView);
paintSurfaceProxy = null;
touchProxy?.Disconnect(platformView);
touchProxy = null;

base.DisconnectHandler(platformView);
}
Expand All @@ -43,38 +46,35 @@ public static void MapIgnorePixelScaling(SKCanvasViewHandler handler, ISKCanvasV

public static void MapEnableTouchEvents(SKCanvasViewHandler handler, ISKCanvasView canvasView)
{
handler.touchHandler ??= new SKTouchHandler(
args => canvasView.OnTouch(args),
(x, y) => handler.OnGetScaledCoord(x, y));

handler.touchHandler?.SetEnabled(handler.PlatformView, canvasView.EnableTouchEvents);
handler.touchProxy?.UpdateEnableTouchEvents(handler.PlatformView, canvasView.EnableTouchEvents);
}

// helper methods

private void OnPaintSurface(object? sender, iOS.SKPaintSurfaceEventArgs e)
private class PaintSurfaceProxy : SKEventProxy<ISKCanvasView, SKCanvasView>
{
var newCanvasSize = e.Info.Size;
if (lastCanvasSize != newCanvasSize)
{
lastCanvasSize = newCanvasSize;
VirtualView?.OnCanvasSizeChanged(newCanvasSize);
}
private SKSizeI lastCanvasSize;

VirtualView?.OnPaintSurface(new SKPaintSurfaceEventArgs(e.Surface, e.Info, e.RawInfo));
}
protected override void OnConnect(ISKCanvasView virtualView, SKCanvasView platformView) =>
platformView.PaintSurface += OnPaintSurface;

private SKPoint OnGetScaledCoord(double x, double y)
{
if (VirtualView?.IgnorePixelScaling == false && PlatformView != null)
protected override void OnDisconnect(SKCanvasView platformView) =>
platformView.PaintSurface -= OnPaintSurface;

private void OnPaintSurface(object? sender, iOS.SKPaintSurfaceEventArgs e)
{
var scale = PlatformView.ContentScaleFactor;
if (VirtualView is not {} view)
return;

x *= scale;
y *= scale;
}
var newCanvasSize = e.Info.Size;
if (lastCanvasSize != newCanvasSize)
{
lastCanvasSize = newCanvasSize;
view.OnCanvasSizeChanged(newCanvasSize);
}

return new SKPoint((float)x, (float)y);
view.OnPaintSurface(new SKPaintSurfaceEventArgs(e.Surface, e.Info, e.RawInfo));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ namespace SkiaSharp.Views.Maui.Handlers
{
public partial class SKGLViewHandler : ViewHandler<ISKGLView, SKMetalView>
{
private SKSizeI lastCanvasSize;
private GRContext? lastGRContext;
private SKTouchHandler? touchHandler;
private PaintSurfaceProxy? paintSurfaceProxy;
private SKTouchHandlerProxy? touchProxy;

protected override SKMetalView CreatePlatformView() =>
new MauiSKMetalView
Expand All @@ -21,17 +20,20 @@ protected override SKMetalView CreatePlatformView() =>

protected override void ConnectHandler(SKMetalView platformView)
{
platformView.PaintSurface += OnPaintSurface;
paintSurfaceProxy = new();
paintSurfaceProxy.Connect(VirtualView, platformView);
touchProxy = new();
touchProxy.Connect(VirtualView, platformView);

base.ConnectHandler(platformView);
}

protected override void DisconnectHandler(SKMetalView platformView)
{
touchHandler?.Detach(platformView);
touchHandler = null;

platformView.PaintSurface -= OnPaintSurface;
paintSurfaceProxy?.Disconnect(platformView);
paintSurfaceProxy = null;
touchProxy?.Disconnect(platformView);
touchProxy = null;

base.DisconnectHandler(platformView);
}
Expand Down Expand Up @@ -61,49 +63,11 @@ public static void MapHasRenderLoop(SKGLViewHandler handler, ISKGLView view)

public static void MapEnableTouchEvents(SKGLViewHandler handler, ISKGLView view)
{
handler.touchHandler ??= new SKTouchHandler(
args => view.OnTouch(args),
(x, y) => handler.OnGetScaledCoord(x, y));

handler.touchHandler?.SetEnabled(handler.PlatformView, view.EnableTouchEvents);
handler.touchProxy?.UpdateEnableTouchEvents(handler.PlatformView, view.EnableTouchEvents);
}

// helper methods

private void OnPaintSurface(object? sender, iOS.SKPaintMetalSurfaceEventArgs e)
{
var newCanvasSize = e.Info.Size;
if (lastCanvasSize != newCanvasSize)
{
lastCanvasSize = newCanvasSize;
VirtualView?.OnCanvasSizeChanged(newCanvasSize);
}
if (sender is SKMetalView platformView)
{
var newGRContext = platformView.GRContext;
if (lastGRContext != newGRContext)
{
lastGRContext = newGRContext;
VirtualView?.OnGRContextChanged(newGRContext);
}
}

VirtualView?.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info, e.RawInfo));
}

private SKPoint OnGetScaledCoord(double x, double y)
{
if (VirtualView?.IgnorePixelScaling == false && PlatformView != null)
{
var scale = PlatformView.ContentScaleFactor;

x *= scale;
y *= scale;
}

return new SKPoint((float)x, (float)y);
}

private class MauiSKMetalView : SKMetalView
{
public bool IgnorePixelScaling { get; set; }
Expand All @@ -123,5 +87,41 @@ protected override void OnPaintSurface(iOS.SKPaintMetalSurfaceEventArgs e)
base.OnPaintSurface(e);
}
}

private class PaintSurfaceProxy : SKEventProxy<ISKGLView, SKMetalView>
{
private SKSizeI lastCanvasSize;
private GRContext? lastGRContext;

protected override void OnConnect(ISKGLView virtualView, SKMetalView platformView) =>
platformView.PaintSurface += OnPaintSurface;

protected override void OnDisconnect(SKMetalView platformView) =>
platformView.PaintSurface -= OnPaintSurface;

private void OnPaintSurface(object? sender, iOS.SKPaintMetalSurfaceEventArgs e)
{
if (VirtualView is not {} view)
return;

var newCanvasSize = e.Info.Size;
if (lastCanvasSize != newCanvasSize)
{
lastCanvasSize = newCanvasSize;
view.OnCanvasSizeChanged(newCanvasSize);
}
if (sender is SKMetalView platformView)
{
var newGRContext = platformView.GRContext;
if (lastGRContext != newGRContext)
{
lastGRContext = newGRContext;
view.OnGRContextChanged(newGRContext);
}
}

view.OnPaintSurface(new SKPaintGLSurfaceEventArgs(e.Surface, e.BackendRenderTarget, e.Origin, e.Info, e.RawInfo));
}
}
}
}
Loading

0 comments on commit ccde024

Please sign in to comment.