Browse Source

Merge pull request #4922 from MarchingCube/cleanup-win32-framebuffer-manager

Cleanup win32 framebuffer manager and actually lock the framebuffer
pull/4930/head
danwalmsley 5 years ago
committed by GitHub
parent
commit
3685545676
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 176
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  2. 100
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs

176
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<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();
}
}
}
}

100
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@ -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…
Cancel
Save