Browse Source
Added support for multiple drawing methods for window implementations and "framebuffer"pull/867/head
committed by
GitHub
38 changed files with 631 additions and 82 deletions
@ -0,0 +1,19 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Controls.Platform.Surfaces |
||||
|
{ |
||||
|
public interface IFramebufferPlatformSurface |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides a framebuffer descriptor for drawing.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Contents should be drawn on actual window after disposing
|
||||
|
/// </remarks>
|
||||
|
ILockedFramebuffer Lock(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.Platform.Surfaces |
||||
|
{ |
||||
|
public interface ILockedFramebuffer : IDisposable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Address of the first pixel
|
||||
|
/// </summary>
|
||||
|
IntPtr Address { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Framebuffer width
|
||||
|
/// </summary>
|
||||
|
int Width { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Framebuffer height
|
||||
|
/// </summary>
|
||||
|
int Height { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Number of bytes per row
|
||||
|
/// </summary>
|
||||
|
int RowBytes { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// DPI of underling screen
|
||||
|
/// </summary>
|
||||
|
Size Dpi { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pixel format
|
||||
|
/// </summary>
|
||||
|
PixelFormat Format { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Controls.Platform.Surfaces |
||||
|
{ |
||||
|
public enum PixelFormat |
||||
|
{ |
||||
|
Rgb565, |
||||
|
Rgba8888, |
||||
|
Bgra8888 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
|
||||
|
namespace Avalonia.Gtk |
||||
|
{ |
||||
|
class FramebufferManager : IFramebufferPlatformSurface, IDisposable |
||||
|
{ |
||||
|
private readonly WindowImplBase _window; |
||||
|
private PixbufFramebuffer _fb; |
||||
|
|
||||
|
public FramebufferManager(WindowImplBase window) |
||||
|
{ |
||||
|
_window = window; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_fb?.Deallocate(); |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock() |
||||
|
{ |
||||
|
if(_window.CurrentDrawable == null) |
||||
|
throw new InvalidOperationException("Window is not in drawing state"); |
||||
|
|
||||
|
var drawable = _window.CurrentDrawable; |
||||
|
var width = (int) _window.ClientSize.Width; |
||||
|
var height = (int) _window.ClientSize.Height; |
||||
|
if (_fb == null || _fb.Width != width || |
||||
|
_fb.Height != height) |
||||
|
{ |
||||
|
_fb?.Dispose(); |
||||
|
_fb = new PixbufFramebuffer(width, height); |
||||
|
} |
||||
|
_fb.SetDrawable(drawable); |
||||
|
return _fb; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Platform; |
||||
|
using Gdk; |
||||
|
|
||||
|
namespace Avalonia.Gtk |
||||
|
{ |
||||
|
class PixbufFramebuffer : ILockedFramebuffer |
||||
|
{ |
||||
|
private Pixbuf _pixbuf; |
||||
|
private Drawable _drawable; |
||||
|
|
||||
|
public PixbufFramebuffer(int width, int height) |
||||
|
{ |
||||
|
_pixbuf = new Pixbuf(Gdk.Colorspace.Rgb, false, 8, width, height); |
||||
|
} |
||||
|
|
||||
|
public void SetDrawable(Drawable drawable) |
||||
|
{ |
||||
|
_drawable = drawable; |
||||
|
} |
||||
|
|
||||
|
public void Deallocate() |
||||
|
{ |
||||
|
_pixbuf.Dispose(); |
||||
|
_pixbuf = null; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
using (var gc = new Gdk.GC(_drawable)) |
||||
|
_drawable.DrawPixbuf(gc, _pixbuf, 0, 0, 0, 0, Width, Height, RgbDither.None, 0, 0); |
||||
|
_drawable = null; |
||||
|
} |
||||
|
|
||||
|
public IntPtr Address => _pixbuf.Pixels; |
||||
|
public int Width => _pixbuf.Width; |
||||
|
public int Height => _pixbuf.Height; |
||||
|
public int RowBytes => _pixbuf.Rowstride; |
||||
|
//TODO: Proper DPI detect
|
||||
|
public Size Dpi => new Size(96, 96); |
||||
|
public PixelFormat Format |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem == |
||||
|
OperatingSystemType.WinNT) |
||||
|
return PixelFormat.Bgra8888; |
||||
|
return PixelFormat.Rgba8888; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
|
||||
|
using Android.App; |
||||
|
using Android.Content; |
||||
|
using Android.OS; |
||||
|
using Android.Runtime; |
||||
|
using Android.Views; |
||||
|
using Android.Widget; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
partial class PlatformRenderInterface |
||||
|
{ |
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
var surfaceView = surfaces?.OfType<SurfaceView>().FirstOrDefault(); |
||||
|
if (surfaceView == null) |
||||
|
throw new ArgumentException("Avalonia.Skia.Android is only capable of drawing on SurfaceView"); |
||||
|
return new WindowRenderTarget(surfaceView); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
partial class PlatformRenderInterface |
||||
|
{ |
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
var fb = surfaces?.OfType<IFramebufferPlatformSurface>().FirstOrDefault(); |
||||
|
if (fb == null) |
||||
|
throw new Exception("Avalonia.Skia.Deskop currently only supports framebuffer render target"); |
||||
|
return new FramebufferRenderTarget(fb); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using Avalonia.Platform; |
||||
|
using Foundation; |
||||
|
using UIKit; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
partial class PlatformRenderInterface |
||||
|
{ |
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
return new WindowRenderTarget(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
public class FramebufferRenderTarget : IRenderTarget |
||||
|
{ |
||||
|
private readonly IFramebufferPlatformSurface _surface; |
||||
|
|
||||
|
public FramebufferRenderTarget(IFramebufferPlatformSurface surface) |
||||
|
{ |
||||
|
_surface = surface; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
//Nothing to do here, since we don't own framebuffer
|
||||
|
} |
||||
|
|
||||
|
class FramebufferDrawingContextImpl : DrawingContextImpl |
||||
|
{ |
||||
|
private readonly SKCanvas _canvas; |
||||
|
private readonly SKSurface _surface; |
||||
|
private readonly ILockedFramebuffer _framebuffer; |
||||
|
|
||||
|
public FramebufferDrawingContextImpl(SKCanvas canvas, SKSurface surface, ILockedFramebuffer framebuffer) : base(canvas) |
||||
|
{ |
||||
|
_canvas = canvas; |
||||
|
_surface = surface; |
||||
|
_framebuffer = framebuffer; |
||||
|
} |
||||
|
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
_canvas.Dispose(); |
||||
|
_surface.Dispose(); |
||||
|
_framebuffer.Dispose(); |
||||
|
base.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
SKColorType TranslatePixelFormat(PixelFormat fmt) |
||||
|
{ |
||||
|
if(fmt == PixelFormat.Rgb565) |
||||
|
return SKColorType.Rgb565; |
||||
|
if(fmt == PixelFormat.Bgra8888) |
||||
|
return SKColorType.Bgra8888; |
||||
|
if (fmt == PixelFormat.Rgba8888) |
||||
|
return SKColorType.Rgba8888; |
||||
|
throw new ArgumentException("Unknown pixel format: " + fmt); |
||||
|
} |
||||
|
|
||||
|
public DrawingContext CreateDrawingContext() |
||||
|
{ |
||||
|
var fb = _surface.Lock(); |
||||
|
|
||||
|
SKImageInfo nfo = new SKImageInfo(fb.Width, fb.Height, TranslatePixelFormat(fb.Format), |
||||
|
SKAlphaType.Opaque); |
||||
|
var surface = SKSurface.Create(nfo, fb.Address, fb.RowBytes); |
||||
|
if (surface == null) |
||||
|
throw new Exception("Unable to create a surface for pixel format " + fb.Format); |
||||
|
var canvas = surface.Canvas; |
||||
|
canvas.RestoreToCount(0); |
||||
|
canvas.Save(); |
||||
|
canvas.Clear(SKColors.Red); |
||||
|
canvas.ResetMatrix(); |
||||
|
|
||||
|
return new DrawingContext(new FramebufferDrawingContextImpl(canvas, surface, fb), |
||||
|
Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Win32.Interop; |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
class FramebufferManager : IFramebufferPlatformSurface, IDisposable |
||||
|
{ |
||||
|
private readonly IntPtr _hwnd; |
||||
|
private WindowFramebuffer _fb; |
||||
|
|
||||
|
public FramebufferManager(IntPtr hwnd) |
||||
|
{ |
||||
|
_hwnd = hwnd; |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock() |
||||
|
{ |
||||
|
UnmanagedMethods.RECT rc; |
||||
|
UnmanagedMethods.GetClientRect(_hwnd, out rc); |
||||
|
var width = rc.right - rc.left; |
||||
|
var height = rc.bottom - rc.top; |
||||
|
if (_fb == null || _fb.Width != width || _fb.Height != height) |
||||
|
{ |
||||
|
_fb?.Deallocate(); |
||||
|
_fb = null; |
||||
|
_fb = new WindowFramebuffer(_hwnd, width, height); |
||||
|
} |
||||
|
return _fb; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_fb?.Deallocate(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,121 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Drawing; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Windows.Interop; |
||||
|
using System.Windows.Media; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Win32.Interop; |
||||
|
using PixelFormat = Avalonia.Controls.Platform.Surfaces.PixelFormat; |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
public class WindowFramebuffer : ILockedFramebuffer |
||||
|
{ |
||||
|
private readonly IntPtr _handle; |
||||
|
private IntPtr _pBitmap; |
||||
|
private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; |
||||
|
|
||||
|
public WindowFramebuffer(IntPtr handle, int width, int height) |
||||
|
{ |
||||
|
|
||||
|
if (width <= 0) |
||||
|
throw new ArgumentException("width is less than zero"); |
||||
|
if (height <= 0) |
||||
|
throw new ArgumentException("height is less than zero"); |
||||
|
_handle = handle; |
||||
|
_bmpInfo.Init(); |
||||
|
_bmpInfo.biPlanes = 1; |
||||
|
_bmpInfo.biBitCount = 32; |
||||
|
_bmpInfo.Init(); |
||||
|
_bmpInfo.biWidth = width; |
||||
|
_bmpInfo.biHeight = -height; |
||||
|
_pBitmap = Marshal.AllocHGlobal(width * height * 4); |
||||
|
} |
||||
|
|
||||
|
~WindowFramebuffer() |
||||
|
{ |
||||
|
Deallocate(); |
||||
|
} |
||||
|
|
||||
|
public IntPtr Address => _pBitmap; |
||||
|
public int RowBytes => Width * 4; |
||||
|
public PixelFormat Format => PixelFormat.Bgra8888; |
||||
|
|
||||
|
public Size 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 Size(dpix, dpiy); |
||||
|
} |
||||
|
} |
||||
|
return new Size(96, 96); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int Width => _bmpInfo.biWidth; |
||||
|
|
||||
|
public int Height => -_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(_pBitmap == IntPtr.Zero) |
||||
|
throw new ObjectDisposedException("Framebuffer"); |
||||
|
if (width == -1) |
||||
|
width = Width; |
||||
|
if (height == -1) |
||||
|
height = Height; |
||||
|
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, |
||||
|
0, (uint)Height, _pBitmap, 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 (_pBitmap == IntPtr.Zero) |
||||
|
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() |
||||
|
{ |
||||
|
if (_pBitmap != IntPtr.Zero) |
||||
|
{ |
||||
|
Marshal.FreeHGlobal(_pBitmap); |
||||
|
_pBitmap = IntPtr.Zero; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue