diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 6969a49dad..699dc7c25d 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -1,41 +1,191 @@ using System; +using System.Threading; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; using Avalonia.Win32.Interop; +#nullable enable + namespace Avalonia.Win32 { - class FramebufferManager : IFramebufferPlatformSurface, IDisposable + internal class FramebufferManager : IFramebufferPlatformSurface, IDisposable { + private const int _bytesPerPixel = 4; + private const PixelFormat _format = PixelFormat.Bgra8888; + private readonly IntPtr _hwnd; - private WindowFramebuffer _fb; + private readonly object _lock; + private readonly Action _onDisposeAction; + + private FramebufferData? _framebufferData; public FramebufferManager(IntPtr hwnd) { _hwnd = hwnd; + _lock = new object(); + _onDisposeAction = DrawAndUnlock; } public ILockedFramebuffer Lock() { - UnmanagedMethods.GetClientRect(_hwnd, out var rc); + Monitor.Enter(_lock); - var width = Math.Max(1, rc.right - rc.left); - var height = Math.Max(1, rc.bottom - rc.top); + LockedFramebuffer? fb = null; - if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height)) + try { - _fb?.Deallocate(); - _fb = null; - _fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height)); - } + UnmanagedMethods.GetClientRect(_hwnd, out var rc); + + var width = Math.Max(1, rc.right - rc.left); + var height = Math.Max(1, rc.bottom - rc.top); + + if (_framebufferData is null || _framebufferData?.Size.Width != width || _framebufferData?.Size.Height != height) + { + _framebufferData?.Dispose(); + + _framebufferData = AllocateFramebufferData(width, height); + } + + var framebufferData = _framebufferData.Value; - return _fb; + return fb = new LockedFramebuffer( + framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes, + GetCurrentDpi(), _format, _onDisposeAction); + } + finally + { + // We free the lock when for whatever reason framebuffer was not created. + // This allows for a potential retry later. + if (fb is null) + { + Monitor.Exit(_lock); + } + } } public void Dispose() { - _fb?.Deallocate(); - _fb = null; + lock (_lock) + { + _framebufferData?.Dispose(); + _framebufferData = null; + } + } + + private void DrawAndUnlock() + { + try + { + if (_framebufferData.HasValue) + DrawToWindow(_hwnd, _framebufferData.Value); + } + finally + { + Monitor.Exit(_lock); + } + } + + private Vector GetCurrentDpi() + { + if (UnmanagedMethods.ShCoreAvailable) + { + var monitor = + UnmanagedMethods.MonitorFromWindow(_hwnd, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out var dpix, + out var dpiy) == 0) + { + return new Vector(dpix, dpiy); + } + } + + return new Vector(96, 96); + } + + private static FramebufferData AllocateFramebufferData(int width, int height) + { + var bitmapBlob = AvaloniaLocator.Current.GetService().AllocBlob(width * height * _bytesPerPixel); + + return new FramebufferData(bitmapBlob, width, height); + } + + private static void DrawToDevice(FramebufferData framebufferData, IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, + int srcY = 0, int width = -1, + int height = -1) + { + if (width == -1) + width = framebufferData.Size.Width; + if (height == -1) + height = framebufferData.Size.Height; + + var bmpInfo = framebufferData.Header; + + UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint)width, (uint)height, srcX, srcY, + 0, (uint)framebufferData.Size.Height, framebufferData.Data.Address, ref bmpInfo, 0); + } + + private static bool DrawToWindow(IntPtr hWnd, FramebufferData framebufferData, int destX = 0, int destY = 0, int srcX = 0, + int srcY = 0, int width = -1, + int height = -1) + { + if (framebufferData.Data.IsDisposed) + throw new ObjectDisposedException("Framebuffer"); + + if (hWnd == IntPtr.Zero) + return false; + + var hDC = UnmanagedMethods.GetDC(hWnd); + + if (hDC == IntPtr.Zero) + return false; + + try + { + DrawToDevice(framebufferData, hDC, destX, destY, srcX, srcY, width, height); + } + finally + { + UnmanagedMethods.ReleaseDC(hWnd, hDC); + } + + return true; + } + + private readonly struct FramebufferData + { + public IUnmanagedBlob Data { get; } + + public PixelSize Size { get; } + + public int RowBytes => Size.Width * _bytesPerPixel; + + public UnmanagedMethods.BITMAPINFOHEADER Header { get; } + + public FramebufferData(IUnmanagedBlob data, int width, int height) + { + Data = data; + Size = new PixelSize(width, height); + + var header = new UnmanagedMethods.BITMAPINFOHEADER(); + header.Init(); + + header.biPlanes = 1; + header.biBitCount = _bytesPerPixel * 8; + header.Init(); + + header.biWidth = width; + header.biHeight = -height; + + Header = header; + } + + public void Dispose() + { + Data.Dispose(); + } } } } diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs deleted file mode 100644 index 19b90685bf..0000000000 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using Avalonia.Platform; -using Avalonia.Win32.Interop; -using PixelFormat = Avalonia.Platform.PixelFormat; - -namespace Avalonia.Win32 -{ - public class WindowFramebuffer : ILockedFramebuffer - { - private readonly IntPtr _handle; - private IUnmanagedBlob _bitmapBlob; - private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; - - public WindowFramebuffer(IntPtr handle, PixelSize size) - { - - if (size.Width <= 0) - throw new ArgumentException("Width is less than zero"); - if (size.Height <= 0) - throw new ArgumentException("Height is less than zero"); - _handle = handle; - _bmpInfo.Init(); - _bmpInfo.biPlanes = 1; - _bmpInfo.biBitCount = 32; - _bmpInfo.Init(); - _bmpInfo.biWidth = size.Width; - _bmpInfo.biHeight = -size.Height; - _bitmapBlob = AvaloniaLocator.Current.GetService().AllocBlob(size.Width * size.Height * 4); - } - - ~WindowFramebuffer() - { - Deallocate(); - } - - public IntPtr Address => _bitmapBlob.Address; - public int RowBytes => Size.Width * 4; - public PixelFormat Format => PixelFormat.Bgra8888; - - public Vector Dpi - { - get - { - if (UnmanagedMethods.ShCoreAvailable) - { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow(_handle, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) - { - return new Vector(dpix, dpiy); - } - } - return new Vector(96, 96); - } - } - - public PixelSize Size => new PixelSize(_bmpInfo.biWidth, -_bmpInfo.biHeight); - - public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, - int height = -1) - { - if (width == -1) - width = Size.Width; - if (height == -1) - height = Size.Height; - UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, - 0, (uint)Size.Height, _bitmapBlob.Address, ref _bmpInfo, 0); - } - - public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, - int height = -1) - { - if (_bitmapBlob.IsDisposed) - throw new ObjectDisposedException("Framebuffer"); - if (hWnd == IntPtr.Zero) - return false; - IntPtr hDC = UnmanagedMethods.GetDC(hWnd); - if (hDC == IntPtr.Zero) - return false; - DrawToDevice(hDC, destX, destY, srcX, srcY, width, height); - UnmanagedMethods.ReleaseDC(hWnd, hDC); - return true; - } - - public void Dispose() - { - //It's not an *actual* dispose. This call means "We are done drawing" - DrawToWindow(_handle); - } - - public void Deallocate() => _bitmapBlob.Dispose(); - } -}