flabbet 1 år sedan
förälder
incheckning
def212a742
29 ändrade filer med 787 tillägg och 39 borttagningar
  1. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs
  2. 8 0
      src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs
  3. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs
  4. 12 0
      src/PixiEditor.Engine.AvaloniaPlatform/AppBuilderExtensions.cs
  5. 9 0
      src/PixiEditor.Engine.AvaloniaPlatform/EmptyDisposable.cs
  6. 3 0
      src/PixiEditor.Engine.AvaloniaPlatform/Exceptions/PlatformNotInitializedException.cs
  7. 4 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEditor.Engine.AvaloniaPlatform.csproj
  8. 25 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineGpuRenderSession.cs
  9. 37 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineGraphics.cs
  10. 17 1
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEnginePlatform.cs
  11. 50 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaGpu.cs
  12. 27 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaGpuRenderTarget.cs
  13. 32 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaSurface.cs
  14. 12 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineTopLevel.cs
  15. 45 17
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineTopLevelImpl.cs
  16. 23 0
      src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineWindowingPlatform.cs
  17. 199 0
      src/PixiEditor.Engine.AvaloniaPlatform/Windowing/PixiEngineWindowImpl.cs
  18. 13 1
      src/PixiEditor.Engine/PixiEngine.cs
  19. 39 2
      src/PixiEditor.Engine/SkiaPixiEngine.cs
  20. 77 17
      src/PixiEditor.Engine/Window.cs
  21. 31 0
      src/PixiEditor.sln
  22. 10 0
      src/PixiEngineAvaloniaApp/App.axaml
  23. 23 0
      src/PixiEngineAvaloniaApp/App.axaml.cs
  24. 9 0
      src/PixiEngineAvaloniaApp/MainWindow.axaml
  25. 11 0
      src/PixiEngineAvaloniaApp/MainWindow.axaml.cs
  26. 23 0
      src/PixiEngineAvaloniaApp/PixiEngineAvaloniaApp.csproj
  27. 20 0
      src/PixiEngineAvaloniaApp/Program.cs
  28. 18 0
      src/PixiEngineAvaloniaApp/app.manifest
  29. 4 1
      src/PixiEngineSampleApp/Program.cs

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs

@@ -17,4 +17,5 @@ public interface ISurfaceImplementation
     public DrawingSurface Create(ImageInfo imageInfo);
     public void Dispose(DrawingSurface drawingSurface);
     public object GetNativeSurface(IntPtr objectPointer);
+    public void Flush(DrawingSurface drawingSurface);
 }

+ 8 - 0
src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs

@@ -11,6 +11,8 @@ namespace PixiEditor.DrawingApi.Core.Surface
     {
         public override object Native => DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurface(ObjectPointer);
         public Canvas Canvas { get; private set; }
+        public bool IsDisposed { get; private set; }
+
         public event SurfaceChangedEventHandler? Changed;
 
         public DrawingSurface(IntPtr objPtr, Canvas canvas) : base(objPtr)
@@ -67,6 +69,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
 
         public override void Dispose()
         {
+            IsDisposed = true;
             Canvas.Changed -= OnCanvasChanged;
             Canvas.Dispose(); // TODO: make sure this is correct
             DrawingBackendApi.Current.SurfaceImplementation.Dispose(this);
@@ -76,5 +79,10 @@ namespace PixiEditor.DrawingApi.Core.Surface
         {
             Changed?.Invoke(changedrect);
         }
+
+        public void Flush()
+        {
+            DrawingBackendApi.Current.SurfaceImplementation.Flush(this);
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs

@@ -115,5 +115,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             
             return surface;
         }
+        
+        public void Flush(DrawingSurface drawingSurface)
+        {
+            ManagedInstances[drawingSurface.ObjectPointer].Flush(true);
+        }
     }
 }

+ 12 - 0
src/PixiEditor.Engine.AvaloniaPlatform/AppBuilderExtensions.cs

@@ -0,0 +1,12 @@
+using Avalonia;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public static class AppBuilderExtensions
+{
+    public static AppBuilder UsePixiEngine(this AppBuilder builder)
+        => builder
+            .UseStandardRuntimePlatformSubsystem()
+            .UseSkia()
+            .UseWindowingSubsystem(PixiEnginePlatform.Initialize);
+}

+ 9 - 0
src/PixiEditor.Engine.AvaloniaPlatform/EmptyDisposable.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class EmptyDisposable : IDisposable
+{
+    public static EmptyDisposable Instance { get; } = new EmptyDisposable();
+    public void Dispose()
+    {
+    }
+}

+ 3 - 0
src/PixiEditor.Engine.AvaloniaPlatform/Exceptions/PlatformNotInitializedException.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.Engine.AvaloniaPlatform.Exceptions;
+
+public class PlatformNotInitializedException(string msg) : Exception(msg);

+ 4 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEditor.Engine.AvaloniaPlatform.csproj

@@ -12,4 +12,8 @@
     <PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)"/>
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\PixiEditor.Engine\PixiEditor.Engine.csproj" />
+  </ItemGroup>
+
 </Project>

+ 25 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineGpuRenderSession.cs

@@ -0,0 +1,25 @@
+using Avalonia.Skia;
+using SkiaSharp;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineGpuRenderSession : ISkiaGpuRenderSession
+{
+    public PixiEngineSkiaSurface Surface { get; }
+    public GRContext GrContext { get; }
+    
+    SKSurface ISkiaGpuRenderSession.SkSurface => Surface.BackendSurface.Native as SKSurface;
+    double ISkiaGpuRenderSession.ScaleFactor => Surface.RenderScaling;
+    GRSurfaceOrigin ISkiaGpuRenderSession.SurfaceOrigin => GRSurfaceOrigin.BottomLeft;
+    
+    public PixiEngineGpuRenderSession(PixiEngineSkiaSurface surface, GRContext context)
+    {
+        Surface = surface;
+        GrContext = context;
+    }
+    
+    public void Dispose()
+    {
+        (Surface.BackendSurface.Native as SKSurface)?.Flush(true);
+    }
+}

+ 37 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineGraphics.cs

@@ -0,0 +1,37 @@
+using Avalonia.Platform;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineGraphics : IPlatformGraphics, IDisposable
+{
+    private PixiEngineSkiaGpu? _gpu;
+    private SkiaPixiEngine _engine;
+    
+    public PixiEngineGraphics(SkiaPixiEngine engine)
+    {
+        _engine = engine;
+    }
+
+    public IPlatformGraphicsContext CreateContext()
+    {
+        throw new System.NotSupportedException();
+    } 
+    
+    public PixiEngineSkiaGpu GetSharedContext()
+    {
+        if (_gpu == null)
+        {
+            _gpu = new PixiEngineSkiaGpu(_engine);
+        }
+        
+        return _gpu;
+    }
+
+    IPlatformGraphicsContext IPlatformGraphics.GetSharedContext() => GetSharedContext();
+
+    public bool UsesSharedContext => true;
+    public void Dispose()
+    {
+        _gpu?.Dispose();
+    }
+}

+ 17 - 1
src/PixiEditor.Engine.AvaloniaPlatform/PixiEnginePlatform.cs

@@ -1,11 +1,27 @@
 using Avalonia;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
+using PixiEditor.Engine.AvaloniaPlatform.Exceptions;
 
 namespace PixiEditor.Engine.AvaloniaPlatform;
 
 internal class PixiEnginePlatform
 {
+    private static Compositor? _compositor;
+    public static Compositor Compositor => _compositor ?? throw new PlatformNotInitializedException($"({nameof(PixiEnginePlatform)})was not initialized.");
     public static void Initialize()
     {
-        //AvaloniaLocator.CurrentMutable
+        SkiaPixiEngine engine = SkiaPixiEngine.Create();
+        var graphics = new PixiEngineGraphics(engine);
+        
+        AvaloniaLocator.CurrentMutable
+            .Bind<IPlatformGraphics>().ToConstant(graphics)
+            .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
+            .Bind<IWindowingPlatform>().ToConstant(new PixiEngineWindowingPlatform());
+
+        _compositor = new Compositor(graphics);
     }
 }

+ 50 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaGpu.cs

