committed by
GitHub
2 changed files with 163 additions and 113 deletions
@ -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<IRuntimePlatform>().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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -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<IRuntimePlatform>().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(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue