diff --git a/scripts/azure-pipelines-complete-internal.yml b/scripts/azure-pipelines-complete-internal.yml index 2b8d263e6b..bb66718e6e 100644 --- a/scripts/azure-pipelines-complete-internal.yml +++ b/scripts/azure-pipelines-complete-internal.yml @@ -45,7 +45,7 @@ parameters: default: pool: name: Azure Pipelines - vmImage: macos-13 + vmImage: macos-14 os: macos - name: buildAgentLinux displayName: 'The Linux build agent configuration:' @@ -144,7 +144,7 @@ extends: buildAgentWindows: ${{ parameters.buildAgentWindows }} buildAgentWindowsNative: ${{ parameters.buildAgentWindows }} buildAgentMac: ${{ parameters.buildAgentMac }} - buildAgentMacNative: ${{ parameters.buildAgentMac }} + buildAgentMacNative: ${{ parameters.buildAgentMacNative }} buildAgentLinux: ${{ parameters.buildAgentLinux }} buildAgentLinuxNative: ${{ parameters.buildAgentLinuxNative }} buildAgentAndroidTests: ${{ parameters.buildAgentAndroidTests }} diff --git a/scripts/azure-pipelines-complete.yml b/scripts/azure-pipelines-complete.yml index 25a7b356c1..f2be8f566f 100644 --- a/scripts/azure-pipelines-complete.yml +++ b/scripts/azure-pipelines-complete.yml @@ -51,7 +51,7 @@ parameters: default: pool: name: Azure Pipelines - vmImage: macos-13 + vmImage: macos-14 os: macos - name: buildAgentLinux displayName: 'The Linux build agent configuration:' diff --git a/scripts/azure-pipelines-variables.yml b/scripts/azure-pipelines-variables.yml index 754b7dc087..f268685191 100644 --- a/scripts/azure-pipelines-variables.yml +++ b/scripts/azure-pipelines-variables.yml @@ -13,7 +13,7 @@ variables: TIZEN_LINUX_PACKAGES: libxcb-icccm4 libxcb-render-util0 gettext libxcb-image0 libsdl1.2debian libv4l-0 libxcb-randr0 bridge-utils libxcb-shape0 libpython2.7 openvpn libkf5itemmodels5 libkf5kiowidgets5 libkchart2 MANAGED_LINUX_PACKAGES: ttf-ancient-fonts ninja-build XCODE_VERSION: '15.4' - XCODE_VERSION_NATIVE: '14.3.1' + XCODE_VERSION_NATIVE: '15.4' VISUAL_STUDIO_VERSION: '' DOTNET_VERSION: '8.0.304' DOTNET_VERSION_PREVIEW: '' diff --git a/scripts/azure-pipelines.yml b/scripts/azure-pipelines.yml index 65e618fc50..f37d76ee8a 100644 --- a/scripts/azure-pipelines.yml +++ b/scripts/azure-pipelines.yml @@ -51,7 +51,7 @@ parameters: default: pool: name: Azure Pipelines - vmImage: macos-13 + vmImage: macos-14 os: macos - name: buildAgentLinux displayName: 'The Linux build agent configuration:' diff --git a/scripts/azure-templates-stages.yml b/scripts/azure-templates-stages.yml index 97ef62373b..ff126e8d78 100644 --- a/scripts/azure-templates-stages.yml +++ b/scripts/azure-templates-stages.yml @@ -458,11 +458,11 @@ stages: - 3.1.56: displayName: '3.1.56_SIMD' version: 3.1.56 - features: _wasmeh,simd,st + features: _wasmeh,st,simd - 3.1.56: displayName: '3.1.56_SIMD_Threading' version: 3.1.56 - features: _wasmeh,simd,mt + features: _wasmeh,mt,simd - ${{ if ne(parameters.buildPipelineType, 'tests') }}: - stage: native diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/ActionHelper.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/ActionHelper.cs index 29a39cf0f6..c9c705de28 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/ActionHelper.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/ActionHelper.cs @@ -1,4 +1,5 @@ -using System; +#if !NET7_0_OR_GREATER +using System; using System.ComponentModel; using Microsoft.JSInterop; @@ -18,3 +19,4 @@ public ActionHelper(Action action) public void Invoke() => action?.Invoke(); } } +#endif diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/DpiWatcherInterop.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/DpiWatcherInterop.cs index 80223f31fa..a5553c01df 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/DpiWatcherInterop.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/DpiWatcherInterop.cs @@ -1,11 +1,18 @@ -using System; +using System; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.JSInterop; +#if NET7_0_OR_GREATER +using System.Runtime.InteropServices.JavaScript; +#endif + namespace SkiaSharp.Views.Blazor.Internal { - internal class DpiWatcherInterop : JSModuleInterop + [SupportedOSPlatform("browser")] + internal partial class DpiWatcherInterop : JSModuleInterop { + private const string ModuleName = "DpiWatcher"; private const string JsFilename = "./_content/SkiaSharp.Views.Blazor/DpiWatcher.js"; private const string StartSymbol = "DpiWatcher.start"; private const string StopSymbol = "DpiWatcher.stop"; @@ -14,9 +21,13 @@ internal class DpiWatcherInterop : JSModuleInterop private static DpiWatcherInterop? instance; private event Action? callbacksEvent; +#if NET7_0_OR_GREATER + private readonly Action callbackHelper; +#else private readonly FloatFloatActionHelper callbackHelper; private DotNetObjectReference? callbackReference; +#endif public static async Task ImportAsync(IJSRuntime js, Action? callback = null) { @@ -31,9 +42,9 @@ public static DpiWatcherInterop Get(IJSRuntime js) => instance ??= new DpiWatcherInterop(js); private DpiWatcherInterop(IJSRuntime js) - : base(js, JsFilename) + : base(js, ModuleName, JsFilename) { - callbackHelper = new FloatFloatActionHelper((o, n) => callbacksEvent?.Invoke(n)); + callbackHelper = new((o, n) => callbacksEvent?.Invoke((float)n)); } protected override void OnDisposingModule() => @@ -60,21 +71,28 @@ public void Unsubscribe(Action callback) Stop(); } +#if NET7_0_OR_GREATER + private double Start() => + Start(callbackHelper); + + [JSImport(StartSymbol, ModuleName)] + private static partial double Start([JSMarshalAs>] Action callback); + + [JSImport(StopSymbol, ModuleName)] + private static partial void Stop(); + + [JSImport(GetDpiSymbol, ModuleName)] + public static partial double GetDpi(); +#else private double Start() { - if (callbackReference != null) - return GetDpi(); - - callbackReference = DotNetObjectReference.Create(callbackHelper); + callbackReference ??= DotNetObjectReference.Create(callbackHelper); return Invoke(StartSymbol, callbackReference); } private void Stop() { - if (callbackReference == null) - return; - Invoke(StopSymbol); callbackReference?.Dispose(); @@ -83,5 +101,6 @@ private void Stop() public double GetDpi() => Invoke(GetDpiSymbol); +#endif } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/FloatFloatActionHelper.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/FloatFloatActionHelper.cs index d8b5b4b4f5..b88056e93b 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/FloatFloatActionHelper.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/FloatFloatActionHelper.cs @@ -1,4 +1,5 @@ -using System; +#if !NET7_0_OR_GREATER +using System; using System.ComponentModel; using Microsoft.JSInterop; @@ -18,3 +19,4 @@ public FloatFloatActionHelper(Action action) public void Invoke(float width, float height) => action?.Invoke(width, height); } } +#endif diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/JSModuleInterop.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/JSModuleInterop.cs index de42afa15b..6acc23d2e3 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/JSModuleInterop.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/JSModuleInterop.cs @@ -1,20 +1,32 @@ using System; using System.Threading.Tasks; +using System.Runtime.Versioning; using Microsoft.JSInterop; +#if NET7_0_OR_GREATER +using System.Runtime.InteropServices.JavaScript; +#else +using JSObject = Microsoft.JSInterop.IJSUnmarshalledObjectReference; +#endif + namespace SkiaSharp.Views.Blazor.Internal { - internal class JSModuleInterop : IDisposable + [SupportedOSPlatform("browser")] + internal partial class JSModuleInterop : IDisposable { - private readonly Task moduleTask; - private IJSUnmarshalledObjectReference? module; + private readonly Task moduleTask; + private JSObject? module; - public JSModuleInterop(IJSRuntime js, string filename) + public JSModuleInterop(IJSRuntime js, string moduleName, string moduleUrl) { +#if NET7_0_OR_GREATER + moduleTask = JSHost.ImportAsync(moduleName, "/" + moduleUrl); +#else if (js is not IJSInProcessRuntime) throw new NotSupportedException("SkiaSharp currently only works on Web Assembly."); - moduleTask = js.InvokeAsync("import", filename).AsTask(); + moduleTask = js.InvokeAsync("import", moduleUrl).AsTask(); +#endif } public async Task ImportAsync() @@ -28,14 +40,16 @@ public void Dispose() Module.Dispose(); } - protected IJSUnmarshalledObjectReference Module => + protected JSObject Module => module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); +#if !NET7_0_OR_GREATER protected void Invoke(string identifier, params object?[]? args) => Module.InvokeVoid(identifier, args); protected TValue Invoke(string identifier, params object?[]? args) => Module.Invoke(identifier, args); +#endif protected virtual void OnDisposingModule() { } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SKHtmlCanvasInterop.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SKHtmlCanvasInterop.cs index 29cbfac86f..31d48dc58b 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SKHtmlCanvasInterop.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SKHtmlCanvasInterop.cs @@ -1,13 +1,20 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +#if NET7_0_OR_GREATER +using System.Runtime.InteropServices.JavaScript; +#endif + namespace SkiaSharp.Views.Blazor.Internal { - internal class SKHtmlCanvasInterop : JSModuleInterop + [SupportedOSPlatform("browser")] + internal partial class SKHtmlCanvasInterop : JSModuleInterop { + private const string ModuleName = "SKHtmlCanvas"; private const string JsFilename = "./_content/SkiaSharp.Views.Blazor/SKHtmlCanvas.js"; private const string InitGLSymbol = "SKHtmlCanvas.initGL"; private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; @@ -17,9 +24,13 @@ internal class SKHtmlCanvasInterop : JSModuleInterop private readonly ElementReference htmlCanvas; private readonly string htmlElementId; +#if NET7_0_OR_GREATER + private readonly Action callbackHelper; +#else private readonly ActionHelper callbackHelper; private DotNetObjectReference? callbackReference; +#endif public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) { @@ -29,30 +40,69 @@ public static async Task ImportAsync(IJSRuntime js, Element } public SKHtmlCanvasInterop(IJSRuntime js, ElementReference element, Action renderFrameCallback) - : base(js, JsFilename) + : base(js, ModuleName, JsFilename) { htmlCanvas = element; - htmlElementId = element.Id; + htmlElementId = "_bl_" + element.Id; - callbackHelper = new ActionHelper(renderFrameCallback); + callbackHelper = new(renderFrameCallback); } protected override void OnDisposingModule() => Deinit(); +#if NET7_0_OR_GREATER + public GLInfo InitGL() + { + Init(); + + var obj = InitGL(null, htmlElementId, callbackHelper); + var info = new GLInfo( + obj.GetPropertyAsInt32("contextId"), + (uint)obj.GetPropertyAsInt32("fboId"), + obj.GetPropertyAsInt32("stencils"), + obj.GetPropertyAsInt32("samples"), + obj.GetPropertyAsInt32("depth")); + return info; + } + + [JSImport(InitGLSymbol, ModuleName)] + public static partial JSObject InitGL(JSObject? element, string elementId, [JSMarshalAs] Action callback); + + public bool InitRaster() + { + Init(); + + return InitRaster(null, htmlElementId, callbackHelper); + } + + [JSImport(InitRasterSymbol, ModuleName)] + public static partial bool InitRaster(JSObject? element, string elementId, [JSMarshalAs] Action callback); + + public void Deinit() => + Deinit(htmlElementId); + + [JSImport(DeinitSymbol, ModuleName)] + public static partial void Deinit(string elementId); + + public void RequestAnimationFrame(bool enableRenderLoop, int rawWidth, int rawHeight) => + RequestAnimationFrame(htmlElementId, enableRenderLoop, rawWidth, rawHeight); + + [JSImport(RequestAnimationFrameSymbol, ModuleName)] + public static partial void RequestAnimationFrame(string elementId, bool enableRenderLoop, int rawWidth, int rawHeight); + + public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => + PutImageData(htmlElementId, intPtr, rawSize.Width, rawSize.Height); + + [JSImport(PutImageDataSymbol, ModuleName)] + public static partial void PutImageData(string elementId, IntPtr intPtr, int rawWidth, int rawHeight); +#else public GLInfo InitGL() { if (callbackReference != null) throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - try - { - InterceptGLObject(); - } - catch - { - // no-op - } + Init(); callbackReference = DotNetObjectReference.Create(callbackHelper); @@ -64,6 +114,8 @@ public bool InitRaster() if (callbackReference != null) throw new InvalidOperationException("Unable to initialize the same canvas more than once."); + Init(); + callbackReference = DotNetObjectReference.Create(callbackHelper); return Invoke(InitRasterSymbol, htmlCanvas, htmlElementId, callbackReference); @@ -80,15 +132,28 @@ public void Deinit() } public void RequestAnimationFrame(bool enableRenderLoop, int rawWidth, int rawHeight) => - Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop, rawWidth, rawHeight); + Invoke(RequestAnimationFrameSymbol, htmlElementId, enableRenderLoop, rawWidth, rawHeight); public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => - Invoke(PutImageDataSymbol, htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); + Invoke(PutImageDataSymbol, htmlElementId, intPtr.ToInt64(), rawSize.Width, rawSize.Height); +#endif public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); + static void Init() + { + try + { + InterceptBrowserObjects(); + } + catch + { + // no-op + } + } + // Workaround for https://github.com/dotnet/runtime/issues/76077 [DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)] - static extern void InterceptGLObject(); + static extern void InterceptBrowserObjects(); } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SizeWatcherInterop.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SizeWatcherInterop.cs index fb82809890..f37e9fdc01 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SizeWatcherInterop.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/Internal/SizeWatcherInterop.cs @@ -1,21 +1,32 @@ using System; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +#if NET7_0_OR_GREATER +using System.Runtime.InteropServices.JavaScript; +#endif + namespace SkiaSharp.Views.Blazor.Internal { - internal class SizeWatcherInterop : JSModuleInterop + [SupportedOSPlatform("browser")] + internal partial class SizeWatcherInterop : JSModuleInterop { + private const string ModuleName = "SizeWatcher"; private const string JsFilename = "./_content/SkiaSharp.Views.Blazor/SizeWatcher.js"; private const string ObserveSymbol = "SizeWatcher.observe"; private const string UnobserveSymbol = "SizeWatcher.unobserve"; private readonly ElementReference htmlElement; private readonly string htmlElementId; +#if NET7_0_OR_GREATER + private readonly Action callbackHelper; +#else private readonly FloatFloatActionHelper callbackHelper; private DotNetObjectReference? callbackReference; +#endif public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) { @@ -26,22 +37,32 @@ public static async Task ImportAsync(IJSRuntime js, ElementR } public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action callback) - : base(js, JsFilename) + : base(js, ModuleName, JsFilename) { htmlElement = element; - htmlElementId = element.Id; - callbackHelper = new FloatFloatActionHelper((x, y) => callback(new SKSize(x, y))); + htmlElementId = "_bl_" + element.Id; + callbackHelper = new((x, y) => callback(new SKSize(x, y))); } protected override void OnDisposingModule() => Stop(); +#if NET7_0_OR_GREATER + public void Start() => + Observe(null, htmlElementId, callbackHelper); + + public void Stop() => + Unobserve(htmlElementId); + + [JSImport(ObserveSymbol, ModuleName)] + private static partial void Observe(JSObject? element, string elementId, [JSMarshalAs>] Action callback); + + [JSImport(UnobserveSymbol, ModuleName)] + private static partial void Unobserve(string elementId); +#else public void Start() { - if (callbackReference != null) - return; - - callbackReference = DotNetObjectReference.Create(callbackHelper); + callbackReference ??= DotNetObjectReference.Create(callbackHelper); Invoke(ObserveSymbol, htmlElement, htmlElementId, callbackReference); } @@ -56,5 +77,6 @@ public void Stop() callbackReference?.Dispose(); callbackReference = null; } +#endif } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKCanvasView.razor.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKCanvasView.razor.cs index 7d803f3496..388ccc94d0 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKCanvasView.razor.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKCanvasView.razor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -8,6 +9,7 @@ namespace SkiaSharp.Views.Blazor { + [SupportedOSPlatform("browser")] public partial class SKCanvasView : IDisposable { private SKHtmlCanvasInterop interop = null!; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs index 6473fb1e8e..fdf68d18d2 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -7,6 +8,7 @@ namespace SkiaSharp.Views.Blazor { + [SupportedOSPlatform("browser")] public partial class SKGLView : IDisposable { private SKHtmlCanvasInterop interop = null!; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SkiaSharp.Views.Blazor.csproj b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SkiaSharp.Views.Blazor.csproj index 8ce803a54f..3c20e1bfb8 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SkiaSharp.Views.Blazor.csproj +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SkiaSharp.Views.Blazor.csproj @@ -20,6 +20,7 @@ + @@ -37,7 +38,7 @@ - + diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.Local.props b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.Local.props index bf5939b8be..0e0d66dbf9 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.Local.props +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.Local.props @@ -3,7 +3,25 @@ - $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)SkiaSharpGLInterop.js" + $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)SkiaSharpInterop.js" + + + + <_WasmLinkStepArgs Remove="@(_EmccLinkStepArgs)" /> + <_EmccLinkStepArgs Remove=""%(_WasmNativeFileForLinking.Identity)"" /> + <_WasmLinkDependencies Remove="@(_WasmNativeFileForLinking)" /> + + <_SkiaSharpToReorder Include="@(_WasmNativeFileForLinking)" Condition="$([System.String]::Copy('%(FullPath)').Contains('libSkiaSharp.a'))" /> + <_WasmNativeFileForLinking Remove="@(_SkiaSharpToReorder)" /> + <_WasmNativeFileForLinking Include="@(_SkiaSharpToReorder)" /> + + <_EmccLinkStepArgs Include=""%(_WasmNativeFileForLinking.Identity)"" /> + <_WasmLinkDependencies Include="@(_WasmNativeFileForLinking)" /> + <_WasmLinkStepArgs Include="@(_EmccLinkStepArgs)" /> + + + diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.props b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.props index d4dd8e8544..ba56e78057 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.props +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharp.Views.Blazor.props @@ -3,9 +3,27 @@ - $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\SkiaSharpGLInterop.js" + $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)SkiaSharpInterop.js" + + + + <_WasmLinkStepArgs Remove="@(_EmccLinkStepArgs)" /> + <_EmccLinkStepArgs Remove=""%(_WasmNativeFileForLinking.Identity)"" /> + <_WasmLinkDependencies Remove="@(_WasmNativeFileForLinking)" /> + + <_SkiaSharpToReorder Include="@(_WasmNativeFileForLinking)" Condition="$([System.String]::Copy('%(FullPath)').Contains('libSkiaSharp.a'))" /> + <_WasmNativeFileForLinking Remove="@(_SkiaSharpToReorder)" /> + <_WasmNativeFileForLinking Include="@(_SkiaSharpToReorder)" /> + + <_EmccLinkStepArgs Include=""%(_WasmNativeFileForLinking.Identity)"" /> + <_WasmLinkDependencies Include="@(_WasmNativeFileForLinking)" /> + <_WasmLinkStepArgs Include="@(_EmccLinkStepArgs)" /> + + + diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpGLInterop.js b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpGLInterop.js deleted file mode 100644 index 24d24d659d..0000000000 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpGLInterop.js +++ /dev/null @@ -1,15 +0,0 @@ -// Workaround for https://github.com/dotnet/runtime/issues/76077 -// Special thanks to the Avalonia UI team - -var SkiaSharpGLInterop = { - $SkiaSharpLibrary: { - internal_func: function () { - } - }, - InterceptGLObject: function () { - globalThis.SkiaSharpGL = GL - } -} - -autoAddDeps(SkiaSharpGLInterop, '$SkiaSharpLibrary') -mergeInto(LibraryManager.library, SkiaSharpGLInterop) diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpInterop.js b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpInterop.js new file mode 100644 index 0000000000..67568ba718 --- /dev/null +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/nuget/buildTransitive/SkiaSharpInterop.js @@ -0,0 +1,16 @@ +// Workaround for https://github.com/dotnet/runtime/issues/76077 +// Special thanks to the Avalonia UI team + +var SkiaSharpInterop = { + $SkiaSharpLibrary: { + internal_func: function () { + } + }, + InterceptBrowserObjects: function () { + globalThis.SkiaSharpGL = GL + globalThis.SkiaSharpModule = Module + } +} + +autoAddDeps(SkiaSharpInterop, '$SkiaSharpLibrary') +mergeInto(LibraryManager.library, SkiaSharpInterop) diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.js b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.js index f0a32d704b..635cd40c90 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.js +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.js @@ -21,7 +21,12 @@ export class DpiWatcher { const lastDpi = DpiWatcher.lastDpi; DpiWatcher.lastDpi = currentDpi; if (Math.abs(lastDpi - currentDpi) > 0.001) { - DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); + if (typeof DpiWatcher.callback === 'function') { + DpiWatcher.callback(lastDpi, currentDpi); + } + else { + DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); + } } } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.ts b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.ts index 72baf14d8f..a0922abdd5 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.ts +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/DpiWatcher.ts @@ -1,14 +1,16 @@  +type DpiWatcherCallback = DotNet.DotNetObjectReference | ((lastDpi: number, currentDpi: number) => void); + export class DpiWatcher { static lastDpi: number; static timerId: number; - static callback: DotNet.DotNetObjectReference; + static callback: DpiWatcherCallback; public static getDpi() { return window.devicePixelRatio; } - public static start(callback: DotNet.DotNetObjectReference): number { + public static start(callback: DpiWatcherCallback): number { //console.info(`Starting DPI watcher with callback ${callback._id}...`); DpiWatcher.lastDpi = window.devicePixelRatio; @@ -35,7 +37,11 @@ export class DpiWatcher { DpiWatcher.lastDpi = currentDpi; if (Math.abs(lastDpi - currentDpi) > 0.001) { - DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); + if (typeof DpiWatcher.callback === 'function') { + DpiWatcher.callback(lastDpi, currentDpi); + } else { + DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); + } } } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.js b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.js index 3264a9228c..d0907933bd 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.js +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.js @@ -12,6 +12,7 @@ export class SKHtmlCanvas { return true; } static init(useGL, element, elementId, callback) { + element = element || document.querySelector('[' + elementId + ']'); var htmlCanvas = element; if (!htmlCanvas) { console.error(`No canvas element was provided.`); @@ -19,7 +20,7 @@ export class SKHtmlCanvas { } if (!SKHtmlCanvas.elements) SKHtmlCanvas.elements = new Map(); - SKHtmlCanvas.elements[elementId] = element; + SKHtmlCanvas.elements.set(elementId, element); const view = new SKHtmlCanvas(useGL, element, callback); htmlCanvas.SKHtmlCanvas = view; return view; @@ -27,28 +28,28 @@ export class SKHtmlCanvas { static deinit(elementId) { if (!elementId) return; - const element = SKHtmlCanvas.elements[elementId]; - SKHtmlCanvas.elements.delete(elementId); + const element = SKHtmlCanvas.elements.get(elementId); + const removed = SKHtmlCanvas.elements.delete(elementId); const htmlCanvas = element; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.deinit(); htmlCanvas.SKHtmlCanvas = undefined; } - static requestAnimationFrame(element, renderLoop, width, height) { - const htmlCanvas = element; + static requestAnimationFrame(elementId, renderLoop, width, height) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId); if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop, width, height); } - static setEnableRenderLoop(element, enable) { - const htmlCanvas = element; + static setEnableRenderLoop(elementId, enable) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId); if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); } - static putImageData(element, pData, width, height) { - const htmlCanvas = element; + static putImageData(elementId, pData, width, height) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId); if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); @@ -101,7 +102,12 @@ export class SKHtmlCanvas { const GL = SKHtmlCanvas.getGL(); GL.makeContextCurrent(this.glInfo.context); } - this.renderFrameCallback.invokeMethod('Invoke'); + if (typeof this.renderFrameCallback === 'function') { + this.renderFrameCallback(); + } + else { + this.renderFrameCallback.invokeMethod('Invoke'); + } this.renderLoopRequest = 0; // we may want to draw the next frame if (this.renderLoopEnabled) @@ -132,6 +138,7 @@ export class SKHtmlCanvas { this.htmlCanvas.width = width; this.htmlCanvas.height = height; // set the canvas to be the bytes + const Module = SKHtmlCanvas.getModule(); var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); var imageData = new ImageData(buffer, width, height); ctx.putImageData(imageData, 0, 0); @@ -166,6 +173,9 @@ export class SKHtmlCanvas { static getGL() { return globalThis.SkiaSharpGL || Module.GL || GL; } + static getModule() { + return globalThis.SkiaSharpModule || Module; + } static getGLctx() { const GL = SKHtmlCanvas.getGL(); return GL.currentContext && GL.currentContext.GLctx || GLctx; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.ts b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.ts index 45605d9e83..5f18ae3843 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.ts +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SKHtmlCanvas.ts @@ -17,16 +17,18 @@ type SKHtmlCanvasElement = { SKHtmlCanvas: SKHtmlCanvas } & HTMLCanvasElement +type SKHtmlCanvasCallback = DotNet.DotNetObjectReference | (() => void); + export class SKHtmlCanvas { static elements: Map; htmlCanvas: HTMLCanvasElement; glInfo: SKGLViewInfo; - renderFrameCallback: DotNet.DotNetObjectReference; + renderFrameCallback: SKHtmlCanvasCallback; renderLoopEnabled: boolean = false; renderLoopRequest: number = 0; - public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKGLViewInfo { + public static initGL(element: HTMLCanvasElement, elementId: string, callback: SKHtmlCanvasCallback): SKGLViewInfo { var view = SKHtmlCanvas.init(true, element, elementId, callback); if (!view || !view.glInfo) return null; @@ -34,7 +36,7 @@ export class SKHtmlCanvas { return view.glInfo; } - public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): boolean { + public static initRaster(element: HTMLCanvasElement, elementId: string, callback: SKHtmlCanvasCallback): boolean { var view = SKHtmlCanvas.init(false, element, elementId, callback); if (!view) return false; @@ -42,7 +44,8 @@ export class SKHtmlCanvas { return true; } - static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKHtmlCanvas { + static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: SKHtmlCanvasCallback): SKHtmlCanvas { + element = element || document.querySelector('[' + elementId + ']'); var htmlCanvas = element as SKHtmlCanvasElement; if (!htmlCanvas) { console.error(`No canvas element was provided.`); @@ -51,7 +54,7 @@ export class SKHtmlCanvas { if (!SKHtmlCanvas.elements) SKHtmlCanvas.elements = new Map(); - SKHtmlCanvas.elements[elementId] = element; + SKHtmlCanvas.elements.set(elementId, element); const view = new SKHtmlCanvas(useGL, element, callback); @@ -64,8 +67,8 @@ export class SKHtmlCanvas { if (!elementId) return; - const element = SKHtmlCanvas.elements[elementId]; - SKHtmlCanvas.elements.delete(elementId); + const element = SKHtmlCanvas.elements.get(elementId); + const removed = SKHtmlCanvas.elements.delete(elementId); const htmlCanvas = element as SKHtmlCanvasElement; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) @@ -75,31 +78,31 @@ export class SKHtmlCanvas { htmlCanvas.SKHtmlCanvas = undefined; } - public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean, width?: number, height?: number) { - const htmlCanvas = element as SKHtmlCanvasElement; + public static requestAnimationFrame(elementId: string, renderLoop?: boolean, width?: number, height?: number) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId) as SKHtmlCanvasElement; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop, width, height); } - public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { - const htmlCanvas = element as SKHtmlCanvasElement; + public static setEnableRenderLoop(elementId: string, enable: boolean) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId) as SKHtmlCanvasElement; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); } - public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) { - const htmlCanvas = element as SKHtmlCanvasElement; + public static putImageData(elementId: string, pData: number, width: number, height: number) { + const htmlCanvas = SKHtmlCanvas.elements.get(elementId) as SKHtmlCanvasElement; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); } - public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObjectReference) { + public constructor(useGL: boolean, element: HTMLCanvasElement, callback: SKHtmlCanvasCallback) { this.htmlCanvas = element; this.renderFrameCallback = callback; @@ -154,7 +157,11 @@ export class SKHtmlCanvas { GL.makeContextCurrent(this.glInfo.context); } - this.renderFrameCallback.invokeMethod('Invoke'); + if (typeof this.renderFrameCallback === 'function') { + this.renderFrameCallback(); + } else { + this.renderFrameCallback.invokeMethod('Invoke'); + } this.renderLoopRequest = 0; // we may want to draw the next frame @@ -191,6 +198,7 @@ export class SKHtmlCanvas { this.htmlCanvas.height = height; // set the canvas to be the bytes + const Module = SKHtmlCanvas.getModule(); var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); var imageData = new ImageData(buffer, width, height); ctx.putImageData(imageData, 0, 0); @@ -231,6 +239,10 @@ export class SKHtmlCanvas { return (globalThis as any).SkiaSharpGL || (Module as any).GL || GL; } + static getModule(): EmscriptenModule { + return (globalThis as any).SkiaSharpModule || (Module as any); + } + static getGLctx(): WebGLRenderingContext { const GL = SKHtmlCanvas.getGL(); return GL.currentContext && GL.currentContext.GLctx || GLctx; diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.js b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.js index 4809dc7e3b..7c57c394de 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.js +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.js @@ -1,14 +1,15 @@ export class SizeWatcher { static observe(element, elementId, callback) { - if (!element || !callback) + if ((!element && !elementId) || !callback) return; //console.info(`Adding size watcher observation with callback ${callback._id}...`); SizeWatcher.init(); + element = element || document.querySelector('[' + elementId + ']'); const watcherElement = element; watcherElement.SizeWatcher = { callback: callback }; - SizeWatcher.elements[elementId] = element; + SizeWatcher.elements.set(elementId, element); SizeWatcher.observer.observe(element); SizeWatcher.invoke(element); } @@ -16,8 +17,8 @@ export class SizeWatcher { if (!elementId || !SizeWatcher.observer) return; //console.info('Removing size watcher observation...'); - const element = SizeWatcher.elements[elementId]; - SizeWatcher.elements.delete(elementId); + const element = SizeWatcher.elements.get(elementId); + const removed = SizeWatcher.elements.delete(elementId); SizeWatcher.observer.unobserve(element); } static init() { @@ -36,6 +37,11 @@ export class SizeWatcher { const instance = watcherElement.SizeWatcher; if (!instance || !instance.callback) return; - return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + if (typeof instance.callback === 'function') { + instance.callback(element.clientWidth, element.clientHeight); + } + else { + instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + } } } diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.ts b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.ts index 88b94f3a80..1664bd04c5 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.ts +++ b/source/SkiaSharp.Views/SkiaSharp.Views.Blazor/wwwroot/SizeWatcher.ts @@ -3,28 +3,32 @@ type SizeWatcherElement = { SizeWatcher: SizeWatcherInstance; } & HTMLElement +type SizeWatcherCallback = DotNet.DotNetObjectReference | ((width: number, height: number) => void); + type SizeWatcherInstance = { - callback: DotNet.DotNetObjectReference; + callback: SizeWatcherCallback; } export class SizeWatcher { static observer: ResizeObserver; static elements: Map; - public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObjectReference) { - if (!element || !callback) + public static observe(element: HTMLElement, elementId: string, callback: SizeWatcherCallback) { + if ((!element && !elementId) || !callback) return; //console.info(`Adding size watcher observation with callback ${callback._id}...`); SizeWatcher.init(); + element = element || document.querySelector('[' + elementId + ']'); + const watcherElement = element as SizeWatcherElement; watcherElement.SizeWatcher = { callback: callback }; - SizeWatcher.elements[elementId] = element; + SizeWatcher.elements.set(elementId, element); SizeWatcher.observer.observe(element); SizeWatcher.invoke(element); @@ -36,9 +40,8 @@ export class SizeWatcher { //console.info('Removing size watcher observation...'); - const element = SizeWatcher.elements[elementId]; - - SizeWatcher.elements.delete(elementId); + const element = SizeWatcher.elements.get(elementId); + const removed = SizeWatcher.elements.delete(elementId); SizeWatcher.observer.unobserve(element); } @@ -63,6 +66,10 @@ export class SizeWatcher { if (!instance || !instance.callback) return; - return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + if (typeof instance.callback === 'function') { + instance.callback(element.clientWidth, element.clientHeight); + } else { + instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + } } }