]> O.S.I.I.S - jp/crow.git/commitdiff
skia backend, nearly functional, save commit
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Thu, 25 Nov 2021 16:08:26 +0000 (17:08 +0100)
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Thu, 25 Nov 2021 16:08:26 +0000 (17:08 +0100)
20 files changed:
Backends/CairoBackend/src/CairoBackendBase.cs [new file with mode: 0644]
Backends/CairoBackend/src/DefaultBackend.cs
Backends/CairoBackend/src/EglBackend.cs [new file with mode: 0644]
Backends/CairoBackend/src/GLDevice.cs
Backends/CairoBackend/src/GLSurface.cs
Backends/CairoBackend/src/ImageBackend.cs
Backends/CairoBackend/src/Surface.cs
Backends/SkiaBackend/SkiaBackend.csproj [new file with mode: 0644]
Backends/SkiaBackend/src/Context.cs [new file with mode: 0644]
Backends/SkiaBackend/src/DefaultBackend.cs [new file with mode: 0644]
Backends/SkiaBackend/src/EglBackend.cs [new file with mode: 0644]
Backends/SkiaBackend/src/Region.cs [new file with mode: 0644]
Backends/SkiaBackend/src/Surface.cs [new file with mode: 0644]
Backends/SkiaBackend/src/VulkanBackend.cs [new file with mode: 0644]
Crow.sln
Crow/Crow.csproj
Crow/src/Interface.cs
Drawing2D/src/Colors.cs
Drawing2D/src/ISurface.cs
Samples/HelloWorld/main.cs

diff --git a/Backends/CairoBackend/src/CairoBackendBase.cs b/Backends/CairoBackend/src/CairoBackendBase.cs
new file mode 100644 (file)
index 0000000..535bb31
--- /dev/null
@@ -0,0 +1,139 @@
+using System;
+using System.IO;
+using Drawing2D;
+using Glfw;
+
+namespace Crow.CairoBackend
+{
+       public abstract class CairoBackendBase : IBackend {
+               protected ISurface surf;
+               /// <summary> Global font rendering settings for Cairo </summary>
+               FontOptions FontRenderingOptions;
+               /// <summary> Global font rendering settings for Cairo </summary>
+               Antialias Antialias = Antialias.Subpixel;
+               protected CairoBackendBase ()
+               {
+                       FontRenderingOptions = new FontOptions ();
+                       FontRenderingOptions.Antialias = Antialias.Subpixel;
+                       FontRenderingOptions.HintMetrics = HintMetrics.On;
+                       FontRenderingOptions.HintStyle = HintStyle.Full;
+                       FontRenderingOptions.SubpixelOrder = SubpixelOrder.Default;
+               }
+               ~CairoBackendBase ()
+               {
+                       Dispose (false);
+               }
+               public abstract ISurface CreateSurface(int width, int height);
+               public abstract ISurface CreateSurface(byte[] data, int width, int height);
+               public ISurface MainSurface => surf;
+               public IRegion CreateRegion () => new Region ();
+               public IContext CreateContext (ISurface surf)
+               {
+                       Context gr = new Context (surf);
+                       gr.FontOptions = FontRenderingOptions;
+                       gr.Antialias = Antialias;
+                       return gr;
+               }
+               //IPattern CreatePattern (PatternType patternType);
+               public IGradient CreateGradient (GradientType gradientType, Rectangle bounds)
+               {
+                       switch (gradientType) {
+                       case GradientType.Vertical:
+                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Left, bounds.Bottom);
+                       case GradientType.Horizontal:
+                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Right, bounds.Top);
+                       case GradientType.Oblic:
+                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
+                       case GradientType.Radial:
+                               throw new NotImplementedException ();
+                       }
+                       return null;
+               }
+               public byte[] LoadBitmap (Stream stream, out Size dimensions)
+               {
+                       byte[] image;
+#if STB_SHARP
+                       StbImageSharp.ImageResult stbi = StbImageSharp.ImageResult.FromStream (stream, StbImageSharp.ColorComponents.RedGreenBlueAlpha);
+                       image = new byte[stbi.Data.Length];
+                       //rgba to argb for cairo.
+                       for (int i = 0; i < stbi.Data.Length; i += 4) {
+                               image[i] = stbi.Data[i + 2];
+                               image[i + 1] = stbi.Data[i + 1];
+                               image[i + 2] = stbi.Data[i];
+                               image[i + 3] = stbi.Data[i + 3];
+                       }
+                       dimensions = new Size (stbi.Width, stbi.Height);
+#else
+                       using (StbImage stbi = new StbImage (stream)) {
+                               image = new byte [stbi.Size];
+                               for (int i = 0; i < stbi.Size; i+=4) {
+                                       //rgba to argb for cairo. ???? looks like bgra to me.
+                                       image [i] = Marshal.ReadByte (stbi.Handle, i + 2);
+                                       image [i + 1] = Marshal.ReadByte (stbi.Handle, i + 1);
+                                       image [i + 2] = Marshal.ReadByte (stbi.Handle, i);
+                                       image [i + 3] = Marshal.ReadByte (stbi.Handle, i + 3);
+                               }
+                               dimensions = new Size (stbi.Width, stbi.Height);
+                       }
+#endif
+                       return image;
+               }
+               public ISvgHandle LoadSvg(Stream stream)
+               {
+                       using (BinaryReader sr = new BinaryReader (stream))
+                               return new SvgHandle (sr.ReadBytes ((int)stream.Length));
+               }
+               public ISvgHandle LoadSvg(string svgFragment) =>
+                       new SvgHandle (System.Text.Encoding.Unicode.GetBytes (svgFragment));
+               bool disposeContextOnFlush;
+               protected IRegion clipping;
+               protected void clear(IContext ctx) {
+                       for (int i = 0; i < clipping.NumRectangles; i++)
+                               ctx.Rectangle (clipping.GetRectangle (i));
+
+                       ctx.ClipPreserve ();
+                       ctx.Operator = Operator.Clear;
+                       ctx.Fill ();
+                       ctx.Operator = Operator.Over;
+               }
+               public virtual IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
+               {
+                       this.clipping = clipping;
+                       IContext ctx = existingContext;
+                       if (ctx == null) {
+                               disposeContextOnFlush = true;
+                               ctx = new Context (MainSurface);
+                       } else
+                               disposeContextOnFlush = false;
+                       return ctx;
+               }
+               public virtual void FlushUIFrame(IContext ctx)
+               {
+                       if (disposeContextOnFlush)
+                               ctx.Dispose ();
+                       clipping = null;
+               }
+               public void ResizeMainSurface (int width, int height)
+               {
+                       surf.Resize (width, height);
+               }
+
+               #region IDispose implementation
+               bool isDisposed;
+               public void Dispose ()
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this);
+               }
+               protected virtual void Dispose (bool disposing)
+               {
+                       if (!isDisposed && disposing) {
+                               surf.Dispose ();
+                               FontRenderingOptions.Dispose ();
+                       }
+                       isDisposed = true;
+               }
+               #endregion
+       }
+}
+
index 9ef04b33f9e14c6f6d415fcd4f8aef4235bd9c52..2adb70a28cd5272383d2ef6ac96c4925717e8f71 100644 (file)
@@ -3,7 +3,7 @@ using Crow.CairoBackend;
 
 namespace Crow.Backends
 {
-       public class DefaultBackend : ImageBackend {
+       public class DefaultBackend : EglBackend {
                /// <summary>
                /// Create a new generic backend bound to the application surface
                /// </summary>
@@ -11,6 +11,9 @@ namespace Crow.Backends
                /// <param name="height">backend surface height</param>
                public DefaultBackend (IntPtr nativeWindoPointer, int width, int height)
                : base (nativeWindoPointer, width, height) { }
+               public DefaultBackend (int width, int height) : base (width, height) {
+
+               }
        }
 }
 
diff --git a/Backends/CairoBackend/src/EglBackend.cs b/Backends/CairoBackend/src/EglBackend.cs
new file mode 100644 (file)
index 0000000..51318a3
--- /dev/null
@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using Drawing2D;
+using Glfw;
+
+namespace Crow.CairoBackend
+{
+       public class EglBackend : CairoBackendBase {
+               IntPtr hWin;
+               EGLDevice device;
+               /// <summary>
+               /// Create a new generic backend bound to the application surface
+               /// </summary>
+               /// <param name="width">backend surface width</param>
+               /// <param name="height">backend surface height</param>
+               public EglBackend (IntPtr nativeWindoPointer, int width, int height) : base () {
+                       hWin = nativeWindoPointer;
+                       Glfw3.MakeContextCurrent (hWin);
+                       Glfw3.SwapInterval (0);
+
+                       device = new EGLDevice (Glfw3.GetEGLDisplay (), Glfw3.GetEGLContext (hWin));
+                       surf = new GLSurface (device, Glfw3.GetEGLSurface (hWin), width, height);
+               }
+               /// <summary>
+               /// Create a new offscreen backend, used in perfTests
+               /// </summary>
+               /// <param name="width">backend surface width</param>
+               /// <param name="height">backend surface height</param>
+               public EglBackend (int width, int height) : base () {
+                       device = new EGLDevice (Glfw3.GetEGLDisplay (), IntPtr.Zero);
+                       surf = new GLTextureSurface (device, width, height);
+               }
+               public override ISurface CreateSurface(int width, int height)
+                       //=> new GLTextureSurface (device, width, height);
+                       => new ImageSurface (Format.ARGB32, width, height);
+               public override ISurface CreateSurface(byte[] data, int width, int height)
+                       => new ImageSurface (data, Format.ARGB32, width, height, 4 * width);
+               public override IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
+               {
+                       Glfw3.MakeContextCurrent (hWin);
+
+                       IContext ctx = base.PrepareUIFrame (existingContext, clipping);
+
+                       clear (ctx);
+
+                       return ctx;
+               }
+               public override void FlushUIFrame(IContext ctx)
+               {
+                       base.FlushUIFrame (ctx);
+
+                       (surf as GLSurface).SwapBuffers ();
+               }
+       }
+}
+
index d90fe1a0acf11589efa1e5d95194608fe419edb3..3c90592f64fb33dffc509503ed041f0bcf7e4184 100644 (file)
@@ -3,8 +3,6 @@
 // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
 using System;
 using Drawing2D;
-using OpenGL;
-using static OpenGL.Gl;
 
 namespace Crow.CairoBackend
 {
@@ -14,15 +12,6 @@ namespace Crow.CairoBackend
                public void SetThreadAware (bool value) {
                        NativeMethods.cairo_gl_device_set_thread_aware (handle, value ? 1 : 0);
                }
-               public virtual ISurface CreateSurface(int width, int height) {
-                       uint tex = GenTexture ();
-                       BindTexture (TextureTarget.Texture2d, tex);
-                       TexImage2D (TextureTarget.Texture2d, 0, InternalFormat.Rgb, width, height,
-                               0, PixelFormat.Rgb, PixelType.UnsignedByte, 0);
-                       TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, NEAREST);
-                       TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, NEAREST);
-                       return new GLSurface (this, Content.ColorAlpha, tex, width, height);
-               }
 
        }
 }
