diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index c0b7ddbe1e..560e8079a6 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/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(); diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index b575bc6dbb..0e5580ebe4 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/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(); + _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(); - // 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; diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs new file mode 100644 index 0000000000..a286affe04 --- /dev/null +++ b/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 _blitCallback; + private readonly Action _onDisposeAction; + + public BlazorSkiaRasterSurface( + SKColorType colorType, PixelSize size, double scaling, Action 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(); + } + } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs index 512309cfe3..fb49df338b 100644 --- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs +++ b/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) { diff --git a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs new file mode 100644 index 0000000000..5463893e27 --- /dev/null +++ b/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; } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index a8a1a970dc..e240f1554e 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/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 blitCallback) + { + _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback); + } + public void SetClientSize(SKSize size, double dpi) { var newSize = new Size(size.Width, size.Height);