From d6ae135432a43e676e54cb16261f516732f748c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Mon, 30 May 2022 11:33:02 -0400 Subject: [PATCH] fix(unowasm): Adds missing Uno.WinUI javascript support file (#2086) --- .../SKSwapChainPanel.Wasm.cs | 12 +- .../SKXamlCanvas.Wasm.cs | 8 +- .../SkiaSharp.Views.Uno.WinUI.Wasm.csproj | 2 + .../WasmScripts/SkiaSharp.Views.Uno.Wasm.js | 171 ++++++++++++++++++ 4 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js 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 index b5d4a15ae6..a454f0962c 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKSwapChainPanel.Wasm.cs @@ -18,6 +18,12 @@ namespace SkiaSharp.Views.UWP [HtmlElement("canvas")] public partial class SKSwapChainPanel : FrameworkElement { +#if HAS_UNO_WINUI + const string SKSwapChainPanelTypeFullName = "SkiaSharp.Views.Windows." + nameof(SKSwapChainPanel); +#else + const string SKSwapChainPanelTypeFullName = "SkiaSharp.Views.UWP" + nameof(SKSwapChainPanel); +#endif + private const int ResourceCacheBytes = 256 * 1024 * 1024; // 256 MB private const SKColorType colorType = SKColorType.Rgba8888; private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; @@ -192,15 +198,15 @@ public JsInfo CreateContext() long IJSObjectMetadata.CreateNativeInstance(IntPtr managedHandle) { - WebAssemblyRuntime.InvokeJS($"SkiaSharp.Views.UWP.SKSwapChainPanel.createInstance('{managedHandle}', '{jsHandle}')"); + WebAssemblyRuntime.InvokeJS(SKSwapChainPanelTypeFullName + $".createInstance('{managedHandle}', '{jsHandle}')"); return jsHandle; } string IJSObjectMetadata.GetNativeInstance(IntPtr managedHandle, long jsHandle) => - $"SkiaSharp.Views.UWP.SKSwapChainPanel.getInstance('{jsHandle}')"; + SKSwapChainPanelTypeFullName + $".getInstance('{jsHandle}')"; void IJSObjectMetadata.DestroyNativeInstance(IntPtr managedHandle, long jsHandle) => - WebAssemblyRuntime.InvokeJS($"SkiaSharp.Views.UWP.SKSwapChainPanel.destroyInstance('{jsHandle}')"); + WebAssemblyRuntime.InvokeJS(SKSwapChainPanelTypeFullName + $".destroyInstance('{jsHandle}')"); object IJSObjectMetadata.InvokeManaged(object instance, string method, string parameters) { diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs index 76d08d3340..7d596cf199 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.Wasm/SKXamlCanvas.Wasm.cs @@ -17,6 +17,12 @@ namespace SkiaSharp.Views.UWP [HtmlElement("canvas")] public partial class SKXamlCanvas { +#if HAS_UNO_WINUI + const string SKXamlCanvasFullTypeName = "SkiaSharp.Views.Windows." + nameof(SKXamlCanvas); +#else + const string SKXamlCanvasFullTypeName = "SkiaSharp.Views.UWP." + nameof(SKXamlCanvas); +#endif + private byte[] pixels; private GCHandle pixelsHandle; private int pixelWidth; @@ -61,7 +67,7 @@ private void DoInvalidate() OnPaintSurface(new SKPaintSurfaceEventArgs(surface, info.WithSize(userVisibleSize), info)); } - WebAssemblyRuntime.InvokeJS($"SkiaSharp.Views.UWP.SKXamlCanvas.invalidateCanvas({pixelsHandle.AddrOfPinnedObject()}, \"{this.GetHtmlId()}\", {info.Width}, {pixelHeight});"); + WebAssemblyRuntime.InvokeJS(SKXamlCanvasFullTypeName + $".invalidateCanvas({pixelsHandle.AddrOfPinnedObject()}, \"{this.GetHtmlId()}\", {info.Width}, {pixelHeight});"); } private SKImageInfo CreateBitmap(out SKSizeI unscaledSize, out float dpi) diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/SkiaSharp.Views.Uno.WinUI.Wasm.csproj b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/SkiaSharp.Views.Uno.WinUI.Wasm.csproj index ccac88e0be..396f2c1c08 100644 --- a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/SkiaSharp.Views.Uno.WinUI.Wasm.csproj +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/SkiaSharp.Views.Uno.WinUI.Wasm.csproj @@ -19,6 +19,7 @@ + @@ -32,5 +33,6 @@ $(AssemblyName).xml + diff --git a/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js new file mode 100644 index 0000000000..d3cd5704ba --- /dev/null +++ b/source/SkiaSharp.Views.Uno/SkiaSharp.Views.Uno.WinUI.Wasm/WasmScripts/SkiaSharp.Views.Uno.Wasm.js @@ -0,0 +1,171 @@ +var SkiaSharp; +(function (SkiaSharp) { + var Views; + (function (Views) { + var Windows; + (function (Windows) { + + class SKXamlCanvas { + static invalidateCanvas(pData, canvasId, width, height) { + var htmlCanvas = document.getElementById(canvasId); + htmlCanvas.width = width; + htmlCanvas.height = height; + + var ctx = htmlCanvas.getContext('2d'); + if (!ctx) + return false; + + var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); + var imageData = new ImageData(buffer, width, height); + ctx.putImageData(imageData, 0, 0); + + 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; + } + } + + Windows.SKXamlCanvas = SKXamlCanvas; + Windows.SKSwapChainPanel = SKSwapChainPanel; + + })(Windows = Views.Windows || (Views.Windows = {})); + })(Views = SkiaSharp.Views || (SkiaSharp.Views = {})); +})(SkiaSharp || (SkiaSharp = {}));