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