diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index f1c5d248ac..17b1cfba0a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -9,6 +9,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Skia.Android; using System; +using System.Collections.Generic; using Avalonia.Controls; namespace Avalonia.Android.Platform.SkiaPlatform @@ -95,6 +96,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform IPlatformHandle ITopLevelImpl.Handle => this; + public IEnumerable Surfaces => new object[] { this }; + public void Activate() { } @@ -188,5 +191,5 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } - } + } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 10fbe746e3..5fc7329f97 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -57,6 +57,9 @@ + + + diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 087a333e8a..77884acf73 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -37,6 +38,18 @@ namespace Avalonia.Platform /// IPlatformHandle Handle { get; } + /// + /// The list of native platform's surfaces that can be consumed by rendering subsystems. + /// + /// + /// Rendering platform will check that list and see if it can utilize one of them to output. + /// It should be enough to expose a native window handle via IPlatformHandle + /// and add support for framebuffer (even if it's emulated one) via IFramebufferPlatformSurface. + /// If you have some rendering platform that's tied to your particular windowing platform, + /// just expose some toolkit-specific object (e. g. Func<Gdk.Drawable> in case of GTK#+Cairo) + /// + IEnumerable Surfaces { get; } + /// /// Gets or sets a method called when the window is activated (receives focus). /// diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs new file mode 100644 index 0000000000..84988e912f --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -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 + { + /// + /// Provides a framebuffer descriptor for drawing. + /// + /// + /// Contents should be drawn on actual window after disposing + /// + ILockedFramebuffer Lock(); + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs new file mode 100644 index 0000000000..d6402d170d --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -0,0 +1,37 @@ +using System; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface ILockedFramebuffer : IDisposable + { + /// + /// Address of the first pixel + /// + IntPtr Address { get; } + + /// + /// Framebuffer width + /// + int Width { get; } + + /// + /// Framebuffer height + /// + int Height { get; } + + /// + /// Number of bytes per row + /// + int RowBytes { get; } + + /// + /// DPI of underling screen + /// + Size Dpi { get; } + + /// + /// Pixel format + /// + PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs new file mode 100644 index 0000000000..c9f8eabe97 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs @@ -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 + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 43c59f0b31..f314629d02 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -237,7 +237,7 @@ namespace Avalonia.Controls /// IRenderTarget IRenderRoot.CreateRenderTarget() { - return _renderInterface.CreateRenderTarget(PlatformImpl.Handle); + return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index c129cbd905..cdec0a07a1 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System.Collections.Generic; using System.IO; using Avalonia.Media; @@ -40,9 +41,11 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The platform handle for the renderer. + /// + /// The list of native platform surfaces that can be used for output. + /// /// An . - IRenderTarget CreateRenderTarget(IPlatformHandle handle); + IRenderTarget CreateRenderTarget(IEnumerable surfaces); /// /// Creates a render target bitmap implementation. diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index e59bcfffa1..e6c493320f 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Cairo.Media; using Avalonia.Cairo.Media.Imaging; using Avalonia.Media; @@ -50,24 +52,14 @@ namespace Avalonia.Cairo return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - var window = handle as Gtk.Window; - if (window != null) - { - window.DoubleBuffered = true; - return new RenderTarget(window); - } - var area = handle as Gtk.DrawingArea; - if (area != null) - { - area.DoubleBuffered = true; - return new RenderTarget(area); - } + var accessor = surfaces?.OfType>().FirstOrDefault(); + if(accessor!=null) + return new RenderTarget(accessor); throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from a '{0}' handle which isn't Gtk.Window or Gtk.DrawingArea", - handle.HandleDescriptor)); + "Don't know how to create a Cairo renderer from any of the provided surfaces.")); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Cairo/RenderTarget.cs b/src/Gtk/Avalonia.Cairo/RenderTarget.cs index d285986762..49f5e18dec 100644 --- a/src/Gtk/Avalonia.Cairo/RenderTarget.cs +++ b/src/Gtk/Avalonia.Cairo/RenderTarget.cs @@ -20,8 +20,8 @@ namespace Avalonia.Cairo public class RenderTarget : IRenderTarget { private readonly Surface _surface; - private readonly Gtk.Window _window; - private readonly Gtk.DrawingArea _area; + private readonly Func _drawableAccessor; + /// /// Initializes a new instance of the class. @@ -29,9 +29,9 @@ namespace Avalonia.Cairo /// The window. /// The width of the window. /// The height of the window. - public RenderTarget(Gtk.Window window) + public RenderTarget(Func drawable) { - _window = window; + _drawableAccessor = drawable; } public RenderTarget(ImageSurface surface) @@ -39,11 +39,6 @@ namespace Avalonia.Cairo _surface = surface; } - public RenderTarget(DrawingArea area) - { - _area = area; - } - /// /// Creates a cairo surface that targets a platform-specific resource. /// @@ -52,12 +47,10 @@ namespace Avalonia.Cairo public IDrawingContextImpl CreateMediaDrawingContext() { - if (_window != null) - return new Media.DrawingContext(_window.GdkWindow); + if (_drawableAccessor != null) + return new Media.DrawingContext(_drawableAccessor()); if (_surface != null) return new Media.DrawingContext(_surface); - if (_area != null) - return new Media.DrawingContext(_area.GdkWindow); throw new InvalidOperationException("Unspecified render target"); } diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index b001c7bc19..2c10b1188b 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -48,7 +48,9 @@ + + diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs new file mode 100644 index 0000000000..72c977b2b4 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -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; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs new file mode 100644 index 0000000000..76e9e8a307 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs @@ -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().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs index e075a31c4c..eca7c24136 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.Gtk { private Gtk.Window _window; private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget); - + public WindowImpl(Gtk.WindowType type) : base(new PlatformHandleAwareWindow(type)) { Init(); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index a9ecfa4058..c8f74b2d39 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -2,14 +2,11 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Reactive.Disposables; -using System.Runtime.InteropServices; -using Gdk; -using Avalonia.Controls; +using System.Collections.Generic; +using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Input; -using Avalonia.Threading; +using Gdk; using Action = System.Action; using WindowEdge = Avalonia.Controls.WindowEdge; @@ -21,8 +18,7 @@ namespace Avalonia.Gtk { private IInputRoot _inputRoot; protected Gtk.Widget _window; - public Gtk.Widget Widget => _window; - + private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -33,6 +29,7 @@ namespace Avalonia.Gtk protected WindowImplBase(Gtk.Widget window) { _window = window; + _framebuffer = new FramebufferManager(this); Init(); } @@ -43,7 +40,6 @@ namespace Avalonia.Gtk _imContext = new Gtk.IMMulticontext(); _imContext.Commit += ImContext_Commit; _window.Realized += OnRealized; - _window.DoubleBuffered = false; _window.Realize(); _window.ButtonPressEvent += OnButtonPressEvent; _window.ButtonReleaseEvent += OnButtonReleaseEvent; @@ -57,6 +53,8 @@ namespace Avalonia.Gtk } public IPlatformHandle Handle { get; private set; } + public Gtk.Widget Widget => _window; + public Gdk.Drawable CurrentDrawable { get; private set; } void OnRealized (object sender, EventArgs eventArgs) { @@ -127,6 +125,13 @@ namespace Avalonia.Gtk public Action ScalingChanged { get; set; } + public IEnumerable Surfaces => new object[] + { + Handle, + new Func(() => CurrentDrawable), + _framebuffer + }; + public IPopupImpl CreatePopup() { return new PopupImpl(); @@ -283,7 +288,9 @@ namespace Avalonia.Gtk void OnExposeEvent(object o, Gtk.ExposeEventArgs args) { + CurrentDrawable = args.Event.Window; Paint(args.Event.Area.ToAvalonia()); + CurrentDrawable = null; args.RetVal = true; } @@ -306,6 +313,7 @@ namespace Avalonia.Gtk public void Dispose() { + _framebuffer.Dispose(); _window.Hide(); _window.Dispose(); _window = null; diff --git a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs new file mode 100644 index 0000000000..3355839314 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs @@ -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 surfaces) + { + var surfaceView = surfaces?.OfType().FirstOrDefault(); + if (surfaceView == null) + throw new ArgumentException("Avalonia.Skia.Android is only capable of drawing on SurfaceView"); + return new WindowRenderTarget(surfaceView); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj index cf352607f0..fdde5553eb 100644 --- a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj +++ b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj @@ -87,6 +87,7 @@ + diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 59ad3b9fbb..03ddf49851 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -26,14 +26,14 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + private readonly SurfaceView _surfaceView; Bitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget(SurfaceView surfaceView) { - _hwnd = hwnd; + _surfaceView = surfaceView; FixSize(); } @@ -63,9 +63,8 @@ namespace Avalonia.Skia private void GetPlatformWindowSize(out int w, out int h) { - var surfaceView = _hwnd as SurfaceView; - w = surfaceView.Width; - h = surfaceView.Height; + w = _surfaceView.Width; + h = _surfaceView.Height; } public override DrawingContext CreateDrawingContext() @@ -85,11 +84,10 @@ namespace Avalonia.Skia public void Present() { - var surfaceView = _hwnd as SurfaceView; Canvas canvas = null; try { - canvas = surfaceView.Holder.LockCanvas(null); + canvas = _surfaceView.Holder.LockCanvas(null); _bitmap.UnlockPixels(); canvas.DrawBitmap(_bitmap, 0, 0, null); } @@ -99,7 +97,7 @@ namespace Avalonia.Skia finally { if (canvas != null) - surfaceView.Holder.UnlockCanvasAndPost(canvas); + _surfaceView.Holder.UnlockCanvasAndPost(canvas); } _bitmap.UnlockPixels(); diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs index 62d8e7bd18..fc6c994bd5 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs @@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android { _renderTarget = AvaloniaLocator.Current.GetService() - .CreateRenderTarget(this); + .CreateRenderTarget(new object[]{this}); } protected override void Draw() diff --git a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj index d7d865225a..62586f750d 100644 --- a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj +++ b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj @@ -72,11 +72,8 @@ + - - UnmanagedMethods.cs - - diff --git a/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs new file mode 100644 index 0000000000..9382a4f6e2 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs @@ -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 surfaces) + { + var fb = surfaces?.OfType().FirstOrDefault(); + if (fb == null) + throw new Exception("Avalonia.Skia.Deskop currently only supports framebuffer render target"); + return new FramebufferRenderTarget(fb); + } + } +} diff --git a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj index 28892c3c01..c8a7f36317 100644 --- a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj +++ b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj @@ -37,6 +37,7 @@ true + diff --git a/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs new file mode 100644 index 0000000000..fdc7389800 --- /dev/null +++ b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs @@ -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 surfaces) + { + return new WindowRenderTarget(); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs index 083b611d5c..abe663e44b 100644 --- a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs @@ -26,14 +26,13 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + SKBitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget() { - _hwnd = hwnd; FixSize(); } diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems index 426be548e2..303de8ee56 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems @@ -12,6 +12,7 @@ + diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs new file mode 100644 index 0000000000..3d3e322e0d --- /dev/null +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -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)); + } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f8558c7428..4cfe1e3e40 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Platform; @@ -7,7 +8,7 @@ using SkiaSharp; namespace Avalonia.Skia { - public class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory + public partial class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory { public IBitmapImpl CreateBitmap(int width, int height) { @@ -63,10 +64,5 @@ namespace Avalonia.Skia return new BitmapImpl(width, height); } - - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) - { - return new WindowRenderTarget(handle); - } } } diff --git a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs index a4e29fc022..db9d10a346 100644 --- a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs @@ -1,6 +1,7 @@ namespace Avalonia.Skia { +#if !DESKTOP // not sure we need this yet internal class WindowDrawingContextImpl : DrawingContextImpl { @@ -18,4 +19,5 @@ namespace Avalonia.Skia _target.Present(); } } +#endif } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index e53692bef0..34595fecc8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -2,11 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Avalonia.Direct2D1.Media; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Rendering; namespace Avalonia @@ -99,18 +102,16 @@ namespace Avalonia.Direct2D1 return new Renderer(root, renderLoop); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - if (handle.HandleDescriptor == "HWND") + var nativeWindow = surfaces?.OfType().FirstOrDefault(); + if (nativeWindow != null) { - return new HwndRenderTarget(handle.Handle); - } - else - { - throw new NotSupportedException(string.Format( - "Don't know how to create a Direct2D1 renderer from a '{0}' handle", - handle.HandleDescriptor)); + if(nativeWindow.HandleDescriptor != "HWND") + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); + return new HwndRenderTarget(nativeWindow); } + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49d4c91c52..49402d54b9 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -11,16 +13,16 @@ namespace Avalonia.Direct2D1 { class HwndRenderTarget : SwapChainRenderTarget { - private readonly IntPtr _hwnd; + private readonly IPlatformHandle _window; - public HwndRenderTarget(IntPtr hwnd) + public HwndRenderTarget(IPlatformHandle window) { - _hwnd = hwnd; + _window = window; } protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc); + return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() @@ -30,7 +32,7 @@ namespace Avalonia.Direct2D1 uint dpix, dpiy; var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, + _window.Handle, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); if (UnmanagedMethods.GetDpiForMonitor( @@ -49,7 +51,7 @@ namespace Avalonia.Direct2D1 protected override Size2 GetWindowSize() { UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); + UnmanagedMethods.GetClientRect(_window.Handle, out rc); return new Size2(rc.right - rc.left, rc.bottom - rc.top); } } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 4b190dc469..851233a19e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -69,6 +69,8 @@ Component + + diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs new file mode 100644 index 0000000000..ecd05f41b4 --- /dev/null +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -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(); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c2785095d0..4d5af748f4 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -568,6 +568,27 @@ namespace Avalonia.Win32.Interop public byte rgbReserved; } + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFOHEADER + { + public uint biSize; + public int biWidth; + public int biHeight; + public ushort biPlanes; + public ushort biBitCount; + public uint biCompression; + public uint biSizeImage; + public int biXPelsPerMeter; + public int biYPelsPerMeter; + public uint biClrUsed; + public uint biClrImportant; + + public void Init() + { + biSize = (uint)Marshal.SizeOf(this); + } + } + [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO { @@ -859,6 +880,31 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + [DllImport("gdi32.dll")] + public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint + dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, + IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw); + [DllImport("gdi32.dll")] + public static extern int DeleteObject(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFileMapping(IntPtr hFile, + IntPtr lpFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + string lpName); + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -1177,6 +1223,7 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } @@ -1197,5 +1244,6 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } } diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs new file mode 100644 index 0000000000..f7ea79e78a --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1ddd1ed10f..9680f31973 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -11,6 +11,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Input; @@ -35,10 +36,12 @@ namespace Avalonia.Win32 private bool _decorated = true; private double _scaling = 1; private WindowState _showWindowState; + private FramebufferManager _framebuffer; public WindowImpl() { CreateWindow(); + _framebuffer = new FramebufferManager(_hwnd); s_instances.Add(this); } @@ -161,6 +164,11 @@ namespace Avalonia.Win32 } } + public IEnumerable Surfaces => new object[] + { + Handle, _framebuffer + }; + public void Activate() { UnmanagedMethods.SetActiveWindow(_hwnd); @@ -174,6 +182,7 @@ namespace Avalonia.Win32 public void Dispose() { s_instances.Remove(this); + _framebuffer.Dispose(); UnmanagedMethods.DestroyWindow(_hwnd); } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 8b14d09573..f9df1fac5e 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -149,6 +149,9 @@ namespace Avalonia.iOS } public Size MaxClientSize => Bounds.Size.ToAvalonia(); + + public IEnumerable Surfaces => new object[] { this }; + public void SetTitle(string title) { //Not supported diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index e4e7551f5c..e00d504124 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -340,7 +340,7 @@ namespace Avalonia.Input.UnitTests throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 3d6a9093e4..ac31b3852b 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -20,7 +20,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); }