@@ -0,0 +1,50 @@
+using Avalonia;
+using Avalonia.Skia;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using Silk.NET.Core.Contexts;
+using Silk.NET.OpenGL;
+using SkiaSharp;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineSkiaGpu : ISkiaGpu
+{
+    private GRContext context;
+    private IGLContext glContext;
+
+    public bool IsLost => context.IsAbandoned;
+
+    public PixiEngineSkiaGpu(SkiaPixiEngine engine)
+    {
+        engine.SetupBackendWindowLess();
+        context = engine.GrContext;
+        glContext = engine.GlContext;
+    }
+    
+
+    public object? TryGetFeature(Type featureType)
+    {
+        return null;
+    }
+
+    public IDisposable EnsureCurrent()
+    {
+        return EmptyDisposable.Instance;
+    }
+
+    public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces) =>
+        surfaces.OfType<PixiEngineSkiaSurface>().FirstOrDefault() is { } surface 
+            ? new PixiEngineSkiaGpuRenderTarget(surface, context)
+            : null;
+
+    ISkiaSurface? ISkiaGpu.TryCreateSurface(PixelSize size, ISkiaGpuRenderSession? session)
+    {
+        return new PixiEngineSkiaSurface(DrawingSurface.Create(new ImageInfo(size.Width, size.Height)), session?.ScaleFactor ?? 1);
+    }
+
+    public void Dispose()
+    {
+        context.Dispose();
+    }
+}

+ 27 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaGpuRenderTarget.cs

@@ -0,0 +1,27 @@
+using Avalonia.Skia;
+using SkiaSharp;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineSkiaGpuRenderTarget : ISkiaGpuRenderTarget
+{
+    private readonly PixiEngineSkiaSurface _surface;
+    private readonly GRContext _context;
+    private readonly double _renderScaling;
+    
+    public bool IsCorrupted => _surface.IsDisposed || _context.IsAbandoned || _renderScaling != _surface.RenderScaling; 
+    
+    public PixiEngineSkiaGpuRenderTarget(PixiEngineSkiaSurface surface, GRContext context)
+    {
+        _surface = surface;
+        _context = context;
+        _renderScaling = surface.RenderScaling;
+    }
+
+    public ISkiaGpuRenderSession BeginRenderingSession()
+    {
+        return new PixiEngineGpuRenderSession(_surface, _context);
+    }
+
+    public void Dispose() { }
+}

+ 32 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineSkiaSurface.cs

@@ -0,0 +1,32 @@
+using Avalonia.Skia;
+using PixiEditor.DrawingApi.Core.Surface;
+using SkiaSharp;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineSkiaSurface : ISkiaSurface
+{
+    public DrawingSurface BackendSurface { get; }
+
+    SKSurface ISkiaSurface.Surface => BackendSurface.Native as SKSurface;
+    public bool CanBlit => false;
+    public bool IsDisposed => BackendSurface.IsDisposed;
+    public double RenderScaling { get; }
+    
+
+    public PixiEngineSkiaSurface(DrawingSurface surface, double renderScaling)
+    {
+        BackendSurface = surface;
+        RenderScaling = renderScaling;
+    }
+
+    public void Blit(SKCanvas canvas)
+    {
+        throw new NotSupportedException();
+    }
+
+    public void Dispose()
+    {
+        BackendSurface.Dispose();
+    }
+}

+ 12 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineTopLevel.cs

@@ -0,0 +1,12 @@
+using Avalonia.Controls.Embedding;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public sealed class PixiEngineTopLevel : EmbeddableControlRoot
+{
+    internal PixiEngineTopLevelImpl Impl { get; }
+    
+    internal PixiEngineTopLevel(PixiEngineTopLevelImpl impl)
+        : base(impl)
+        => Impl = impl;
+}

+ 45 - 17
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineTopLevelImpl.cs

@@ -14,58 +14,86 @@ internal sealed class PixiEngineTopLevelImpl : ITopLevelImpl
     public double RenderScaling { get; }
     public IEnumerable<object> Surfaces { get; }
     public Action<RawInputEventArgs>? Input { get; set; }
-    public Action<Rect>? Paint { get; set;  }
+    public Action<Rect>? Paint { get; set; }
     public Action<Size, WindowResizeReason>? Resized { get; set; }
     public Action<double>? ScalingChanged { get; set; }
     public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
     public Compositor Compositor { get; }
     public Action? Closed { get; set; }
     public Action? LostFocus { get; set; }
-    public WindowTransparencyLevel TransparencyLevel { get; }
     public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
 
+    public WindowTransparencyLevel TransparencyLevel
+    {
+        get => _transparencyLevel;
+        private set
+        {
+            if (_transparencyLevel.Equals(value))
+            {
+                return;
+            }
+            
+            _transparencyLevel = value;
+            TransparencyLevelChanged?.Invoke(value);
+        }
+    }
+
+    private IInputRoot? _inputRoot;
+    private ICursorImpl _cursor;
+    private WindowTransparencyLevel _transparencyLevel;
+
+    public PixiEngineTopLevelImpl(Compositor compositor)
+    {
+        Compositor = compositor;
+    }
+
     public object? TryGetFeature(Type featureType)
     {
-        throw new NotImplementedException();
+        return null;
     }
 
-    public void SetInputRoot(IInputRoot inputRoot)
+    void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot)
     {
-        throw new NotImplementedException();
+        _inputRoot = inputRoot;
     }
 
     public Point PointToClient(PixelPoint point)
     {
-        throw new NotImplementedException();
+        return point.ToPoint(RenderScaling);
     }
 
     public PixelPoint PointToScreen(Point point)
     {
-        throw new NotImplementedException();
+        return PixelPoint.FromPoint(point, RenderScaling);
     }
 
     public void SetCursor(ICursorImpl? cursor)
     {
-        throw new NotImplementedException();
+        _cursor = cursor;
     }
 
-    public IPopupImpl? CreatePopup()
+    IPopupImpl? ITopLevelImpl.CreatePopup()
     {
-        throw new NotImplementedException();
+        return null;
     }
 
-    public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
+    void ITopLevelImpl.SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
     {
-        throw new NotImplementedException();
+        foreach (var transparencyLevel in transparencyLevels)
+        {
+            if (transparencyLevel == WindowTransparencyLevel.Transparent ||
+                transparencyLevel == WindowTransparencyLevel.None)
+            {
+                TransparencyLevel = transparencyLevel;
+                return;
+            }
+        }
     }
 
-    public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
-    {
-        throw new NotImplementedException();
-    }
+    public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
 
     public void Dispose()
     {
-        throw new NotImplementedException();
+       Closed?.Invoke(); 
     }
 }

+ 23 - 0
src/PixiEditor.Engine.AvaloniaPlatform/PixiEngineWindowingPlatform.cs

@@ -0,0 +1,23 @@
+using Avalonia.Platform;
+using PixiEditor.Engine.AvaloniaPlatform.Windowing;
+
+namespace PixiEditor.Engine.AvaloniaPlatform;
+
+public class PixiEngineWindowingPlatform() : IWindowingPlatform
+{
+    public IWindowImpl CreateWindow()
+    {
+        Window window = new Window();
+        return new PixiEngineWindowImpl(window);
+    }
+
+    public IWindowImpl CreateEmbeddableWindow()
+    {
+        return CreateWindow();
+    }
+
+    public ITrayIconImpl? CreateTrayIcon()
+    {
+        return null;
+    }
+}

+ 199 - 0
src/PixiEditor.Engine.AvaloniaPlatform/Windowing/PixiEngineWindowImpl.cs

