using System; using System.Reactive.Disposables; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; using SkiaSharp; namespace Avalonia.Skia { /// /// Skia render target that renders to a framebuffer surface. No gpu acceleration available. /// internal class FramebufferRenderTarget : IRenderTarget { private readonly IFramebufferPlatformSurface _platformSurface; private SKImageInfo _currentImageInfo; private IntPtr _currentFramebufferAddress; private SKSurface _framebufferSurface; private PixelFormatConversionShim _conversionShim; private IDisposable _preFramebufferCopyHandler; /// /// Create new framebuffer render target using a target surface. /// /// Target surface. public FramebufferRenderTarget(IFramebufferPlatformSurface platformSurface) { _platformSurface = platformSurface; } /// public void Dispose() { FreeSurface(); } /// public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { var framebuffer = _platformSurface.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType(), framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); CreateSurface(framebufferImageInfo, framebuffer); var canvas = _framebufferSurface.Canvas; canvas.RestoreToCount(-1); canvas.Save(); canvas.ResetMatrix(); var createInfo = new DrawingContextImpl.CreateInfo { Surface = _framebufferSurface, Dpi = framebuffer.Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = true }; return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); } /// /// Check if two images info are compatible. /// /// Current. /// Desired. /// True, if images are compatible. private static bool AreImageInfosCompatible(SKImageInfo currentImageInfo, SKImageInfo desiredImageInfo) { return currentImageInfo.Width == desiredImageInfo.Width && currentImageInfo.Height == desiredImageInfo.Height && currentImageInfo.ColorType == desiredImageInfo.ColorType; } /// /// Create Skia surface backed by given framebuffer. /// /// Desired image info. /// Backing framebuffer. private void CreateSurface(SKImageInfo desiredImageInfo, ILockedFramebuffer framebuffer) { if (_framebufferSurface != null && AreImageInfosCompatible(_currentImageInfo, desiredImageInfo) && _currentFramebufferAddress == framebuffer.Address) { return; } FreeSurface(); _currentFramebufferAddress = framebuffer.Address; var surface = SKSurface.Create(desiredImageInfo, _currentFramebufferAddress, framebuffer.RowBytes, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); // If surface cannot be created - try to create a compatibility shim first if (surface == null) { _conversionShim = new PixelFormatConversionShim(desiredImageInfo, framebuffer.Address); _preFramebufferCopyHandler = _conversionShim.SurfaceCopyHandler; surface = _conversionShim.Surface; } _framebufferSurface = surface ?? throw new Exception("Unable to create a surface for pixel format " + framebuffer.Format + " or pixel format translator"); _currentImageInfo = desiredImageInfo; } /// /// Free Skia surface. /// private void FreeSurface() { _conversionShim?.Dispose(); _conversionShim = null; _preFramebufferCopyHandler = null; _framebufferSurface?.Dispose(); _framebufferSurface = null; _currentFramebufferAddress = IntPtr.Zero; } /// /// Converts non-compatible pixel formats using bitmap copies. /// private class PixelFormatConversionShim : IDisposable { private readonly SKBitmap _bitmap; private readonly SKImageInfo _destinationInfo; private readonly IntPtr _framebufferAddress; public PixelFormatConversionShim(SKImageInfo destinationInfo, IntPtr framebufferAddress) { _destinationInfo = destinationInfo; _framebufferAddress = framebufferAddress; // Create bitmap using default platform settings _bitmap = new SKBitmap(destinationInfo.Width, destinationInfo.Height); SKColorType bitmapColorType; if (!_bitmap.CanCopyTo(destinationInfo.ColorType)) { bitmapColorType = _bitmap.ColorType; _bitmap.Dispose(); throw new Exception( $"Unable to create pixel format shim for conversion from {bitmapColorType} to {destinationInfo.ColorType}"); } Surface = SKSurface.Create(_bitmap.Info, _bitmap.GetPixels(), _bitmap.RowBytes, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)); if (Surface == null) { bitmapColorType = _bitmap.ColorType; _bitmap.Dispose(); throw new Exception( $"Unable to create pixel format shim surface for conversion from {bitmapColorType} to {destinationInfo.ColorType}"); } SurfaceCopyHandler = Disposable.Create(CopySurface); } /// /// Skia surface. /// public SKSurface Surface { get; } /// /// Handler to start conversion via surface copy. /// public IDisposable SurfaceCopyHandler { get; } /// public void Dispose() { Surface.Dispose(); _bitmap.Dispose(); } /// /// Convert and copy surface to a framebuffer. /// private void CopySurface() { using (var snapshot = Surface.Snapshot()) { snapshot.ReadPixels(_destinationInfo, _framebufferAddress, _destinationInfo.RowBytes, 0, 0, SKImageCachingHint.Disallow); } } } } }