Explorar o código

Enable use of Skia Raster backend for HTML canvas in Blazor

To enable the raster backend set CustomGpuFactory to null in
the existing SkiaOptions, by default Avalonia will use the
GPU/GL Skia backend.
Lobster Uberlord %!s(int64=3) %!d(string=hai) anos
pai
achega
508fad9d2c

+ 1 - 0
samples/ControlCatalog.Web/App.razor.cs

@@ -11,6 +11,7 @@ public partial class App
             {
                 ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
             })
+            //.With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
             .SetupWithSingleViewLifetime();
 
         base.OnParametersSet();

+ 41 - 16
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

@@ -37,6 +37,7 @@ namespace Avalonia.Web.Blazor
         private const SKColorType ColorType = SKColorType.Rgba8888;
 
         private bool _initialised;
+        private bool _useGL;
 
         [Inject] private IJSRuntime Js { get; set; } = null!;
 
@@ -261,25 +262,44 @@ namespace Avalonia.Web.Blazor
                 _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame);
 
                 Console.WriteLine("Interop created");
-                _jsGlInfo = _interop.InitGL();
-
-                Console.WriteLine("jsglinfo created - init gl");
+                
+                var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
+                _useGL = skiaOptions?.CustomGpuFactory != null;
 
-                // create the SkiaSharp context
-                if (_context == null)
+                if (_useGL)
+                {
+                    _jsGlInfo = _interop.InitGL();
+                    Console.WriteLine("jsglinfo created - init gl");
+                }
+                else
                 {
-                    Console.WriteLine("create glcontext");
-                    _glInterface = GRGlInterface.Create();
-                    _context = GRContext.CreateGl(_glInterface);
-
-                    var options = AvaloniaLocator.Current.GetService<SkiaOptions>();
-                    // bump the default resource cache limit
-                    _context.SetResourceCacheLimit(options?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
-                    Console.WriteLine("glcontext created and resource limit set");
+                    var rasterInitialized = _interop.InitRaster();
+                    Console.WriteLine("raster initialized: {0}", rasterInitialized);
                 }
 
-                _topLevelImpl.SetSurface(_context, _jsGlInfo, ColorType,
-                    new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
+                if (_useGL)
+                {
+                    // create the SkiaSharp context
+                    if (_context == null)
+                    {
+                        Console.WriteLine("create glcontext");
+                        _glInterface = GRGlInterface.Create();
+                        _context = GRContext.CreateGl(_glInterface);
+
+                        
+                        // bump the default resource cache limit
+                        _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
+                        Console.WriteLine("glcontext created and resource limit set");
+                    }
+
+                    _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType,
+                        new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
+                }
+                else
+                {
+                    _topLevelImpl.SetSurface(ColorType,
+                        new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
+                }
                 
                 _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
 
@@ -301,7 +321,12 @@ namespace Avalonia.Web.Blazor
 
         private void OnRenderFrame()
         {
-            if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0 || _jsGlInfo == null)
+            if (_useGL && (_jsGlInfo == null))
+            {
+                Console.WriteLine("nothing to render");
+                return;
+            }
+            if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0)
             {
                 Console.WriteLine("nothing to render");
                 return;

+ 91 - 0
src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs

@@ -0,0 +1,91 @@
+using System.Runtime.InteropServices;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using Avalonia.Skia;
+using SkiaSharp;
+
+namespace Avalonia.Web.Blazor
+{
+    internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable
+    {
+        public SKColorType ColorType { get; set; }
+
+        public PixelSize Size { get; set; }
+
+        public double Scaling { get; set; }
+
+        private FramebufferData? _fbData;
+        private readonly Action<IntPtr, SKSizeI> _blitCallback;
+        private readonly Action _onDisposeAction;
+
+        public BlazorSkiaRasterSurface(
+            SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback)
+        {
+            ColorType = colorType;
+            Size = size;
+            Scaling = scaling;
+            _blitCallback = blitCallback;
+            _onDisposeAction = Blit;
+        }
+
+        public void Dispose()
+        {
+            _fbData?.Dispose();
+            _fbData = null;
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            var bytesPerPixel = 4; // TODO: derive from ColorType
+            var dpi = Scaling * 96.0;
+            var width = (int)(Size.Width * Scaling);
+            var height = (int)(Size.Height * Scaling);
+
+            if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height)
+            {
+                _fbData?.Dispose();
+                _fbData = new FramebufferData(width, height, bytesPerPixel);
+            }
+
+            var pixelFormat = ColorType.ToPixelFormat();
+            var data = _fbData.Value;
+            return new LockedFramebuffer(
+                data.Address, data.Size, data.RowBytes,
+                new Vector(dpi, dpi), pixelFormat, _onDisposeAction);
+        }
+
+        private void Blit()
+        {
+            if (_fbData != null)
+            {
+                var data = _fbData.Value;
+                _blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height));
+            }
+        }
+
+        private readonly struct FramebufferData
+        {
+            private readonly byte[]? _data;
+            private readonly GCHandle _dataHandle;
+
+            public PixelSize Size { get; }
+
+            public int RowBytes { get; }
+
+            public IntPtr Address => _dataHandle.AddrOfPinnedObject();
+
+            public FramebufferData(int width, int height, int bytesPerPixel)
+            {
+                Size = new PixelSize(width, height);
+                RowBytes = width * bytesPerPixel;
+                _data = new byte[width * height * bytesPerPixel];
+                _dataHandle = GCHandle.Alloc(_data, GCHandleType.Pinned);
+            }
+
+            public void Dispose()
+            {
+                _dataHandle.Free();
+            }
+        }
+    }
+}

+ 1 - 1
src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs

@@ -3,7 +3,7 @@ using SkiaSharp;
 
 namespace Avalonia.Web.Blazor
 {
-    internal class BlazorSkiaSurface
+    internal class BlazorSkiaSurface : IBlazorSkiaSurface
     {
         public BlazorSkiaSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin)
         {

+ 9 - 0
src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs

@@ -0,0 +1,9 @@
+namespace Avalonia.Web.Blazor
+{
+    internal interface IBlazorSkiaSurface
+    {
+        public PixelSize Size { get; set; }
+
+        public double Scaling { get; set; }
+    }
+}

+ 6 - 1
src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Web.Blazor
     internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
     {
         private Size _clientSize;
-        private BlazorSkiaSurface? _currentSurface;
+        private IBlazorSkiaSurface? _currentSurface;
         private IInputRoot? _inputRoot;
         private readonly Stopwatch _sw = Stopwatch.StartNew();
         private readonly AvaloniaView _avaloniaView;
@@ -40,6 +40,11 @@ namespace Avalonia.Web.Blazor
                 new BlazorSkiaSurface(context, glInfo, colorType, size, scaling, GRSurfaceOrigin.BottomLeft);
         }
 
+        internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback)
+        {
+            _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback);
+        }
+
         public void SetClientSize(SKSize size, double dpi)
         {
             var newSize = new Size(size.Width, size.Height);