@@ -0,0 +1,199 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+
+namespace PixiEditor.Engine.AvaloniaPlatform.Windowing;
+
+public class PixiEngineWindowImpl : IWindowImpl
+{
+    public WindowState WindowState { get; set; }
+    public Action<WindowState>? WindowStateChanged { get; set; }
+    public Action? GotInputWhenDisabled { get; set; }
+    public Func<WindowCloseReason, bool>? Closing { get; set; }
+    public bool IsClientAreaExtendedToDecorations { get; }
+    public Action<bool>? ExtendClientAreaToDecorationsChanged { get; set; }
+    public bool NeedsManagedDecorations { get; }
+    public Thickness ExtendedMargins { get; }
+    public Thickness OffScreenMargin { get; }
+
+    public WindowTransparencyLevel TransparencyLevel
+    {
+        get => _transparencyLevel;
+        private set
+        {
+            if (_transparencyLevel.Equals(value))
+            {
+                return;
+            }
+
+            _transparencyLevel = value;
+            TransparencyLevelChanged?.Invoke(value);
+        }
+    }
+
+    public Size ClientSize { get; }
+    public Size? FrameSize { get; }
+    public double RenderScaling { get; } = 1;
+    public IEnumerable<object> Surfaces { get; }
+    public Action<RawInputEventArgs>? Input { get; set; }
+    public Action<Rect>? Paint { get; set; }
+    public Action<Size, WindowResizeReason>? Resized { get; set; }
+    public Action<double>? ScalingChanged { get; set; }
+    public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
+    public Compositor Compositor { get; }
+    public Action? Closed { get; set; }
+    public Action? LostFocus { get; set; }
+    public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
+
+    public double DesktopScaling { get; } = 1;
+    public PixelPoint Position { get; }
+    public Action<PixelPoint>? PositionChanged { get; set; }
+    public Action? Deactivated { get; set; }
+    public Action? Activated { get; set; }
+    public IPlatformHandle Handle { get; }
+    public Size MaxAutoSizeHint { get; }
+    public IScreenImpl Screen { get; }
+
+    private IInputRoot? _inputRoot;
+    private ICursorImpl? _cursor;
+    private WindowTransparencyLevel _transparencyLevel;
+    private Window _underlyingWindow;
+
+    public PixiEngineWindowImpl(Window underlyingWindow)
+    {
+        _underlyingWindow = underlyingWindow;
+        Compositor = PixiEnginePlatform.Compositor;
+    }
+    
+    object? IOptionalFeatureProvider.TryGetFeature(Type featureType) { return null; }
+
+    void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot)
+    {
+        _inputRoot = inputRoot;
+    }
+
+    public Point PointToClient(PixelPoint point)
+    {
+        return point.ToPoint(RenderScaling);
+    }
+
+    public PixelPoint PointToScreen(Point point)
+    {
+        return PixelPoint.FromPoint(point, RenderScaling);
+    }
+
+    public void SetCursor(ICursorImpl? cursor)
+    {
+        _cursor = cursor;
+    }
+
+    public IPopupImpl? CreatePopup()
+    {
+        return null;
+    }
+
+    public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
+    {
+        foreach (var transparencyLevel in transparencyLevels)
+        {
+            if (transparencyLevel == WindowTransparencyLevel.Transparent ||
+                transparencyLevel == WindowTransparencyLevel.None)
+            {
+                TransparencyLevel = transparencyLevel;
+                return;
+            }
+        }
+    }
+
+    public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
+    {
+    }
+
+    public void Show(bool activate, bool isDialog)
+    {
+        _underlyingWindow.Show();
+    }
+
+    public void Hide()
+    {
+        _underlyingWindow.Hide();
+    }
+
+    public void Activate()
+    {
+        _underlyingWindow.Activate();
+    }
+
+    public void SetTopmost(bool value)
+    {
+        _underlyingWindow.TopMost = value;
+    }
+
+    public void SetTitle(string? title)
+    {
+        _underlyingWindow.Title = title;
+    }
+
+    public void SetParent(IWindowImpl parent)
+    {
+    }
+
+    public void SetEnabled(bool enable)
+    {
+    }
+
+    public void SetSystemDecorations(SystemDecorations enabled)
+    {
+    }
+
+    public void SetIcon(IWindowIconImpl? icon)
+    {
+    }
+
+    public void ShowTaskbarIcon(bool value)
+    {
+    }
+
+    public void CanResize(bool value)
+    {
+    }
+
+    public void BeginMoveDrag(PointerPressedEventArgs e)
+    {
+    }
+
+    public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e)
+    {
+    }
+
+    public void Resize(Size clientSize, WindowResizeReason reason = WindowResizeReason.Application)
+    {
+    }
+
+    public void Move(PixelPoint point)
+    {
+    }
+
+    public void SetMinMaxSize(Size minSize, Size maxSize)
+    {
+    }
+
+    public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
+    {
+    }
+
+    public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
+    {
+    }
+
+    public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
+    {
+    }
+
+    public void Dispose()
+    {
+    }
+}

+ 13 - 1
src/PixiEditor.Engine/PixiEngine.cs