index 1f486c50a2fa910b0a589e169251144d49784c1a..20d5b525899bceac299cb31688c00c8763752f8a 100644 (file)
 //
 
 using System;
+using OpenGL;
+using static OpenGL.Gl;
 
 namespace Crow.CairoBackend {
 
+       public class GLTextureSurface : Surface
+       {
+               uint texId;
+               internal GLTextureSurface (CairoDevice device, int width, int height)
+                       : base ()
+               {
+                       texId = GenTexture ();
+                       BindTexture (TextureTarget.Texture2d, texId);
+                       TexImage2D (TextureTarget.Texture2d, 0, InternalFormat.Rgb, width, height,
+                               0, PixelFormat.Rgb, PixelType.UnsignedByte, 0);
+                       TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, NEAREST);
+                       TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, NEAREST);
+
+                       handle = NativeMethods.cairo_gl_surface_create_for_texture (device.Handle,
+                               (uint)Content.ColorAlpha, texId, width, height);
+               }
+               protected override void Dispose (bool disposing)
+               {
+                       if (disposing && handle != IntPtr.Zero)
+                               OpenGL.Gl.DeleteTextures (texId);
+                       base.Dispose (disposing);
+               }
+
+       }
        public class GLSurface : Surface
        {
                public GLSurface (IntPtr ptr, bool own) : base (ptr, own)
@@ -59,6 +85,8 @@ namespace Crow.CairoBackend {
                }
                public override int Width => NativeMethods.cairo_gl_surface_get_width (handle);
                public override int Height => NativeMethods.cairo_gl_surface_get_height (handle);
+               public override void Resize(int width, int height)
+                       => NativeMethods.cairo_gl_surface_set_size(handle, width, height);
 
                public void SwapBuffers(){
                        NativeMethods.cairo_gl_surface_swapbuffers (this.Handle);
index 7443b787c410b3d394e85be2752e1ad6779526e6..fa9b009e05a94ca0f280173e95d14ac4db8b0cbc 100644 (file)
@@ -5,26 +5,13 @@ using Glfw;
 
 namespace Crow.CairoBackend
 {
-       public class ImageBackend : IBackend {
-               ISurface surf;
-               /// <summary> Global font rendering settings for Cairo </summary>
-               FontOptions FontRenderingOptions;
-               /// <summary> Global font rendering settings for Cairo </summary>
-               Antialias Antialias = Antialias.Subpixel;
-               protected ImageBackend ()
-               {
-                       FontRenderingOptions = new FontOptions ();
-                       FontRenderingOptions.Antialias = Antialias.Subpixel;
-                       FontRenderingOptions.HintMetrics = HintMetrics.On;
-                       FontRenderingOptions.HintStyle = HintStyle.Full;
-                       FontRenderingOptions.SubpixelOrder = SubpixelOrder.Default;
-               }
+       public class ImageBackend : CairoBackendBase {
                /// <summary>
                /// Create a new generic backend bound to the application surface
                /// </summary>
                /// <param name="width">backend surface width</param>
                /// <param name="height">backend surface height</param>
-               public ImageBackend (IntPtr nativeWindoPointer, int width, int height) : this () {
+               public ImageBackend (IntPtr nativeWindoPointer, int width, int height) : base () {
 
                        switch (Environment.OSVersion.Platform) {
                        case PlatformID.Unix:
@@ -50,132 +37,29 @@ namespace Crow.CairoBackend
                /// </summary>
                /// <param name="width">backend surface width</param>
                /// <param name="height">backend surface height</param>
-               public ImageBackend (int width, int height) : this () {
+               public ImageBackend (int width, int height) : base () {
                        surf = new ImageSurface (Format.ARGB32, width, height);
                }
-               ~ImageBackend ()
-               {
-                       Dispose (false);
-               }
-               public virtual ISurface CreateSurface(int width, int height)
+               public override ISurface CreateSurface(int width, int height)
                        => new ImageSurface (Format.ARGB32, width, height);
-               public virtual ISurface CreateSurface(byte[] data, int width, int height)
+               public override ISurface CreateSurface(byte[] data, int width, int height)
                        => new ImageSurface (data, Format.ARGB32, width, height, 4 * width);
-               public ISurface MainSurface => surf;
-               public IRegion CreateRegion () => new Region ();
-               public IContext CreateContext (ISurface surf)
-               {
-                       Context gr = new Context (surf);
-                       gr.FontOptions = FontRenderingOptions;
-                       gr.Antialias = Antialias;
-                       return gr;
-               }
-               //IPattern CreatePattern (PatternType patternType);
-               public IGradient CreateGradient (GradientType gradientType, Rectangle bounds)
-               {
-                       switch (gradientType) {
-                       case GradientType.Vertical:
-                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Left, bounds.Bottom);
-                       case GradientType.Horizontal:
-                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Right, bounds.Top);
-                       case GradientType.Oblic:
-                               return new LinearGradient (bounds.Left, bounds.Top, bounds.Right, bounds.Bottom);
-                       case GradientType.Radial:
-                               throw new NotImplementedException ();
-                       }
-                       return null;
-               }
-               public byte[] LoadBitmap (Stream stream, out Size dimensions)
-               {
-                       byte[] image;
-#if STB_SHARP
-                       StbImageSharp.ImageResult stbi = StbImageSharp.ImageResult.FromStream (stream, StbImageSharp.ColorComponents.RedGreenBlueAlpha);
-                       image = new byte[stbi.Data.Length];
-                       //rgba to argb for cairo.
-                       for (int i = 0; i < stbi.Data.Length; i += 4) {
-                               image[i] = stbi.Data[i + 2];
-                               image[i + 1] = stbi.Data[i + 1];
-                               image[i + 2] = stbi.Data[i];
-                               image[i + 3] = stbi.Data[i + 3];
-                       }
-                       dimensions = new Size (stbi.Width, stbi.Height);
-#else
-                       using (StbImage stbi = new StbImage (stream)) {
-                               image = new byte [stbi.Size];
-                               for (int i = 0; i < stbi.Size; i+=4) {
-                                       //rgba to argb for cairo. ???? looks like bgra to me.
-                                       image [i] = Marshal.ReadByte (stbi.Handle, i + 2);
-                                       image [i + 1] = Marshal.ReadByte (stbi.Handle, i + 1);
-                                       image [i + 2] = Marshal.ReadByte (stbi.Handle, i);
-                                       image [i + 3] = Marshal.ReadByte (stbi.Handle, i + 3);
-                               }
-                               dimensions = new Size (stbi.Width, stbi.Height);
-                       }
-#endif
-                       return image;
-               }
-               public ISvgHandle LoadSvg(Stream stream)
-               {
-                       using (BinaryReader sr = new BinaryReader (stream))
-                               return new SvgHandle (sr.ReadBytes ((int)stream.Length));
-               }
-               public ISvgHandle LoadSvg(string svgFragment) =>
-                       new SvgHandle (System.Text.Encoding.Unicode.GetBytes (svgFragment));
-               bool disposeContextOnFlush;
-               IRegion clipping;
-               protected void clear(IContext ctx) {
-                       for (int i = 0; i < clipping.NumRectangles; i++)
-                               ctx.Rectangle (clipping.GetRectangle (i));
-
-                       ctx.ClipPreserve ();
-                       ctx.Operator = Operator.Clear;
-                       ctx.Fill ();
-                       ctx.Operator = Operator.Over;
-               }
-               public IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
+               public override IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
                {
-                       this.clipping = clipping;
-                       IContext ctx = existingContext;
-                       if (ctx == null) {
-                               disposeContextOnFlush = true;
-                               ctx = new Context (MainSurface);
-                       } else
-                               disposeContextOnFlush = false;
+                       IContext ctx = base.PrepareUIFrame (existingContext, clipping);
 
                        clear (ctx);
                        ctx.PushGroup ();
 
                        return ctx;
                }
-               public void FlushUIFrame(IContext ctx)
+               public override void FlushUIFrame(IContext ctx)
                {
                        ctx.PopGroupToSource ();
                        ctx.Paint ();
 
-                       if (disposeContextOnFlush)
-                               ctx.Dispose ();
-                       clipping = null;
-               }
-               public void ResizeMainSurface (int width, int height)
-               {
-                       surf.Resize (width, height);
-               }
-               #region IDispose implementation
-               bool isDisposed;
-               public void Dispose ()
-               {
-                       Dispose (true);
-                       GC.SuppressFinalize (this);
-               }
-               protected virtual void Dispose (bool disposing)
-               {
-                       if (!isDisposed && disposing) {
-                               surf.Dispose ();
-                               FontRenderingOptions.Dispose ();
-                       }
-                       isDisposed = true;
+                       base.FlushUIFrame (ctx);
                }
-               #endregion
        }
 }
 
index 8e7976585b6103d191f3ae5d26e4690046c76f10..b09ff5e9e438646a0a15ac8e02997e9a17134042 100644 (file)
@@ -42,7 +42,6 @@ namespace Crow.CairoBackend {
        {
                protected IntPtr handle = IntPtr.Zero;
 
-               [Obsolete]
                protected Surface()
                {
                }
@@ -141,6 +140,7 @@ namespace Crow.CairoBackend {
                        handle = IntPtr.Zero;
                }
                public virtual void Resize (int width, int height) {
+                       
                }
 
                public Status Finish ()
diff --git a/Backends/SkiaBackend/SkiaBackend.csproj b/Backends/SkiaBackend/SkiaBackend.csproj
new file mode 100644 (file)
index 0000000..f86af04
--- /dev/null
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+         <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
+  </PropertyGroup>
+
+  <ItemGroup>
+         <Compile Include="src\**\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.3" />
+    <PackageReference Include="vke" Version="0.2.4-beta" />
+    <!--<PackageReference Include="SkiaSharp" Version="2.80.3" />-->
+    <ProjectReference Include="..\..\Drawing2D\Drawing2D.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Backends/SkiaBackend/src/Context.cs b/Backends/SkiaBackend/src/Context.cs
new file mode 100644 (file)
index 0000000..a4e34c3
--- /dev/null
@@ -0,0 +1,432 @@
+// Copyright (c) 2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using System.Text;
+using System.Linq;
+using Drawing2D;
+using SkiaSharp;
+
+namespace Crow.SkiaBackend
+{
+       public class Context : IContext
+       {
+               VkSurface surf;
+               SKCanvas canvas;
+               SKPaint paint;
+               SKPath path;
+               FillRule fillRule = FillRule.NonZero;
+               SKFont font;
+               static Operator[] operatorLut = new Operator[] {
+                       Operator.Clear,
+                       Operator.Source,
+                       Operator.Dest,
+                       Operator.Over,
+                       Operator.DestOver,
+                       Operator.In,
+                       Operator.DestIn,
+                       Operator.Out,
+                       Operator.DestOut,
+                       Operator.Atop,
+                       Operator.DestAtop,
+                       Operator.Xor,
+                       Operator.Add,
+                       0, //SKBlendMode.Modulate,
+                       Operator.Screen,
+                       Operator.Overlay,
+                       Operator.Darken,
+                       Operator.Lighten,
+                       0, //SKBlendMode.ColorDodge,
+                       0, //SKBlendMode.ColorBurn,
+                       0, //SKBlendMode.HardLight,
+                       0, //SKBlendMode.SoftLight,
+                       0, //SKBlendMode.Difference,
+                       0, //SKBlendMode.Exclusion,
+                       Operator.Multiply,
+                       0, //SKBlendMode.Hue,
+                       Operator.Saturate,//mod
+                       0, //SKBlendMode.Color,
+                       0, //SKBlendMode.Luminosity
+               };
+               static SKBlendMode[] skBlendModeLut = new SKBlendMode[] {
+                       SKBlendMode.Clear,
+                       SKBlendMode.Src,
+                       SKBlendMode.SrcOver,
+                       SKBlendMode.SrcIn,
+                       SKBlendMode.SrcOut,
+                       SKBlendMode.SrcATop,
+                       SKBlendMode.Dst,
+                       SKBlendMode.DstOver,
+                       SKBlendMode.DstIn,
+                       SKBlendMode.DstOut,
+                       SKBlendMode.DstATop,
+                       SKBlendMode.Xor,
+                       SKBlendMode.Plus,
+                       SKBlendMode.Saturation,
+                       SKBlendMode.Multiply,
+                       SKBlendMode.Screen,
+                       SKBlendMode.Overlay,
+                       SKBlendMode.Darken,
+                       SKBlendMode.Lighten
+               };
+               internal Context (VkSurface surf)
+               {
+                       canvas = surf.Canvas;
+                       paint = new SKPaint ();
+               }
+               ~Context()
+               {
+                       Dispose(false);
+               }
+
+               public IntPtr Handle => throw new NotImplementedException();
+
+               public double LineWidth {
+                       get => (float)paint.StrokeWidth;
+                       set => paint.StrokeWidth = (float)value;
+               }
+               public LineJoin LineJoin {
+                       get => (LineJoin)paint.StrokeJoin;
+                       set => paint.StrokeJoin = (SKStrokeJoin)value;
+               }
+               public LineCap LineCap {
+                       get => (LineCap)paint.StrokeCap;
+                       set => paint.StrokeCap = (SKStrokeCap)value;
+               }
+               public Operator Operator {
+                       get => operatorLut [(int)paint.BlendMode];
+                       set => paint.BlendMode = skBlendModeLut[(int)value];
+               }
+               public FillRule FillRule {
+                       get => fillRule;
+                       set => fillRule = value;
+               }
+
+               public FontExtents FontExtents {
+                       get {
+                               if (font == null)
+                                       createDefaultFont ();
+                               paint.GetFontMetrics (out SKFontMetrics m);
+                               return new FontExtents (m.Ascent, m.Descent, m.CapHeight, m.XMax, m.XHeight);
+                       }
+               }
+               public Antialias Antialias {
+                       get => paint.IsAntialias ? Antialias.Grey : Antialias.None;
+                       set => paint.IsAntialias = (value == Antialias.Grey);
+               }
+
+               void checkPath () {
+                       if (path == null)
+                               path = new SKPath ();
+               }
+               public void Arc(double xc, double yc, double radius, double a1, double a2)
+               {
+                       checkPath ();
+
+                       float r = (float)radius;
+                       float x = (float)xc;
+                       float y = (float)yc;
+
+                       SKRect rect = new SKRect (x - r, y - r, x + r, y + r);
+                       path.ArcTo (rect, (float)a1, (float)a2 - (float)a1, false);
+               }
+
+               public void Arc(PointD center, double radius, double angle1, double angle2)
+                       => Arc (center.X, center.Y, radius, angle1, angle2);
+               public void ArcNegative(double xc, double yc, double radius, double angle1, double angle2)
+                       => Arc (xc, yc, radius, angle2, angle1);
+
+               public void ArcNegative(PointD center, double radius, double angle1, double angle2)
+                       => Arc (center.X, center.Y, radius, angle2, angle1);
+
+               public void Clear() => canvas.Clear ();
+               public void Clip()
+               {
+                       ClipPreserve ();
+                       path?.Dispose ();
+                       path = null;
+               }
+
+               public void ClipPreserve()
+               {
+                       if (path == null)
+                               return;
+                       canvas.ClipPath (path,
+                               fillRule == FillRule.NonZero ? SKClipOperation.Intersect : SKClipOperation.Difference, paint.IsAntialias);
+               }
+
+               public void ClosePath() => path?.Close ();
+
+               public void CurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
+               {
+                       checkPath ();
+                       path.CubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
+               }
+               public void Fill()
+               {
+                       FillPreserve ();
+                       path?.Dispose ();
+                       path = null;
+               }
+
+               public void FillPreserve()
+               {
+                       if (path == null)
+                               return;
+                       paint.IsStroke = false;
+                       canvas.DrawPath (path, paint);
+               }
+
+               public void Flush() => canvas.Flush ();
+               public void LineTo(double x, double y)
+               {
+                       checkPath ();
+                       path.LineTo ((float)x, (float)y);
+               }
+               public void LineTo(Point p) => LineTo (p.X, p.Y);
+               public void LineTo(PointD p) => LineTo (p.X, p.Y);
+               public void MoveTo(double x, double y)
+               {
+                       checkPath ();
+                       path.MoveTo ((float)x, (float)y);
+               }
+               public void MoveTo(Point p) => MoveTo (p.X, p.Y);
+               public void MoveTo(PointD p) => MoveTo (p.X, p.Y);
+               public void NewPath()
+               {
+                       path?.Dispose ();
+                       path = new SKPath ();
+               }
+
+               public void NewSubPath()
+               {
+                       path = new SKPath (path);
+               }
+
+               public void Paint() => canvas.DrawPaint (paint);
+               public void PaintWithAlpha(double alpha)
+               {
+//                     throw new NotImplementedException();
+                       canvas.DrawPaint (paint);
+               }
+
+               public void PopGroupToSource()
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void PushGroup()
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void Rectangle(double x, double y, double width, double height)
+               {
+                       checkPath ();
+                       SKRect r = new SKRect ((float) x, (float) y, (float) (x + width), (float) (y + height));
+                       path.AddRect (r, SKPathDirection.Clockwise);
+               }
+
+               public void Rectangle(Rectangle r) => Rectangle (r.X, r.Y, r.Width, r.Height);
+
+               public void RelCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
+               {
+                       if (path == null)
+                               return;
+                       double dx = path.LastPoint.X, dy = path.LastPoint.Y;
+                       CurveTo (dx + x1, dx + y1, dx + x2, dx + y2, dx + x3, dx + y3);
+               }
+
+               public void RelLineTo(double x, double y)
+               {
+                       if (path == null)
+                               return;
+                       double dx = path.LastPoint.X, dy = path.LastPoint.Y;
+                       LineTo (dx + x, dx + y);
+               }
+
+               public void RelMoveTo(double x, double y)
+               {
+                       if (path == null)
+                               return;
+                       double dx = path.LastPoint.X, dy = path.LastPoint.Y;
+                       MoveTo (dx + x, dx + y);
+               }
+
+               public void RenderSvg(IntPtr svgNativeHandle, string subId = null)
+               {
+                       //throw new NotImplementedException();
+               }
+
+               public void ResetClip()
+               {
+                       if (!canvas.IsClipEmpty)
+                               canvas.ClipRegion (new SKRegion (new SKRectI (0,0, surf.Width, surf.Height)));
+               }
+
+               public void Restore() => canvas.Restore ();
+               SKMatrix savedMat;
+               public void RestoreTransformations()
+               {
+                       savedMat = canvas.TotalMatrix;
+               }
+
+               public void Rotate(double alpha) => canvas.RotateRadians ((float)alpha);
+               public void Save() => canvas.Save ();
+               public void SaveTransformations() => canvas.SetMatrix (savedMat);
+               public void Scale(double sx, double sy) => canvas.Scale ((float)sx, (float)sy);
+
+               void updatePaintFont () {
+                       paint.Typeface = font.Typeface;
+                       paint.TextSize = font.Size;
+               }
+               void createDefaultFont () {
+                       font = new SKFont (SKTypeface.FromFamilyName ("mono"));
+               }
+               public void SelectFontFace(string family, FontSlant slant, FontWeight weight)
+               {
+                       font?.Dispose ();
+                       font = new SKFont (SKTypeface.FromFamilyName (family,
+                               (weight == FontWeight.Normal) ? SKFontStyleWeight.Normal : SKFontStyleWeight.Bold,
+                               SKFontStyleWidth.Normal, (SKFontStyleSlant)slant));
+                       updatePaintFont ();
+               }
+
+               public void SetDash(double[] dashes, double offset = 0)
+               {
+                       if (dashes == null || dashes.Length == 1) {
+                               paint.PathEffect = null;
+                               return;
+                       }
+                       paint.PathEffect = SKPathEffect.CreateDash (dashes.Cast<float>().ToArray(), (float)offset);
+               }
+
+               public void SetFontSize(double scale)
+               {
+                       if (font == null)
+                               createDefaultFont ();
+                       font.Size = (float)scale;
+                       updatePaintFont ();
+               }
+
+               public void SetSource(IPattern pat)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void SetSource(Color color)
+               {
+                       paint.Color = (UInt32)color;
+               }
+
+               public void SetSource(double r, double g, double b, double a = 1)
+               {
+                       paint.Color = (UInt32)new Color (r, g, b, a);
+               }
+
+               public void SetSource(ISurface surf, double x = 0, double y = 0)
+               {
+                       if (!(surf is Surface s))
+                               return;
+                       canvas.DrawSurface (s.SkSurf, (float)x, (float)y);
+               }
+
+               public void ShowText(string text)
+               {
+                       ShowText (text.AsSpan ());
+               }
+
+               public void ShowText(ReadOnlySpan<char> s, int tabSize = 4)
+               {
+                       int size = s.Length * 4 + 1;
+                       Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+                       int encodedBytes = s.ToUtf8 (bytes, tabSize);
+                       bytes[encodedBytes] = 0;
+                       ShowText (bytes.Slice (0, encodedBytes + 1));
+               }
+
+               public void ShowText(Span<byte> bytes)
+               {
+                       SKPoint origin = (path == null) ? SKPoint.Empty : path.LastPoint;
+                       if (font == null)
+                               createDefaultFont ();
+                       SKTextBlob tb = SKTextBlob.Create (bytes, SKTextEncoding.Utf8, font, origin);
+                       canvas.DrawText (tb, origin.X, origin.Y, paint);
+               }
+
+               public void Stroke()
+               {
+                       StrokePreserve ();
+                       path?.Dispose ();
+                       path = null;
+               }
+
+               public Rectangle StrokeExtents()
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void StrokePreserve()
+               {
+                       if (path == null)
+                               return;
+                       paint.IsStroke = true;
+                       canvas.DrawPath (path, paint);
+               }
+
+               public TextExtents TextExtents(ReadOnlySpan<char> s, int tabSize = 4)
+               {
+                       TextExtents (s, tabSize, out Drawing2D.TextExtents e);
+                       return e;
+               }
+
+               public void TextExtents(ReadOnlySpan<char> s, int tabSize, out TextExtents extents)
+               {
+                       if (s.Length == 0) {
+                               extents = default;
+                               return;
+                       }
+                       int size = s.Length * 4 + 1;
+                       Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+                       int encodedBytes = s.ToUtf8 (bytes, tabSize);
+                       bytes[encodedBytes] = 0;
+                       TextExtents (bytes.Slice (0, encodedBytes + 1), out extents);
+               }
+
+               public void TextExtents(Span<byte> bytes, out TextExtents extents)
+               {
+                       if (font == null)
+                               createDefaultFont ();
+                       paint.GetFontMetrics (out SKFontMetrics metrics);
+                       SKRect bounds = default;
+                       paint.MeasureText (bytes, ref bounds);
+                       extents = new TextExtents (0,0,bounds.Width, bounds.Height, bounds.Width, bounds.Height);
+               }
+
+               public void Translate(double dx, double dy)
+               {
+                       canvas.Translate ((float)dx, (float)dy);
+               }
+
+               public void Translate(PointD p) => Translate (p.X, p.Y);
+
+
+               #region IDisposable implementation
+               public void Dispose()
+               {
+                       Dispose(true);
+                       GC.SuppressFinalize(this);
+               }
+               protected virtual void Dispose(bool disposing)
+               {
+                       if (!disposing)
+                               return;
+                       paint?.Dispose ();
+                       path?.Dispose ();
+                       font?.Dispose ();
+               }
+               #endregion
+       }
+}
+
diff --git a/Backends/SkiaBackend/src/DefaultBackend.cs b/Backends/SkiaBackend/src/DefaultBackend.cs
new file mode 100644 (file)
index 0000000..6d0601c
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (c) 2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Crow.SkiaBackend;
+
+namespace Crow.Backends
+{
+       public class DefaultBackend : VulkanBackend
+       {
+               public DefaultBackend (IntPtr nativeWindoPointer, int width, int height)
+               : base (nativeWindoPointer, width, height) {}
+               public DefaultBackend (int width, int height)
+               : base (width, height) {}
+       }
+}
\ No newline at end of file
diff --git a/Backends/SkiaBackend/src/EglBackend.cs b/Backends/SkiaBackend/src/EglBackend.cs
new file mode 100644 (file)
index 0000000..96be728
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright (c) 2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Drawing2D;
+using SkiaSharp;
+
+namespace Crow.SkiaBackend
+{
+       public class EglBackend : IBackend
+       {
+               [DllImport("libEGL")]
+               extern static IntPtr eglGetProcAddress(string procname);
+
+               int sampleCount = 1;
+               Surface surf;
+               public ISurface MainSurface => surf;
+               public EglBackend (IntPtr nativeWindoPointer, int width, int height) : base () {
+
+               }
+               /// <summary>
+               /// Create a new offscreen backend, used in perfTests
+               /// </summary>
+               /// <param name="width">backend surface width</param>
+               /// <param name="height">backend surface height</param>
+               public EglBackend (int width, int height) : base ()
+               {
+                       /*GRGlGetProcedureAddressDelegate del = new GRGlGetProcedureAddressDelegate (eglGetProcAddress);
+                       GRGlInterface iface = GRGlInterface.CreateGles (del);
+                       GRContext gr = GRContext.CreateGl (iface, default);
+                       SKImageInfo sKImgInfo = new SKImageInfo (width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
+                       GRBackendRenderTarget rtd = GRBackendRenderTarget.
+                       GRBackendRenderTarget target = new GRBackendRenderTarget (width, height, sampleCount, 0, sKImgInfo);
+                       SKSurface.CreateAsRenderTarget (gr,)
+                       surf = new Surface (width, height);*/
+               }
+               public IContext CreateContext(ISurface surf)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public IGradient CreateGradient(GradientType gradientType, Rectangle bounds)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public IRegion CreateRegion()
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISurface CreateSurface(int width, int height)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISurface CreateSurface(byte[] data, int width, int height)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void Dispose()
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void FlushUIFrame(IContext ctx)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public byte[] LoadBitmap(Stream stream, out Size dimensions)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISvgHandle LoadSvg(Stream stream)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISvgHandle LoadSvg(string svgFragment)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public void ResizeMainSurface(int width, int height)
+               {
+                       throw new NotImplementedException();
+               }
+       }
+}
\ No newline at end of file
diff --git a/Backends/SkiaBackend/src/Region.cs b/Backends/SkiaBackend/src/Region.cs
new file mode 100644 (file)
index 0000000..22df9d9
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright (c) 2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System.Collections.Generic;
+using System;
+using Drawing2D;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Crow.SkiaBackend {
+       public class Region : IRegion
+       {
+               Rectangle _bounds;
+               bool boundsUpToDate = true;
+               public List<Rectangle> list = new List<Rectangle>();
+               public int count => list.Count;
+
+               public void AddRectangle(Rectangle r)
+               {
+                       if (r == default)
+                               return;
+                       if (DoesNotContains (r)) {
+                               list.Add (r);
+                               boundsUpToDate = false;
+                       }
+               }
+               public bool DoesNotContains(Rectangle r)
+               {
+                       foreach (Rectangle rInList in list)
+                               if (rInList.ContainsOrIsEqual(r))
+                                       return false;
+                       return true;
+               }
+               public bool intersect(Rectangle r)
+               {
+                       foreach (Rectangle rInList in list)
+                               if (rInList.Intersect(r))
+                                       return true;
+                       return false;
+               }
+               public void stroke(IContext ctx, Color c)
+               {
+                       foreach (Rectangle r in list)
+                               ctx.Rectangle(r);
+
+                       ctx.SetSource(c);
+
+                       ctx.LineWidth = 2;
+                       ctx.Stroke ();
+               }
+               public void clearAndClip(IContext ctx)
+               {
+                       if (list.Count == 0)
+                               return;
+                       foreach (Rectangle r in list)
+                               ctx.Rectangle(r);
+
+                       ctx.ClipPreserve();
+                       ctx.Operator = Operator.Clear;
+                       ctx.Fill();
+                       ctx.Operator = Operator.Over;
+               }
+
+               public void clip(IContext ctx)
+               {
+                       foreach (Rectangle r in list)
+                               ctx.Rectangle(r);
+
+                       ctx.Clip();
+               }
+               public Rectangle Bounds {
+                       get {
+                               if (!boundsUpToDate) {
+                                       if (list.Count > 0) {
+                                               _bounds = list [0];
+                                               for (int i = 1; i < list.Count; i++) {
+                                                       _bounds += list [i];
+                                               }
+                                       } else
+                                               _bounds = default;
+                                       boundsUpToDate = true;
+                               }
+                               return _bounds;
+                       }
+               }
+               public void clear(IContext ctx)
+               {
+                       foreach (Rectangle r in list)
+                               ctx.Rectangle(r);
+                       ctx.Operator = Operator.Clear;
+                       ctx.Fill();
+                       ctx.Operator = Operator.Over;
+               }
+
+               #region  IRegion implemenatation
+               public bool IsEmpty => list.Count == 0;
+               public int NumRectangles => list.Count;
+               public Rectangle GetRectangle(int i) => list[i];
+               public void UnionRectangle (Rectangle r) {
+                       /*if (r == default)
+                               System.Diagnostics.Debugger.Break ();*/
+                       AddRectangle (r);
+               }
+               public bool OverlapOut (Rectangle r) {
+                       foreach (Rectangle rInList in list)
+                               if (rInList.Intersect(r))
+                                       return false;
+                       return true;
+               }
+               public RegionOverlap Contains(Rectangle rectangle)
+               {
+                       throw new NotImplementedException();
+               }
+               public void Reset()
+               {
+                       list = new List<Rectangle>();
+                       _bounds = default;
+                       boundsUpToDate = true;
+               }
+
+               public bool Equals([AllowNull] IRegion other)
+                       => other is Region r ? Bounds.Equals (r.Bounds) : false;
+               #endregion
+
+               public override string ToString ()
+               {
+                       string tmp = "";
+                       foreach (Rectangle r in list) {
+                               tmp += r.ToString ();
+                       }
+                       return tmp;
+               }
+
+               public void Dispose()
+               {
+
+               }
+
+       }
+}
diff --git a/Backends/SkiaBackend/src/Surface.cs b/Backends/SkiaBackend/src/Surface.cs
new file mode 100644 (file)
index 0000000..31d84f2
--- /dev/null
@@ -0,0 +1,139 @@
+// Copyright (c) 2018-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Drawing2D;
+using SkiaSharp;
+using vke;
+using Vulkan;
+
+namespace Crow.SkiaBackend
+{
+       public class VkSurface : ISurface {
+               GRBackendRenderTarget rt;
+               SKSurface skSurf;
+               GRVkImageInfo imgInfo;
+               vke.Image img;
+
+               internal VkSurface (Device dev, Queue queue, GRContext gr, int width, int height, VkSampleCountFlags samples)
+               {
+                       img = new vke.Image (dev, VkFormat.R8g8b8a8Unorm,
+                               VkImageUsageFlags.TransferSrc | VkImageUsageFlags.ColorAttachment, VkMemoryPropertyFlags.DeviceLocal,
+                               (uint)width, (uint)height);
+
+                       imgInfo = new GRVkImageInfo {
+                               CurrentQueueFamily = queue.qFamIndex,
+                               Format = (uint)img.Format,
+                               Image = img.Handle.Handle,
+                               ImageLayout = (uint)VkImageLayout.ColorAttachmentOptimal,
+                               ImageTiling = (uint)VkImageTiling.Optimal,
+                               LevelCount = img.CreateInfo.mipLevels,
+                               Protected = false,
+                               Alloc = new GRVkAlloc()
+                               {
+                                       Memory = img.Memory.Handle,
+                                       Flags = 0,
+                                       Offset = 0,
+                                       Size = img.AllocatedDeviceMemorySize
+                               }
+                       };
+                       rt = new GRBackendRenderTarget (width, height, (int)samples, imgInfo);
+                       skSurf = SKSurface.Create (gr, rt, SKColorType.Rgba8888);
+               }
+               ~VkSurface ()
+               {
+                       Dispose (false);
+               }
+
+               public IntPtr Handle => throw new NotImplementedException();
+               public int Width => (int)img.Width;
+               public int Height => (int)img.Height;
+               internal SKCanvas Canvas => skSurf.Canvas;
+               internal SKSurface SkSurf => skSurf;
+               internal vke.Image Img => img;
+
+
+               public void Flush() => skSurf.Flush ();
+
+               public void Resize(int width, int height)
+               {
+                       //throw new NotImplementedException();
+               }
+               #region IDisposable implementation
+               public void Dispose ()
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this);
+               }
+
+               protected virtual void Dispose (bool disposing)
+               {
+                       if (!disposing || skSurf == null)
+                               return;
+                       skSurf.Dispose ();
+                       rt.Dispose ();
+                       img.Dispose ();
+               }
+               #endregion
+
+       }
+       public class Surface: ISurface
+       {
+               protected SKSurface skSurf;
+               SKImageInfo sKImgInfo;
+               public IntPtr Handle => throw new NotImplementedException();
+               public int Width => sKImgInfo.Width;
+               public int Height => sKImgInfo.Height;
+               internal SKCanvas Canvas => skSurf.Canvas;
+               internal SKSurface SkSurf => skSurf;
+
+               #region CTOR
+               public Surface (int width, int height)
+               {
+                       sKImgInfo = new SKImageInfo (width, height);
+                       skSurf = SKSurface.Create (sKImgInfo);
+               }
+               Surface (IntPtr devHandle, int width, int height)
+               {
+
+               }
+               #endregion
+               ~Surface ()
+               {
+                       Dispose (false);
+               }
+
+               public void Resize(int width, int height)
+               {
+
+               }
+               public void Flush ()
+               {
+                       skSurf.Canvas.Flush ();
+               }
+
+               /*public void WriteToPng (string path) {
+               }
+               public void WriteTo (IntPtr bitmap) {
+               }
+               public void Clear () {
+               }*/
+
+               #region IDisposable implementation
+               public void Dispose ()
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this);
+               }
+
+               protected virtual void Dispose (bool disposing)
+               {
+                       if (!disposing || skSurf == null)
+                               return;
+                       skSurf.Dispose ();
+               }
+               #endregion
+       }
+}
+
diff --git a/Backends/SkiaBackend/src/VulkanBackend.cs b/Backends/SkiaBackend/src/VulkanBackend.cs
new file mode 100644 (file)
index 0000000..40882bf
--- /dev/null
@@ -0,0 +1,293 @@
+// Copyright (c) 2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Crow.SkiaBackend;
+using Drawing2D;
+using Glfw;
+using SkiaSharp;
+
+using vke;
+using Vulkan;
+using static Vulkan.Vk;
+
+namespace Crow.SkiaBackend
+{
+       public class VulkanBackend : IBackend
+       {
+               protected Instance instance;
+               protected PhysicalDevice phy;
+               protected vke.Device dev;
+               protected Queue graphicQueue;   //for vkvg, we must have at least a graphic queue
+               protected IntPtr hWin;                  //GLFW window native pointer.
+               protected VkSurfaceKHR hSurf;   //Vulkan Surface
+               protected SwapChain swapChain;
+               protected CommandPool cmdPool;
+               protected PrimaryCommandBuffer[] cmds;
+               protected VkSemaphore[] drawComplete;
+               protected Fence drawFence;
+               VkSampleCountFlags samples = VkSampleCountFlags.SampleCount1;
+               bool vsync = false;
+
+               GRContext gr;
+               VkSurface surf;
+               public ISurface MainSurface => surf;
+
+               bool tryGetPhy (vke.PhysicalDeviceCollection physicalDevices, VkPhysicalDeviceType phyType, out PhysicalDevice phy, bool swapchainSupport) {
+                       phy = physicalDevices.FirstOrDefault (p => p.Properties.deviceType == phyType && p.HasSwapChainSupport == swapchainSupport);
+                       return phy != null;
+               }
+
+               IntPtr getVkProcAddress(string name, IntPtr instance, IntPtr device) {
+                       using (FixedUtf8String n = new FixedUtf8String (name))
+                       {
+                               return device == IntPtr.Zero ?
+                                       Vk.vkGetInstanceProcAddr (instance, n) :
+                                       Vk.vkGetDeviceProcAddr (device, n);
+                       }
+               }
+
+               /// <summary>
+               /// Create a new offscreen backend, used in perfTests
+               /// </summary>
+               /// <param name="width">backend surface width</param>
+               /// <param name="height">backend surface height</param>
+               public VulkanBackend (int width, int height) : base () {
+#if DEBUG
+                       instance = new Instance (Ext.I.VK_EXT_debug_utils);
+#else
+                       instance = new Instance ();
+#endif
+                       vke.PhysicalDeviceCollection phys = instance.GetAvailablePhysicalDevice ();
+                       if (phys.Count() == 0)
+                               throw new Exception ("[Crow.VkvgBackend] No vulkan hardware found.");
+
+                       if (!tryGetPhy (phys, VkPhysicalDeviceType.DiscreteGpu, out phy, false))
+                               if (!tryGetPhy (phys, VkPhysicalDeviceType.IntegratedGpu, out phy, false))
+                                       phy = phys[0];
+
+                       VkPhysicalDeviceFeatures enabledFeatures = default;
+                       dev = new vke.Device (phy);
+                       graphicQueue = new Queue (dev, VkQueueFlags.Graphics);
+                       dev.Activate (enabledFeatures);
+
+               }
+               public VulkanBackend (IntPtr nativeWindoPointer, int width, int height) : base ()
+               {
+                       hWin = nativeWindoPointer;
+
+                       SwapChain.IMAGES_USAGE = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferDst;
+                       SwapChain.PREFERED_FORMAT = VkFormat.B8g8r8a8Unorm;
+
+                       List<string> instExts = new List<string> (Glfw3.GetRequiredInstanceExtensions ());
+#if DEBUG
+                       instExts.Add (Ext.I.VK_EXT_debug_utils);
+#endif
+                       instance = new Instance (instExts.ToArray());
+                       hSurf = instance.CreateSurface (hWin);
+
+                       vke.PhysicalDeviceCollection phys = instance.GetAvailablePhysicalDevice ();
+                       if (phys.Count() == 0)
+                               throw new Exception ("[Crow.VkvgBackend] No vulkan hardware found.");
+
+                       if (!tryGetPhy (phys, VkPhysicalDeviceType.DiscreteGpu, out phy, true))
+                               if (!tryGetPhy (phys, VkPhysicalDeviceType.IntegratedGpu, out phy, true))
+                                       phy = phys[0];
+
+                       VkPhysicalDeviceFeatures enabledFeatures = default;
+                       dev = new vke.Device (phy);
+                       graphicQueue = new PresentQueue (dev, VkQueueFlags.Graphics, hSurf);
+
+                       dev.Activate (enabledFeatures, Ext.D.VK_KHR_swapchain);
+
+                       swapChain = new SwapChain (graphicQueue as PresentQueue, (uint)width, (uint)height, SwapChain.PREFERED_FORMAT,
+                               vsync ? VkPresentModeKHR.FifoKHR : VkPresentModeKHR.ImmediateKHR);
+                       swapChain.Activate ();
+
+                       cmdPool = new CommandPool (dev, graphicQueue.qFamIndex, VkCommandPoolCreateFlags.ResetCommandBuffer);
+                       cmds = cmdPool.AllocateCommandBuffer (swapChain.ImageCount);
+
+                       drawComplete = new VkSemaphore[swapChain.ImageCount];
+                       drawFence = new Fence (dev, true, "draw fence");
+
+                       for (int i = 0; i < swapChain.ImageCount; i++) {
+                               drawComplete[i] = dev.CreateSemaphore ();
+                               drawComplete[i].SetDebugMarkerName (dev, "Semaphore DrawComplete" + i);
+                       }
+
+                       cmdPool.SetName ("main CmdPool");
+                       GRVkBackendContext grVkCtx = new GRVkBackendContext {
+                               VkInstance = instance.Handle,
+                               VkPhysicalDevice = phy.Handle,
+                               VkQueue = graphicQueue.Handle.Handle,
+                               VkDevice = dev.VkDev.Handle,
+                               GraphicsQueueIndex = 0,
+                               GetProcedureAddress = getVkProcAddress
+                       };
+                       gr = GRContext.CreateVulkan (grVkCtx);
+
+                       createMainSurface ((uint)width, (uint)height);
+               }
+               ~VulkanBackend ()
+               {
+                       Dispose (false);
+               }
+
+               public IContext CreateContext(ISurface surf) => new Context (surf as VkSurface);
+               public IGradient CreateGradient(GradientType gradientType, Rectangle bounds)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public IRegion CreateRegion () => new Crow.SkiaBackend.Region ();
+               public ISurface CreateSurface(int width, int height)
+               {
+                       return new VkSurface (dev, graphicQueue, gr, (int)width, (int)height, samples);
+               }
+
+               public ISurface CreateSurface(byte[] data, int width, int height)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public byte[] LoadBitmap(Stream stream, out Size dimensions)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISvgHandle LoadSvg(Stream stream)
+               {
+                       throw new NotImplementedException();
+               }
+
+               public ISvgHandle LoadSvg(string svgFragment)
+               {
+                       throw new NotImplementedException();
+               }
+
+               bool disposeContextOnFlush;
+               IRegion clipping;
+               protected void clear(IContext ctx) {
+                       for (int i = 0; i < clipping.NumRectangles; i++)
+                               ctx.Rectangle (clipping.GetRectangle (i));
+
+                       ctx.ClipPreserve ();
+                       ctx.Operator = Operator.Clear;
+                       ctx.Fill ();
+                       ctx.Operator = Operator.Over;
+               }
+               public IContext PrepareUIFrame(IContext existingContext, IRegion clipping)
+               {
+                       this.clipping = clipping;
+                       IContext ctx = existingContext;
+                       if (ctx == null) {
+                               disposeContextOnFlush = true;
+                               ctx = new Context (surf);
+                       } else
+                               disposeContextOnFlush = false;
+
+                       clear (ctx);
+
+                       return ctx;
+               }
+               public void FlushUIFrame(IContext ctx)
+               {
+                       if (disposeContextOnFlush)
+                               ctx.Dispose ();
+                       clipping = null;
+
+                       dev.WaitIdle();
+
+                       int idx = swapChain.GetNextImage ();
+                       if (idx < 0) {
+                               createMainSurface (swapChain.Width, swapChain.Height);
+                               return;
+                       }
+
+                       drawFence.Wait ();
+                       drawFence.Reset ();
+
+                       graphicQueue.Submit (cmds[idx], swapChain.presentComplete, drawComplete[idx], drawFence);
+                       (graphicQueue as PresentQueue).Present (swapChain, drawComplete[idx]);
+
+                       dev.WaitIdle();
+               }
+               public void ResizeMainSurface (int width, int height) {
+                       //resize is done on swapchain image aquisition failure
+               }
+               void createMainSurface (uint width, uint height) {
+                       dev.WaitIdle();
+
+                       surf?.Dispose ();
+                       surf = new VkSurface (dev, graphicQueue, gr, (int)width, (int)height, samples);
+
+                       cmdPool.Reset();
+
+                       vke.Image blitSource = surf.Img;
+
+                       for (int i = 0; i < swapChain.ImageCount; i++) {
+                               vke.Image blitDest = swapChain.images[i];
+                               vke.PrimaryCommandBuffer cmd = cmds[i];
+                               cmd.Start();
+
+                               blitDest.SetLayout (cmd, VkImageAspectFlags.Color,
+                                       VkImageLayout.Undefined, VkImageLayout.TransferDstOptimal,
+                                       VkPipelineStageFlags.BottomOfPipe, VkPipelineStageFlags.Transfer);
+
+                               blitSource.SetLayout (cmd, VkImageAspectFlags.Color,
+                                       VkImageLayout.ColorAttachmentOptimal, VkImageLayout.TransferSrcOptimal,
+                                       VkPipelineStageFlags.ColorAttachmentOutput, VkPipelineStageFlags.Transfer);
+
+                               blitSource.BlitTo (cmd, blitDest, VkFilter.Nearest);
+
+                               blitDest.SetLayout (cmd, VkImageAspectFlags.Color,
+                                       VkImageLayout.TransferDstOptimal, VkImageLayout.PresentSrcKHR,
+                                       VkPipelineStageFlags.Transfer, VkPipelineStageFlags.BottomOfPipe);
+
+                               blitSource.SetLayout (cmd, VkImageAspectFlags.Color,
+                                       VkImageLayout.TransferSrcOptimal, VkImageLayout.ColorAttachmentOptimal,
+                                       VkPipelineStageFlags.Transfer, VkPipelineStageFlags.ColorAttachmentOutput);
+
+                               cmd.End ();
+                       }
+                       dev.WaitIdle ();
+               }
+               #region IDisposable implementation
+               bool isDisposed;
+               public void Dispose ()
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this);
+               }
+
+               protected virtual void Dispose (bool disposing)
+               {
+                       if (!isDisposed && disposing) {
+                               dev.WaitIdle ();
+
+                               surf.Dispose ();
+                               gr.Dispose ();
+
+                               for (int i = 0; i < swapChain.ImageCount; i++) {
+                                       dev.DestroySemaphore (drawComplete[i]);
+                                       cmds[i].Free ();
+                               }
+                               drawFence.Dispose ();
+                               swapChain.Dispose ();
+                               cmdPool.Dispose ();
+
+                               vkDestroySurfaceKHR (instance.Handle, hSurf, IntPtr.Zero);
+
+                               dev.Dispose ();
+                               instance.Dispose ();
+                       }
+                       isDisposed = true;
+               }
+               #endregion
+       }
+}
\ No newline at end of file
index 470b92b3a119549e079ef7142e76ca9e31270dde..dc5009cfac44edc875f54737ff6ea2fe287303ba 100644 (file)
--- a/Crow.sln
+++ b/Crow.sln
@@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CairoBackend", "Backends\Ca
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VkvgBackend", "Backends\VkvgBackend\VkvgBackend.csproj", "{34976828-80CF-4AC5-8C81-F66F635DC5FC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaBackend", "Backends\SkiaBackend\SkiaBackend.csproj", "{ADAABC24-0152-41E2-BBEC-3AC9ACEE1175}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -59,6 +61,10 @@ Global
                {34976828-80CF-4AC5-8C81-F66F635DC5FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {34976828-80CF-4AC5-8C81-F66F635DC5FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {34976828-80CF-4AC5-8C81-F66F635DC5FC}.Release|Any CPU.Build.0 = Release|Any CPU
+               {ADAABC24-0152-41E2-BBEC-3AC9ACEE1175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {ADAABC24-0152-41E2-BBEC-3AC9ACEE1175}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {ADAABC24-0152-41E2-BBEC-3AC9ACEE1175}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {ADAABC24-0152-41E2-BBEC-3AC9ACEE1175}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -69,6 +75,7 @@ Global
                {18EBB41F-815E-4BF5-B80F-C9E2FAB2993A} = {B2C7855A-2878-47FD-AD32-9A83DB4AB8C6}
                {E06441A9-0CFD-45BB-9478-99D28CEB327F} = {451F5727-2A2E-4361-A41B-089429ADE8F9}
                {34976828-80CF-4AC5-8C81-F66F635DC5FC} = {451F5727-2A2E-4361-A41B-089429ADE8F9}
+               {ADAABC24-0152-41E2-BBEC-3AC9ACEE1175} = {451F5727-2A2E-4361-A41B-089429ADE8F9}
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {00D4E149-7131-49F4-BAAD-559AA961A78E}
index 97866d71277f73b33269347c9f11d45794b43a50..f99c239d688f8b8db5d72fa9ffb978e5c3dc33d0 100644 (file)
@@ -74,8 +74,9 @@
 
        <ItemGroup>
          <ProjectReference Include="..\Drawing2D\Drawing2D.csproj" />
+         <ProjectReference Include="..\Backends\SkiaBackend\SkiaBackend.csproj" />
          <!--<ProjectReference Include="..\Backends\CairoBackend\CairoBackend.csproj" />-->
-         <ProjectReference Include="..\Backends\VkvgBackend\VkvgBackend.csproj" />
+         <!--<ProjectReference Include="..\Backends\VkvgBackend\VkvgBackend.csproj" />-->
        </ItemGroup>
 
        <PropertyGroup Condition=" '$(CrowStbSharp)' == 'true'">
index a3870a3aa4a4237d7973b2200ea7e81644c4e1cd..8ad67291013a1be5a7c920c5b9a06a098927f077 100644 (file)
@@ -412,11 +412,12 @@ namespace Crow
                {
                        Glfw3.Init ();
 
-                       //Glfw3.WindowHint (WindowAttribute.ClientApi, Constants.OpenglEsApi);
+                       
                        Glfw3.WindowHint (WindowAttribute.ClientApi, 0);
-                       //Glfw3.WindowHint (WindowAttribute.ContextVersionMajor, 3);
-                       //Glfw3.WindowHint (WindowAttribute.ContextVersionMajor, 0);
-                       Glfw3.WindowHint (WindowAttribute.ContextCreationApi, Constants.EglContextApi);
+                       /*Glfw3.WindowHint (WindowAttribute.ClientApi, Constants.OpenglEsApi);
+                       Glfw3.WindowHint (WindowAttribute.ContextVersionMajor, 3);
+                       Glfw3.WindowHint (WindowAttribute.ContextVersionMinor, 2);
+                       Glfw3.WindowHint (WindowAttribute.ContextCreationApi, Constants.EglContextApi);*/
 
                        Glfw3.WindowHint (WindowAttribute.Resizable, 1);
                        Glfw3.WindowHint (WindowAttribute.Decorated, 1);
index 2bd58adaec00331387f6d77a15de9873583e78b5..a20770e63381df2b3ebc1cf14a9f3d7b2b6fff7d 100644 (file)
@@ -233,12 +233,12 @@ namespace Drawing2D
                #region Operators
                /*public static implicit operator string(Color c) => c.ToString();
 
-               public static implicit operator UInt32 (Color c) => c.value;*/
+               */
 
                /*public static implicit operator Color(string s)
                {
                }*/
-
+               public static implicit operator UInt32 (Color c) => c.value;
                public static implicit operator Color (Colors c) => new Color ((UInt32)c);
                public static implicit operator Colors (Color c) => (Colors)c.value;
                public static bool operator ==(Color a, Color b) => a.Equals (b);
index e4940872a3a7d1308c6cb0e7ebf967fb197d67cd..7410a1721b181315b01c43ffa540f768a2946d2e 100644 (file)
@@ -14,10 +14,10 @@ namespace Drawing2D
 
                void Flush ();
 
-               void WriteToPng (string path);
+               /*void WriteToPng (string path);
                void WriteTo (IntPtr bitmap);
                void Clear ();
-               ISurface CreateSimilar (int width, int height);
+               ISurface CreateSimilar (int width, int height);*/
                void Resize (int width, int height);
        }
 }
index e84607b9dc99876cc44708409c8ff0f05ab9383f..20fa9a27fc7ed31584f52c14c57dab4b13234995 100644 (file)
@@ -9,7 +9,7 @@ namespace HelloWorld
                Program() : base (800, 600, true, true) {}
                static void Main (string[] args) {
                        using (Program app = new Program ()) {
-                               app.Initialized += (sender, e) => app.LoadIMLFragment (@"<Window Caption='Hello World'/>");
+                               app.Initialized += (sender, e) => app.LoadIMLFragment (@"<Label Text='Hello World'/>");
                                app.Run ();
                        }
                }