From b3f5975cd8636b96c0689d756dd3f37039343df2 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 27 Jul 2020 18:46:04 +0200 Subject: [PATCH] Add the GPU views for Uno Platform (#1429) * Add the GPU views for the platforms * Add the render loop for the platforms * Fix the samples for infinite recursion * Added the GL views to the gallery --- .../Gallery/Shared/Samples/CreatePdfSample.cs | 3 +- .../Gallery/Shared/Samples/CreateXpsSample.cs | 3 +- .../Properties/AndroidManifest.xml | 2 +- .../Resources/values/styles.xml | 12 + .../Uno/SkiaSharpSample.Shared/MainPage.xaml | 9 + .../SkiaSharpSample.Shared/MainPage.xaml.cs | 30 ++- scripts/azure-pipelines.yml | 1 + .../SKSwapChainPanel.Android.cs | 57 +++++ .../SkiaSharp.Views.Uno.Android.csproj | 3 + .../SKSwapChainPanel.macOS.cs | 83 +++++++ .../SkiaSharp.Views.Uno.Mac.csproj | 1 + .../SKSwapChainPanel.Wasm.cs | 210 ++++++++++++++++++ .../WasmScripts/SkiaSharp.Views.Uno.Wasm.js | 142 ++++++++++++ .../SKSwapChainPanel.iOS.cs | 81 +++++++ .../SkiaSharp.Views.Uno.iOS.csproj | 1 + .../SkiaSharp.Views.Uno/SKSwapChainPanel.cs | 144 ++++++++++++ .../SkiaSharp.Views.Android/GLTextureView.cs | 11 +- .../SKGLTextureView.cs | 11 +- .../SKGLTextureViewRenderer.cs | 11 +- .../SkiaSharp.Views.AppleiOS/SKGLView.cs | 11 +- .../SkiaSharp.Views.Mac/SKGLView.cs | 14 +- .../GlesInterop/Gles.cs | 2 +- 22 files changed, 828 insertions(+), 14 deletions(-) create mode 100644 samples/Gallery/Uno/SkiaSharpSample.Android/Resources/values/styles.xml create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Android/SKSwapChainPanel.Android.cs create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SKSwapChainPanel.macOS.cs create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SKSwapChainPanel.iOS.cs create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno/SKSwapChainPanel.cs diff --git a/samples/Gallery/Shared/Samples/CreatePdfSample.cs b/samples/Gallery/Shared/Samples/CreatePdfSample.cs index e00f1f979..308ec7194 100644 --- a/samples/Gallery/Shared/Samples/CreatePdfSample.cs +++ b/samples/Gallery/Shared/Samples/CreatePdfSample.cs @@ -50,7 +50,7 @@ namespace SkiaSharpSample.Samples private void GenerateDocument() { - if (isSupported && File.Exists(path)) + if (!isSupported || (isSupported && File.Exists(path))) return; var metadata = new SKDocumentPdfMetadata @@ -70,7 +70,6 @@ namespace SkiaSharpSample.Samples if (document == null) { isSupported = false; - Refresh(); return; } diff --git a/samples/Gallery/Shared/Samples/CreateXpsSample.cs b/samples/Gallery/Shared/Samples/CreateXpsSample.cs index 6fddfb9a1..5dbec24da 100644 --- a/samples/Gallery/Shared/Samples/CreateXpsSample.cs +++ b/samples/Gallery/Shared/Samples/CreateXpsSample.cs @@ -52,7 +52,7 @@ namespace SkiaSharpSample.Samples private void GenerateDocument() { - if (isSupported && File.Exists(path)) + if (!isSupported || (isSupported && File.Exists(path))) return; using var document = SKDocument.CreateXps(path); @@ -60,7 +60,6 @@ namespace SkiaSharpSample.Samples if (document == null) { isSupported = false; - Refresh(); return; } diff --git a/samples/Gallery/Uno/SkiaSharpSample.Android/Properties/AndroidManifest.xml b/samples/Gallery/Uno/SkiaSharpSample.Android/Properties/AndroidManifest.xml index c859af2de..10670ed13 100644 --- a/samples/Gallery/Uno/SkiaSharpSample.Android/Properties/AndroidManifest.xml +++ b/samples/Gallery/Uno/SkiaSharpSample.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/Gallery/Uno/SkiaSharpSample.Android/Resources/values/styles.xml b/samples/Gallery/Uno/SkiaSharpSample.Android/Resources/values/styles.xml new file mode 100644 index 000000000..09b05bf16 --- /dev/null +++ b/samples/Gallery/Uno/SkiaSharpSample.Android/Resources/values/styles.xml @@ -0,0 +1,12 @@ + + + + diff --git a/samples/Gallery/Uno/SkiaSharpSample.Shared/MainPage.xaml b/samples/Gallery/Uno/SkiaSharpSample.Shared/MainPage.xaml index 17aa82c4c..7d2a40e25 100644 --- a/samples/Gallery/Uno/SkiaSharpSample.Shared/MainPage.xaml +++ b/samples/Gallery/Uno/SkiaSharpSample.Shared/MainPage.xaml @@ -44,6 +44,7 @@ + @@ -54,6 +55,14 @@ OverflowButtonVisibility="Collapsed" Foreground="White"> + + + + + + + + + glTextureView?.CanvasSize ?? SKSize.Empty; + + private GRContext GetGRContext() => + glTextureView?.GRContext; + + partial void DoLoaded() + { + glTextureView = new SKGLTextureView(Context); + DoEnableRenderLoop(EnableRenderLoop); + glTextureView.PaintSurface += OnPaintSurface; + AddView(glTextureView); + } + + partial void DoUnloaded() + { + if (glTextureView == null) + return; + + RemoveView(glTextureView); + glTextureView.PaintSurface -= OnPaintSurface; + glTextureView.Dispose(); + glTextureView = null; + } + + partial void DoEnableRenderLoop(bool enable) + { + if (glTextureView == null) + return; + + glTextureView.RenderMode = enable + ? Rendermode.Continuously + : Rendermode.WhenDirty; + } + + private void DoInvalidate() => + glTextureView?.RequestRender(); + + private void OnPaintSurface(object sender, SKPaintGLSurfaceEventArgs e) => + OnPaintSurface(e); + } +} diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Android/SkiaSharp.Views.Uno.Android.csproj b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Android/SkiaSharp.Views.Uno.Android.csproj index 44b1a1d2d..d12ec0721 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Android/SkiaSharp.Views.Uno.Android.csproj +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Android/SkiaSharp.Views.Uno.Android.csproj @@ -15,6 +15,9 @@ + + + diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SKSwapChainPanel.macOS.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SKSwapChainPanel.macOS.cs new file mode 100644 index 000000000..f4ed3f600 --- /dev/null +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SKSwapChainPanel.macOS.cs @@ -0,0 +1,83 @@ +using CoreVideo; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace SkiaSharp.Views.UWP +{ + public partial class SKSwapChainPanel : FrameworkElement + { + private SKGLView glView; + private CVDisplayLink displayLink; + + public SKSwapChainPanel() + { + Initialize(); + } + + private SKSize GetCanvasSize() => + glView?.CanvasSize ?? SKSize.Empty; + + private GRContext GetGRContext() => + glView?.GRContext; + + partial void DoLoaded() + { + glView = new SKGLView(Bounds); + glView.PaintSurface += OnPaintSurface; + AddSubview(glView); + } + + partial void DoUnloaded() + { + DoEnableRenderLoop(false); + + if (glView != null) + { + glView.RemoveFromSuperview(); + glView.PaintSurface -= OnPaintSurface; + glView.Dispose(); + glView = null; + } + } + + private void DoInvalidate() => + DoEnableRenderLoop(true); + + private void OnPaintSurface(object sender, SKPaintGLSurfaceEventArgs e) => + OnPaintSurface(e); + + partial void DoEnableRenderLoop(bool enable) + { + // stop the render loop + if (!enable) + { + if (displayLink != null) + { + displayLink.Stop(); + displayLink.Dispose(); + displayLink = null; + } + return; + } + + // only start if we haven't already + if (displayLink != null) + return; + + // create the loop + displayLink = new CVDisplayLink(); + displayLink.SetOutputCallback(delegate + { + // redraw the view + glView?.BeginInvokeOnMainThread(() => glView?.Display()); + + // stop the render loop if it has been disabled or the views are disposed + if (glView == null || !EnableRenderLoop) + DoEnableRenderLoop(false); + + return CVReturn.Success; + }); + displayLink.Start(); + } + } +} diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SkiaSharp.Views.Uno.Mac.csproj b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SkiaSharp.Views.Uno.Mac.csproj index 6e5501b18..ae44ed81c 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SkiaSharp.Views.Uno.Mac.csproj +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Mac/SkiaSharp.Views.Uno.Mac.csproj @@ -16,6 +16,7 @@ + diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs new file mode 100644 index 000000000..155ec446a --- /dev/null +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs @@ -0,0 +1,210 @@ +using System; +using System.Threading; +using Uno.Foundation; +using Uno.Foundation.Interop; +using Windows.UI.Xaml; + +namespace SkiaSharp.Views.UWP +{ + public partial class SKSwapChainPanel : FrameworkElement + { + private const int ResourceCacheBytes = 256 * 1024 * 1024; // 256 MB + private const SKColorType colorType = SKColorType.Rgba8888; + private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; + + private readonly SKSwapChainPanelJsInterop jsInterop; + + private GRGlInterface glInterface; + private GRContext context; + private JsInfo jsInfo; + private GRGlFramebufferInfo glInfo; + private GRBackendRenderTarget renderTarget; + private SKSurface surface; + private SKCanvas canvas; + + private SKSizeI lastSize; + + public SKSwapChainPanel() + : base("canvas") + { + jsInterop = new SKSwapChainPanelJsInterop(this); + Initialize(); + } + + private SKSize GetCanvasSize() => lastSize; + + private GRContext GetGRContext() => context; + + partial void DoLoaded() + { + jsInfo = jsInterop.CreateContext(); + + Invalidate(); + } + + partial void DoEnableRenderLoop(bool enable) => + jsInterop.SetEnableRenderLoop(enable); + + //partial void DoUpdateBounds() => + // jsInterop.ResizeCanvas(); + + private void DoInvalidate() + { + if (designMode) + return; + + if (!isVisible) + return; + + if ((int)ActualWidth <= 0 || (int)ActualHeight <= 0) + return; + + jsInterop.RequestAnimationFrame(EnableRenderLoop); + } + + internal void RenderFrame() + { + if (!jsInfo.IsValid) + return; + + // create the SkiaSharp context + if (context == null) + { + glInterface = GRGlInterface.Create(); + context = GRContext.CreateGl(glInterface); + + // bump the default resource cache limit + context.SetResourceCacheLimit(ResourceCacheBytes); + } + + // get the new surface size + var newSize = new SKSizeI((int)(ActualWidth * ContentsScale), (int)(ActualHeight * ContentsScale)); + + // manage the drawing surface + if (renderTarget == null || lastSize != newSize || !renderTarget.IsValid) + { + // create or update the dimensions + lastSize = newSize; + + glInfo = new GRGlFramebufferInfo(jsInfo.FboId, 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, jsInfo.Samples, jsInfo.Stencil, glInfo); + } + + // create the surface + if (surface == null) + { + surface = SKSurface.Create(context, renderTarget, surfaceOrigin, colorType); + canvas = surface.Canvas; + } + + using (new SKAutoCanvasRestore(canvas, true)) + { + // start drawing + OnPaintSurface(new SKPaintGLSurfaceEventArgs(surface, renderTarget, surfaceOrigin, colorType, glInfo)); + } + + // update the control + canvas.Flush(); + context.Flush(); + } + + private struct JsInfo + { + public bool IsValid { get; set; } + + public int ContextId { get; set; } + + public uint FboId { get; set; } + + public int Stencil { get; set; } + + public int Samples { get; set; } + + public int Depth { get; set; } + } + + private class SKSwapChainPanelJsInterop : IJSObject, IJSObjectMetadata + { + private static long handleCounter = 0L; + + private readonly long jsHandle; + + public SKSwapChainPanelJsInterop(SKSwapChainPanel panel) + { + Panel = panel ?? throw new ArgumentNullException(nameof(panel)); + + jsHandle = Interlocked.Increment(ref handleCounter); + Handle = JSObjectHandle.Create(this, this); + } + + public SKSwapChainPanel Panel { get; } + + public JSObjectHandle Handle { get; } + + public void RenderFrame() => + Panel.RenderFrame(); + + public void RequestAnimationFrame(bool renderLoop) => + WebAssemblyRuntime.InvokeJSWithInterop($"{this}.requestAnimationFrame({(renderLoop ? "true" : "false")});"); + + public void SetEnableRenderLoop(bool enable) => + WebAssemblyRuntime.InvokeJSWithInterop($"{this}.setEnableRenderLoop({(enable ? "true" : "false")});"); + + public void ResizeCanvas() => + WebAssemblyRuntime.InvokeJSWithInterop($"{this}.resizeCanvas();"); + + public JsInfo CreateContext() + { + var resultString = WebAssemblyRuntime.InvokeJSWithInterop($"return {this}.createContext('{Panel.HtmlId}');"); + var result = resultString?.Split(','); + if (result?.Length != 5) + return default; + + return new JsInfo + { + IsValid = true, + ContextId = int.Parse(result[0]), + FboId = uint.Parse(result[1]), + Stencil = int.Parse(result[2]), + Samples = int.Parse(result[3]), + Depth = int.Parse(result[4]), + }; + } + + long IJSObjectMetadata.CreateNativeInstance(IntPtr managedHandle) + { + WebAssemblyRuntime.InvokeJS($"SkiaSharp.Views.UWP.SKSwapChainPanel.createInstance('{managedHandle}', '{jsHandle}')"); + return jsHandle; + } + + string IJSObjectMetadata.GetNativeInstance(IntPtr managedHandle, long jsHandle) => + $"SkiaSharp.Views.UWP.SKSwapChainPanel.getInstance('{jsHandle}')"; + + void IJSObjectMetadata.DestroyNativeInstance(IntPtr managedHandle, long jsHandle) => + WebAssemblyRuntime.InvokeJS($"SkiaSharp.Views.UWP.SKSwapChainPanel.destroyInstance('{jsHandle}')"); + + object IJSObjectMetadata.InvokeManaged(object instance, string method, string parameters) + { + switch (method) + { + case nameof(RenderFrame): + RenderFrame(); + break; + + default: + throw new ArgumentException($"Unable to execute method: {method}", nameof(method)); + } + + return null; + } + } + } +} diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js index e9dc668df..de4ad9f68 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js @@ -22,7 +22,149 @@ return true; } } + + class SKSwapChainPanel { + static activeInstances = {}; + + constructor(managedHandle) { + this.managedHandle = managedHandle; + this.canvas = undefined; + this.jsInfo = undefined; + this.renderLoop = false; + this.currentRequest = 0; + } + + // JSObject + static createInstance(managedHandle, jsHandle) { + SKSwapChainPanel.activeInstances[jsHandle] = new SKSwapChainPanel(managedHandle); + } + static getInstance(jsHandle) { + return SKSwapChainPanel.activeInstances[jsHandle]; + } + static destroyInstance(jsHandle) { + delete SKSwapChainPanel.activeInstances[jsHandle]; + } + + requestAnimationFrame(renderLoop) { + // optionally update the render loop + if (renderLoop !== undefined && this.renderLoop !== renderLoop) + this.setEnableRenderLoop(renderLoop); + + // skip because we have a render loop + if (this.currentRequest !== 0) + return; + + // make sure the canvas is scaled correctly for the drawing + this.resizeCanvas(); + + // add the draw to the next frame + this.currentRequest = window.requestAnimationFrame(() => { + Uno.Foundation.Interop.ManagedObject.dispatch(this.managedHandle, 'RenderFrame', null); + + this.currentRequest = 0; + + // we may want to draw the next frame + if (this.renderLoop) + this.requestAnimationFrame(); + }); + } + + resizeCanvas() { + if (!this.canvas) + return; + + var scale = window.devicePixelRatio || 1; + var w = this.canvas.clientWidth * scale + var h = this.canvas.clientHeight * scale; + + if (this.canvas.width !== w) + this.canvas.width = w; + if (this.canvas.height !== h) + this.canvas.height = h; + } + + setEnableRenderLoop(enable) { + this.renderLoop = enable; + + // either start the new frame or cancel the existing one + if (enable) { + this.requestAnimationFrame(); + } else if (this.currentRequest !== 0) { + window.cancelAnimationFrame(this.currentRequest); + this.currentRequest = 0; + } + } + + createContext(canvasOrCanvasId) { + if (!canvasOrCanvasId) + throw 'No element or ID was provided'; + + var canvas = canvasOrCanvasId; + if (canvas.tagName !== 'CANVAS') { + canvas = document.getElementById(canvasOrCanvasId); + if (!canvas) + throw `No with id ${canvasOrCanvasId} was found`; + } + + var ctx = SKSwapChainPanel.createWebGLContext(canvas); + if (!ctx || ctx < 0) + throw `Failed to create WebGL context: err ${ctx}`; + + // make current + GL.makeContextCurrent(ctx); + + // read values + this.canvas = canvas; + var info = { + ctx: ctx, + fbo: GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING), + stencil: GLctx.getParameter(GLctx.STENCIL_BITS), + sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES) + depth: GLctx.getParameter(GLctx.DEPTH_BITS), + }; + + // format as array for nicer parsing + this.jsInfo = [ + info.ctx, + info.fbo ? info.fbo.id : 0, + info.stencil, + info.sample, + info.depth, + ]; + return this.jsInfo; + } + + static createWebGLContext(canvas) { + var contextAttributes = { + alpha: 1, + depth: 1, + stencil: 8, + antialias: 1, + premultipliedAlpha: 1, + preserveDrawingBuffer: 0, + preferLowPowerToHighPerformance: 0, + failIfMajorPerformanceCaveat: 0, + majorVersion: 2, + minorVersion: 0, + enableExtensionsByDefault: 1, + explicitSwapControl: 0, + renderViaOffscreenBackBuffer: 0, + }; + + var ctx = GL.createContext(canvas, contextAttributes); + if (!ctx && contextAttributes.majorVersion > 1) { + console.warn('Falling back to WebGL 1.0'); + contextAttributes.majorVersion = 1; + contextAttributes.minorVersion = 0; + ctx = GL.createContext(canvas, contextAttributes); + } + + return ctx; + } + } + UWP.SKXamlCanvas = SKXamlCanvas; + UWP.SKSwapChainPanel = SKSwapChainPanel; })(UWP = Views.UWP || (Views.UWP = {})); })(Views = SkiaSharp.Views || (SkiaSharp.Views = {})); diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SKSwapChainPanel.iOS.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SKSwapChainPanel.iOS.cs new file mode 100644 index 000000000..9401a8399 --- /dev/null +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SKSwapChainPanel.iOS.cs @@ -0,0 +1,81 @@ +using CoreAnimation; +using Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace SkiaSharp.Views.UWP +{ + public partial class SKSwapChainPanel : FrameworkElement + { + private SKGLView glView; + private CADisplayLink displayLink; + + public SKSwapChainPanel() + { + Initialize(); + } + + private SKSize GetCanvasSize() => + glView?.CanvasSize ?? SKSize.Empty; + + private GRContext GetGRContext() => + glView?.GRContext; + + partial void DoLoaded() + { + glView = new SKGLView(Bounds); + glView.PaintSurface += OnPaintSurface; + AddSubview(glView); + } + + partial void DoUnloaded() + { + DoEnableRenderLoop(false); + + if (glView != null) + { + glView.RemoveFromSuperview(); + glView.PaintSurface -= OnPaintSurface; + glView.Dispose(); + glView = null; + } + } + + private void DoInvalidate() => + DoEnableRenderLoop(true); + + private void OnPaintSurface(object sender, SKPaintGLSurfaceEventArgs e) => + OnPaintSurface(e); + + partial void DoEnableRenderLoop(bool enable) + { + // stop the render loop + if (!enable) + { + if (displayLink != null) + { + displayLink.Invalidate(); + displayLink.Dispose(); + displayLink = null; + } + return; + } + + // only start if we haven't already + if (displayLink != null) + return; + + // create the loop + displayLink = CADisplayLink.Create(delegate + { + // redraw the view + glView?.BeginInvokeOnMainThread(() => glView?.Display()); + + // stop the render loop if it has been disabled or the views are disposed + if (glView == null || !EnableRenderLoop) + DoEnableRenderLoop(false); + }); + displayLink.AddToRunLoop(NSRunLoop.Current, NSRunLoop.NSDefaultRunLoopMode); + } + } +} diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SkiaSharp.Views.Uno.iOS.csproj b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SkiaSharp.Views.Uno.iOS.csproj index 8e26b35f7..bcaab6530 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SkiaSharp.Views.Uno.iOS.csproj +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.iOS/SkiaSharp.Views.Uno.iOS.csproj @@ -15,6 +15,7 @@ + diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno/SKSwapChainPanel.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno/SKSwapChainPanel.cs new file mode 100644 index 000000000..38db166ca --- /dev/null +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno/SKSwapChainPanel.cs @@ -0,0 +1,144 @@ +using System; +using Uno; +using Windows.ApplicationModel; +using Windows.Graphics.Display; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace SkiaSharp.Views.UWP +{ + public partial class SKSwapChainPanel : FrameworkElement + { + private static readonly DependencyProperty ProxyVisibilityProperty = + DependencyProperty.Register( + "ProxyVisibility", + typeof(Visibility), + typeof(SKSwapChainPanel), + new PropertyMetadata(Visibility.Visible, OnVisibilityChanged)); + + private static bool designMode = DesignMode.DesignModeEnabled; + + private bool isVisible = true; + private bool enableRenderLoop = false; + + // workaround for https://github.com/mono/SkiaSharp/issues/1118 + private int loadUnloadCounter = 0; + + private void Initialize() + { + if (designMode) + return; + + var display = DisplayInformation.GetForCurrentView(); + OnDpiChanged(display); + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + SizeChanged += OnSizeChanged; + + var binding = new Binding + { + Path = new PropertyPath(nameof(Visibility)), + Source = this + }; + SetBinding(ProxyVisibilityProperty, binding); + } + + public SKSize CanvasSize => GetCanvasSize(); + + public GRContext GRContext => GetGRContext(); + + public double ContentsScale { get; private set; } + + [NotImplemented] + public bool DrawInBackground + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public bool EnableRenderLoop + { + get => enableRenderLoop; + set + { + if (enableRenderLoop != value) + { + enableRenderLoop = value; + DoEnableRenderLoop(enableRenderLoop); + } + } + } + + public new void Invalidate() + { + _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, DoInvalidate); + } + + public event EventHandler PaintSurface; + + protected virtual void OnPaintSurface(SKPaintGLSurfaceEventArgs e) + { + // invoke the event + PaintSurface?.Invoke(this, e); + } + + private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is SKSwapChainPanel canvas && e.NewValue is Visibility visibility) + { + canvas.isVisible = visibility == Visibility.Visible; + canvas.DoUpdateBounds(); + canvas.Invalidate(); + } + } + + private void OnDpiChanged(DisplayInformation sender, object args = null) + { + ContentsScale = sender.LogicalDpi / 96.0f; + DoUpdateBounds(); + Invalidate(); + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + DoUpdateBounds(); + Invalidate(); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + loadUnloadCounter++; + if (loadUnloadCounter != 1) + return; + + DoLoaded(); + + var display = DisplayInformation.GetForCurrentView(); + display.DpiChanged += OnDpiChanged; + + OnDpiChanged(display); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + loadUnloadCounter--; + if (loadUnloadCounter != 0) + return; + + DoUnloaded(); + + var display = DisplayInformation.GetForCurrentView(); + display.DpiChanged -= OnDpiChanged; + } + + partial void DoLoaded(); + + partial void DoUnloaded(); + + partial void DoUpdateBounds(); + + partial void DoEnableRenderLoop(bool enable); + } +} diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Android/GLTextureView.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Android/GLTextureView.cs index 9ecf0359e..98a11e644 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Android/GLTextureView.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Android/GLTextureView.cs @@ -18,9 +18,18 @@ using EGLContext = Javax.Microedition.Khronos.Egl.EGLContext; using EGLDisplay = Javax.Microedition.Khronos.Egl.EGLDisplay; using EGLSurface = Javax.Microedition.Khronos.Egl.EGLSurface; +#if HAS_UNO +namespace SkiaSharp.Views.UWP +#else namespace SkiaSharp.Views.Android +#endif { - public class GLTextureView : TextureView, TextureView.ISurfaceTextureListener, View.IOnLayoutChangeListener +#if HAS_UNO + internal +#else + public +#endif + partial class GLTextureView : TextureView, TextureView.ISurfaceTextureListener, View.IOnLayoutChangeListener { private const bool EnableLogging = false; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureView.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureView.cs index 64578379a..827f43727 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureView.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureView.cs @@ -4,9 +4,18 @@ using Android.Content; using Android.Opengl; using Android.Util; +#if HAS_UNO +namespace SkiaSharp.Views.UWP +#else namespace SkiaSharp.Views.Android +#endif { - public class SKGLTextureView : GLTextureView +#if HAS_UNO + internal +#else + public +#endif + partial class SKGLTextureView : GLTextureView { private SKGLTextureViewRenderer renderer; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureViewRenderer.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureViewRenderer.cs index 3dab38ba4..ff0587a28 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureViewRenderer.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Android/SKGLTextureViewRenderer.cs @@ -5,9 +5,18 @@ using Javax.Microedition.Khronos.Opengles; using EGLConfig = Javax.Microedition.Khronos.Egl.EGLConfig; +#if HAS_UNO +namespace SkiaSharp.Views.UWP +#else namespace SkiaSharp.Views.Android +#endif { - public abstract class SKGLTextureViewRenderer : Java.Lang.Object, GLTextureView.IRenderer +#if HAS_UNO + internal +#else + public +#endif + abstract partial class SKGLTextureViewRenderer : Java.Lang.Object, GLTextureView.IRenderer { private const SKColorType colorType = SKColorType.Rgba8888; private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.AppleiOS/SKGLView.cs b/source/SkiaSharp.Views/SkiaSharp.Views.AppleiOS/SKGLView.cs index f6dee0f71..b85464b69 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.AppleiOS/SKGLView.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.AppleiOS/SKGLView.cs @@ -8,7 +8,9 @@ using GLKit; using OpenGLES; using SkiaSharp.Views.GlesInterop; -#if __TVOS__ +#if HAS_UNO +namespace SkiaSharp.Views.UWP +#elif __TVOS__ namespace SkiaSharp.Views.tvOS #elif __IOS__ namespace SkiaSharp.Views.iOS @@ -16,7 +18,12 @@ namespace SkiaSharp.Views.iOS { [Register(nameof(SKGLView))] [DesignTimeVisible(true)] - public class SKGLView : GLKView, IGLKViewDelegate, IComponent +#if HAS_UNO + internal +#else + public +#endif + class SKGLView : GLKView, IGLKViewDelegate, IComponent { private const SKColorType colorType = SKColorType.Rgba8888; private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Mac/SKGLView.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Mac/SKGLView.cs index 50bb69db6..b3f22c6a2 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Mac/SKGLView.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Mac/SKGLView.cs @@ -5,11 +5,20 @@ using CoreGraphics; using Foundation; using SkiaSharp.Views.GlesInterop; +#if HAS_UNO +namespace SkiaSharp.Views.UWP +#else namespace SkiaSharp.Views.Mac +#endif { [Register(nameof(SKGLView))] [DesignTimeVisible(true)] - public class SKGLView : NSOpenGLView +#if HAS_UNO + internal +#else + public +#endif + partial class SKGLView : NSOpenGLView { private const SKColorType colorType = SKColorType.Rgba8888; private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; @@ -88,7 +97,8 @@ namespace SkiaSharp.Views.Mac base.Reshape(); // get the new surface size - newSize = ConvertSizeToBacking(Bounds.Size).ToSKSize().ToSizeI(); + var size = ConvertSizeToBacking(Bounds.Size); + newSize = new SKSizeI((int)size.Width, (int)size.Height); } public override void DrawRect(CGRect dirtyRect) diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Shared/GlesInterop/Gles.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Shared/GlesInterop/Gles.cs index 409b1c76a..2c9fe52fd 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Shared/GlesInterop/Gles.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Shared/GlesInterop/Gles.cs @@ -1,4 +1,4 @@ -#if !__WATCHOS__ && !HAS_UNO +#if !__WATCHOS__ && !__WASM__ using System.Runtime.InteropServices; namespace SkiaSharp.Views.GlesInterop