@@ -1,19 +1,31 @@
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Skia;
+using Silk.NET.Core.Contexts;
+using Silk.NET.Windowing;
 using SkiaSharp;
 
 namespace PixiEditor.Engine;
 
 public abstract class PixiEngine
 {
+    public static PixiEngine ActiveEngine { get; protected set; }
+    
+    public GRContext GrContext { get; protected set; }
+    public IGLContext GlContext { get; protected set; }
     private IDrawingBackend _drawingBackend;
     
     internal PixiEngine(IDrawingBackend drawingBackend)
     {
         _drawingBackend = drawingBackend;
+        ActiveEngine = this;
+    }
+
+    public virtual IWindow GetWindow(WindowOptions options)
+    {
+        return Silk.NET.Windowing.Window.Create(options);   
     }
 
-    protected virtual void Setup()
+    protected void SetBackendActive()
     {
         DrawingBackendApi.SetupBackend(_drawingBackend);
     }

+ 39 - 2
src/PixiEditor.Engine/SkiaPixiEngine.cs

@@ -1,6 +1,11 @@
 using PixiEditor.DrawingApi.Core.Bridge;
 using PixiEditor.DrawingApi.Skia;
 using PixiEditor.DrawingApi.Skia.Exceptions;
+using PixiEditor.Numerics;
+using Silk.NET.GLFW;
+using Silk.NET.Maths;
+using Silk.NET.OpenGL;
+using Silk.NET.Windowing;
 using SkiaSharp;
 
 namespace PixiEditor.Engine;
@@ -8,20 +13,52 @@ namespace PixiEditor.Engine;
 public sealed class SkiaPixiEngine : PixiEngine
 {
     private SkiaDrawingBackend _drawingBackend;
+    
+    private IWindow _mainWindow;
+    
     private SkiaPixiEngine(IDrawingBackend drawingBackend) : base(drawingBackend)
     {
         _drawingBackend = (SkiaDrawingBackend)drawingBackend;
     }
-
+    
     public static SkiaPixiEngine Create()
     {
         return new SkiaPixiEngine(new SkiaDrawingBackend());
     }
 
+    public override IWindow GetWindow(WindowOptions options)
+    {
+        if (!_mainWindow.IsVisible)
+        {
+            _mainWindow.IsVisible = true;
+            _mainWindow.Size = options.Size;
+            _mainWindow.Title = options.Title;
+            
+            return _mainWindow;
+        }
+        
+        return base.GetWindow(options); 
+    }
+
+    public void SetupBackendWindowLess()
+    {
+        _mainWindow = Silk.NET.Windowing.Window.Create(WindowOptions.Default with { IsVisible = false, Size = new Vector2D<int>(1, 1)}); 
+        _mainWindow.Load += () =>
+        {
+            GRGlGetProcedureAddressDelegate proc = Glfw.GetApi().GetProcAddress;
+            GrContext = GRContext.CreateGl(GRGlInterface.Create(proc));
+            GlContext = _mainWindow.GLContext;
+            Setup(GrContext);
+        };
+        
+        _mainWindow.Initialize();
+    }
+
     public void Setup(GRContext context)
     {
-        Setup();
+        SetBackendActive();
         _drawingBackend.GraphicsContext = context;
+        GrContext = context;
         _drawingBackend.Setup();
     }
     

+ 77 - 17
src/PixiEditor.Engine/Window.cs

@@ -11,47 +11,67 @@ namespace PixiEditor.Engine;
 public class Window
 {
     private GL gl;
-    private IWindow _window;
+    protected IWindow _window;
     private GRContext _grContext;
 
-    private SKSurface frontBufferSurface;
-    
+    private SKSurface? frontBufferSurface;
+
+    internal IWindow Native => _window;
+
     public string Title
     {
         get => _window.Title;
         set => _window.Title = value;
     }
-    
+
     public VecI Size
     {
         get => _window.Size.ToVecI();
         set => _window.Size = value.ToVector2D();
     }
 
+    public bool TopMost
+    {
+        get => _window.TopMost;
+        set => _window.TopMost = value;
+    }
+
     public event Action<SKSurface, double> Render;
     public event Action Init;
     internal event Action<GRContext> InitWithGrContext;
 
-    public Window(string title, VecI size)
+    private bool ran = false;
+
+    public Window(string title = "", VecI size = default)
     {
         WindowOptions options = WindowOptions.Default with
         {
-            Title = title, Size = size.ToVector2D()
+            Title = title, Size = size == default ? new Vector2D<int>(600, 600) : size.ToVector2D()
         };
 
-        _window = Silk.NET.Windowing.Window.Create(options);
+        if (PixiEngine.ActiveEngine == null)
+            throw new InvalidOperationException("PixiEngine is not initialized.");
+
+        _window = PixiEngine.ActiveEngine.GetWindow(options);
 
-        _window.Load += () =>
+        if (!_window.IsInitialized)
         {
-            gl = GL.GetApi(_window);
-            frontBufferSurface = SKSurface.Create(new SKImageInfo(1200, 600));
+            _window.Load += () =>
+            {
+                gl = GL.GetApi(_window);
+                frontBufferSurface = SKSurface.Create(new SKImageInfo(1200, 600));
 
-            _window.GLContext.MakeCurrent();
+                InitSkiaSurface(_window.FramebufferSize);
 
-            InitSkiaSurface();
+                InitWithGrContext?.Invoke(_grContext);
+                Init?.Invoke();
+            };
+        }
 
-            InitWithGrContext?.Invoke(_grContext);
-            Init?.Invoke();
+        _window.FramebufferResize += (newSize) =>
+        {
+            frontBufferSurface?.Dispose();
+            InitSkiaSurface(newSize);
         };
 
         _window.Render += OnRender;
@@ -59,9 +79,25 @@ public class Window
 
     public void Run()
     {
+        if (_window.IsInitialized)
+        {
+            if (frontBufferSurface == null)
+            {
+                InitSkiaSurface(_window.FramebufferSize);
+            }
+
+            Init?.Invoke();
+        }
+
+        ran = true;
         _window.Run();
     }
 
+    internal void Initialize()
+    {
+        _window.Initialize();
+    }
+
     private void OnRender(double deltaTime)
     {
         frontBufferSurface.Canvas.Clear(SKColors.White);
@@ -69,11 +105,35 @@ public class Window
         frontBufferSurface.Canvas.Flush();
     }
 
-    private void InitSkiaSurface()
+    private void InitSkiaSurface(Vector2D<int> size)
     {
-        _grContext = GRContext.CreateGl(GRGlInterface.Create(Glfw.GetApi().GetProcAddress));
+        _grContext = PixiEngine.ActiveEngine.GrContext;
         var frameBuffer = new GRGlFramebufferInfo(0, SKColorType.RgbaF16.ToGlSizedFormat());
-        GRBackendRenderTarget target = new GRBackendRenderTarget(_window.Size.X, _window.Size.Y, 4, 0, frameBuffer);
+        GRBackendRenderTarget target = new GRBackendRenderTarget(size.X, size.Y, 4, 0, frameBuffer);
         frontBufferSurface = SKSurface.Create(_grContext, target, GRSurfaceOrigin.BottomLeft, SKColorType.RgbaF16);
     }
+
+    public void Show()
+    {
+        if (!ran)
+        {
+            Run();
+        }
+        
+        _window.IsVisible = true;
+    }
+
+    public void Hide()
+    {
+        _window.IsVisible = false;
+    }
+
+    public void Activate()
+    {
+    }
+
+    public void Close()
+    {
+        _window.Close();
+    }
 }

+ 31 - 0
src/PixiEditor.sln

@@ -110,6 +110,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Engine.AvaloniaP
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEngineSampleApp", "PixiEngineSampleApp\PixiEngineSampleApp.csproj", "{AAC75D6D-AAD0-4144-9951-9EC58CF09C80}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEngineAvaloniaApp", "PixiEngineAvaloniaApp\PixiEngineAvaloniaApp.csproj", "{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x64 = Debug|x64
@@ -1592,6 +1594,34 @@ Global
 		{AAC75D6D-AAD0-4144-9951-9EC58CF09C80}.Steam|x64.Build.0 = Debug|Any CPU
 		{AAC75D6D-AAD0-4144-9951-9EC58CF09C80}.Steam|ARM64.ActiveCfg = Debug|Any CPU
 		{AAC75D6D-AAD0-4144-9951-9EC58CF09C80}.Steam|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Debug|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevRelease|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevRelease|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevRelease|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevRelease|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevSteam|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevSteam|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevSteam|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.DevSteam|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.MSIX|ARM64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Release|x64.ActiveCfg = Release|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Release|x64.Build.0 = Release|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Release|ARM64.Build.0 = Release|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Steam|x64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Steam|x64.Build.0 = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Steam|ARM64.ActiveCfg = Debug|Any CPU
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3}.Steam|ARM64.Build.0 = Debug|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -1641,6 +1671,7 @@ Global
 		{4B1436E5-F586-4165-946D-CE2AE653561A} = {55A3BEC1-C848-4BA9-BB16-028467CC46C8}
 		{78D6EAAD-9660-4D96-A887-0D3D3D7D8F4C} = {55A3BEC1-C848-4BA9-BB16-028467CC46C8}
 		{AAC75D6D-AAD0-4144-9951-9EC58CF09C80} = {5AFBF881-C054-4CE4-8159-8D4017FFD27A}
+		{D63AC04D-075B-4A8B-8FDC-C46975AFEFF3} = {5AFBF881-C054-4CE4-8159-8D4017FFD27A}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {D04B4AB0-CA33-42FD-A909-79966F9255C5}

+ 10 - 0
src/PixiEngineAvaloniaApp/App.axaml

@@ -0,0 +1,10 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="PixiEngineAvaloniaApp.App"
+             RequestedThemeVariant="Default">
+             <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
+
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+</Application>

+ 23 - 0
src/PixiEngineAvaloniaApp/App.axaml.cs

@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEngineAvaloniaApp;
+
+public partial class App : Application
+{
+    public override void Initialize()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+
+    public override void OnFrameworkInitializationCompleted()
+    {
+        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            desktop.MainWindow = new MainWindow();
+        }
+
+        base.OnFrameworkInitializationCompleted();
+    }
+}

+ 9 - 0
src/PixiEngineAvaloniaApp/MainWindow.axaml

@@ -0,0 +1,9 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="PixiEngineAvaloniaApp.MainWindow"
+        Title="PixiEngineAvaloniaApp">
+    Welcome to Avalonia!
+</Window>

+ 11 - 0
src/PixiEngineAvaloniaApp/MainWindow.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace PixiEngineAvaloniaApp;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}

+ 23 - 0
src/PixiEngineAvaloniaApp/PixiEngineAvaloniaApp.csproj

@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <OutputType>WinExe</OutputType>
+        <TargetFramework>net8.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+        <ApplicationManifest>app.manifest</ApplicationManifest>
+        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Avalonia" Version="$(AvaloniaVersion)"/>
+        <PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)"/>
+        <PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)"/>
+        <PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)"/>
+        <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
+        <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10"/>
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Engine.AvaloniaPlatform\PixiEditor.Engine.AvaloniaPlatform.csproj" />
+    </ItemGroup>
+</Project>

