diff --git a/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs b/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs index 7769d118ff..7e386583d4 100644 --- a/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs +++ b/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Avalonia.Browser.Interop; +using Avalonia.Browser.Rendering; using Avalonia.Metadata; namespace Avalonia.Browser; @@ -109,6 +110,10 @@ public static class BrowserAppBuilder AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); await AvaloniaModule.ImportMain(); + if (BrowserWindowingPlatform.IsThreadingEnabled) + { + await RenderWorker.InitializeAsync(); + } if (builder.WindowingSubsystemInitializer is null) { diff --git a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs index b48a00919c..8e2d66b73e 100644 --- a/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs +++ b/src/Browser/Avalonia.Browser/Interop/AvaloniaModule.cs @@ -6,11 +6,13 @@ namespace Avalonia.Browser.Interop; internal static partial class AvaloniaModule { - private static readonly Lazy s_importMain = new(() => + private static readonly Lazy s_importMain = new(ImportMainToCurrentContext); + + public static Task ImportMainToCurrentContext() { var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions(); return JSHost.ImportAsync(MainModuleName, options.FrameworkAssetPathResolver!("avalonia.js")); - }); + } private static readonly Lazy s_importStorage = new(() => { diff --git a/src/Browser/Avalonia.Browser/Rendering/BrowserRenderTimer.cs b/src/Browser/Avalonia.Browser/Rendering/BrowserRenderTimer.cs index 06a7a1766a..55f42d0df7 100644 --- a/src/Browser/Avalonia.Browser/Rendering/BrowserRenderTimer.cs +++ b/src/Browser/Avalonia.Browser/Rendering/BrowserRenderTimer.cs @@ -22,16 +22,8 @@ internal class BrowserRenderTimer : IRenderTimer { add { - if (!_started) - { - _started = true; - if (BrowserWindowingPlatform.IsThreadingEnabled) - { - - } - else - TimerHelper.RunAnimationFrames(RenderFrameCallback); - } + if (!BrowserWindowingPlatform.IsThreadingEnabled) + StartOnThisThread(); _tick += value; } @@ -41,6 +33,15 @@ internal class BrowserRenderTimer : IRenderTimer } } + public void StartOnThisThread() + { + if (!_started) + { + _started = true; + TimerHelper.RunAnimationFrames(RenderFrameCallback); + } + } + private bool RenderFrameCallback(double timestamp) { if (_tick is { } tick) diff --git a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs b/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs index 8223eaf58f..9a7c2a551d 100644 --- a/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs +++ b/src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs @@ -1,6 +1,81 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.JavaScript; +using System.Threading.Tasks; +using Avalonia.Browser.Interop; + namespace Avalonia.Browser.Rendering; -internal class RenderWorker +internal partial class RenderWorker { + [DllImport("*")] + private static extern int pthread_self(); + + [JSImport("WebRenderTargetRegistry.initializeWorker", AvaloniaModule.MainModuleName)] + private static partial void InitializeRenderTargets(); + + public static int WorkerThreadId; + public static Task InitializeAsync() + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var workerTask = JSWebWorkerWrapper.RunAsync(async () => + { + try + { + await AvaloniaModule.ImportMainToCurrentContext(); + InitializeRenderTargets(); + WorkerThreadId = pthread_self(); + BrowserCompositor.RenderTimer.StartOnThisThread(); + tcs.SetResult(); + // Never surrender + await new TaskCompletionSource().Task; + } + catch (Exception e) + { + tcs.SetException(e); + } + }); + + workerTask.ContinueWith(_ => + { + if (workerTask.IsFaulted) + tcs.TrySetException(workerTask.Exception); + }); + return tcs.Task; + } + + class JSWebWorkerWrapper + { + + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", + "System.Runtime.InteropServices.JavaScript")] + [UnconditionalSuppressMessage("Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "Private runtime API")] + static JSWebWorkerWrapper() + { + var type = typeof(System.Runtime.InteropServices.JavaScript.JSHost) + .Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker"); +#pragma warning disable IL2075 + var m = type! + + .GetMethods(BindingFlags.Static | BindingFlags.Public + ).First(m => m.Name == "RunAsync" + && m.ReturnType == typeof(Task) + && m.GetParameters() is { } parameters + && parameters.Length == 1 + && parameters[0].ParameterType == typeof(Func)); + +#pragma warning restore IL2075 + RunAsync = (Func, Task>) Delegate.CreateDelegate(typeof(Func, Task>), m); + + } + + public static Func, Task> RunAsync { get; set; } + } + } \ No newline at end of file diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 7865fb02e7..5ecaa1bb84 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -65,7 +65,6 @@ internal class BrowserWindowingPlatform : IWindowingPlatform .Bind().ToConstant(s_keyboard) .Bind().ToSingleton() .Bind().ToSingleton() - .Bind().ToConstant(ManualTriggerRenderTimer.Instance) .Bind().ToConstant(instance) .Bind().ToConstant(new BrowserSkiaGraphics()) .Bind().ToSingleton() diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/timer.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/timer.ts index 7e52092056..51a5590d25 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/timer.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/timer.ts @@ -3,10 +3,10 @@ export class TimerHelper { function render(time: number) { const next = renderFrameCallback(time); if (next) { - window.requestAnimationFrame(render); + self.requestAnimationFrame(render); } } - window.requestAnimationFrame(render); + self.requestAnimationFrame(render); } }