+ 20 - 0
src/PixiEngineAvaloniaApp/Program.cs

@@ -0,0 +1,20 @@
+using Avalonia;
+using System;
+using PixiEditor.Engine.AvaloniaPlatform;
+
+namespace PixiEngineAvaloniaApp;
+
+class Program
+{
+    // Initialization code. Don't use any Avalonia, third-party APIs or any
+    // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+    // yet and stuff might break.
+    [STAThread]
+    public static void Main(string[] args) => BuildAvaloniaApp()
+        .StartWithClassicDesktopLifetime(args);
+
+    // Avalonia configuration, don't remove; also used by visual designer.
+    public static AppBuilder BuildAvaloniaApp()
+        => AppBuilder.Configure<App>()
+            .UsePixiEngine();
+}

+ 18 - 0
src/PixiEngineAvaloniaApp/app.manifest

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <!-- This manifest is used on Windows only.
+       Don't remove it as it might cause problems with window transparency and embedded controls.
+       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+  <assemblyIdentity version="1.0.0.0" name="PixiEngineAvaloniaApp.Desktop"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+</assembly>

+ 4 - 1
src/PixiEngineSampleApp/Program.cs

@@ -18,11 +18,13 @@ public static class Program
 
     public static void Main()
     {
+        SkiaPixiEngine.Create().SetupBackendWindowLess();
+        
         Window mainWindow = new Window("PixiEngineSampleApp", new VecI(1200, 600));
         mainWindow.Render += WindowOnRender;
         mainWindow.Init += Init;
         
-        SkiaPixiEngine.CreateAndRun(mainWindow);
+        mainWindow.Run();
     }
 
     private static void Init()
@@ -39,6 +41,7 @@ public static class Program
         {
             _drawingSurface.Canvas.Clear();
             _drawingSurface.Canvas.DrawText("Seconds: " + _time, 10, 10, _paint);
+            _drawingSurface.Flush();
             _time++;
             _lastTime = 0;
         }