From 4cc76903d5556886f39dc35400f7ba5e9f732eb7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Thu, 25 Nov 2021 17:08:26 +0100 Subject: [PATCH] skia backend, nearly functional, save commit --- Backends/CairoBackend/src/CairoBackendBase.cs | 139 ++++++ Backends/CairoBackend/src/DefaultBackend.cs | 5 +- Backends/CairoBackend/src/EglBackend.cs | 56 +++ Backends/CairoBackend/src/GLDevice.cs | 11 - Backends/CairoBackend/src/GLSurface.cs | 28 ++ Backends/CairoBackend/src/ImageBackend.cs | 134 +----- Backends/CairoBackend/src/Surface.cs | 2 +- Backends/SkiaBackend/SkiaBackend.csproj | 19 + Backends/SkiaBackend/src/Context.cs | 432 ++++++++++++++++++ Backends/SkiaBackend/src/DefaultBackend.cs | 17 + Backends/SkiaBackend/src/EglBackend.cs | 100 ++++ Backends/SkiaBackend/src/Region.cs | 140 ++++++ Backends/SkiaBackend/src/Surface.cs | 139 ++++++ Backends/SkiaBackend/src/VulkanBackend.cs | 293 ++++++++++++ Crow.sln | 7 + Crow/Crow.csproj | 3 +- Crow/src/Interface.cs | 9 +- Drawing2D/src/Colors.cs | 4 +- Drawing2D/src/ISurface.cs | 4 +- Samples/HelloWorld/main.cs | 2 +- 20 files changed, 1396 insertions(+), 148 deletions(-) create mode 100644 Backends/CairoBackend/src/CairoBackendBase.cs create mode 100644 Backends/CairoBackend/src/EglBackend.cs create mode 100644 Backends/SkiaBackend/SkiaBackend.csproj create mode 100644 Backends/SkiaBackend/src/Context.cs create mode 100644 Backends/SkiaBackend/src/DefaultBackend.cs create mode 100644 Backends/SkiaBackend/src/EglBackend.cs create mode 100644 Backends/SkiaBackend/src/Region.cs create mode 100644 Backends/SkiaBackend/src/Surface.cs create mode 100644 Backends/SkiaBackend/src/VulkanBackend.cs diff --git a/Backends/CairoBackend/src/CairoBackendBase.cs b/Backends/CairoBackend/src/CairoBackendBase.cs new file mode 100644 index 00000000..535bb31d --- /dev/null +++ b/Backends/CairoBackend/src/CairoBackendBase.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using Drawing2D; +using Glfw; + +namespace Crow.CairoBackend +{ + public abstract class CairoBackendBase : IBackend { + protected ISurface surf; + /// Global font rendering settings for Cairo + FontOptions FontRenderingOptions; + /// Global font rendering settings for Cairo + 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 + } +} + diff --git a/Backends/CairoBackend/src/DefaultBackend.cs b/Backends/CairoBackend/src/DefaultBackend.cs index 9ef04b33..2adb70a2 100644 --- a/Backends/CairoBackend/src/DefaultBackend.cs +++ b/Backends/CairoBackend/src/DefaultBackend.cs @@ -3,7 +3,7 @@ using Crow.CairoBackend; namespace Crow.Backends { - public class DefaultBackend : ImageBackend { + public class DefaultBackend : EglBackend { /// /// Create a new generic backend bound to the application surface /// @@ -11,6 +11,9 @@ namespace Crow.Backends /// backend surface height 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 index 00000000..51318a39 --- /dev/null +++ b/Backends/CairoBackend/src/EglBackend.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using Drawing2D; +using Glfw; + +namespace Crow.CairoBackend +{ + public class EglBackend : CairoBackendBase { + IntPtr hWin; + EGLDevice device; + /// + /// Create a new generic backend bound to the application surface + /// + /// backend surface width + /// backend surface height + 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); + } + /// + /// Create a new offscreen backend, used in perfTests + /// + /// backend surface width + /// backend surface height + 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 (); + } + } +} + diff --git a/Backends/CairoBackend/src/GLDevice.cs b/Backends/CairoBackend/src/GLDevice.cs index d90fe1a0..3c90592f 100644 --- a/Backends/CairoBackend/src/GLDevice.cs +++ b/Backends/CairoBackend/src/GLDevice.cs @@ -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); - } } } diff --git a/Backends/CairoBackend/src/GLSurface.cs b/Backends/CairoBackend/src/GLSurface.cs index 1f486c50..20d5b525 100644 --- a/Backends/CairoBackend/src/GLSurface.cs +++ b/Backends/CairoBackend/src/GLSurface.cs @@ -29,9 +29,35 @@ // 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); diff --git a/Backends/CairoBackend/src/ImageBackend.cs b/Backends/CairoBackend/src/ImageBackend.cs index 7443b787..fa9b009e 100644 --- a/Backends/CairoBackend/src/ImageBackend.cs +++ b/Backends/CairoBackend/src/ImageBackend.cs @@ -5,26 +5,13 @@ using Glfw; namespace Crow.CairoBackend { - public class ImageBackend : IBackend { - ISurface surf; - /// Global font rendering settings for Cairo - FontOptions FontRenderingOptions; - /// Global font rendering settings for Cairo - 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 { /// /// Create a new generic backend bound to the application surface /// /// backend surface width /// backend surface height - 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 /// /// backend surface width /// backend surface height - 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 } } diff --git a/Backends/CairoBackend/src/Surface.cs b/Backends/CairoBackend/src/Surface.cs index 8e797658..b09ff5e9 100644 --- a/Backends/CairoBackend/src/Surface.cs +++ b/Backends/CairoBackend/src/Surface.cs @@ -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 index 00000000..f86af042 --- /dev/null +++ b/Backends/SkiaBackend/SkiaBackend.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.0 + false + + + + + + + + + + + + + + diff --git a/Backends/SkiaBackend/src/Context.cs b/Backends/SkiaBackend/src/Context.cs new file mode 100644 index 00000000..a4e34c33 --- /dev/null +++ b/Backends/SkiaBackend/src/Context.cs @@ -0,0 +1,432 @@ +// Copyright (c) 2021 Jean-Philippe Bruyère +// +// 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().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 s, int tabSize = 4) + { + int size = s.Length * 4 + 1; + Span 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 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 s, int tabSize = 4) + { + TextExtents (s, tabSize, out Drawing2D.TextExtents e); + return e; + } + + public void TextExtents(ReadOnlySpan s, int tabSize, out TextExtents extents) + { + if (s.Length == 0) { + extents = default; + return; + } + int size = s.Length * 4 + 1; + Span 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 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 index 00000000..6d0601ca --- /dev/null +++ b/Backends/SkiaBackend/src/DefaultBackend.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2021 Jean-Philippe Bruyère +// +// 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 index 00000000..96be7280 --- /dev/null +++ b/Backends/SkiaBackend/src/EglBackend.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2021 Jean-Philippe Bruyère +// +// 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 () { + + } + /// + /// Create a new offscreen backend, used in perfTests + /// + /// backend surface width + /// backend surface height + 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 index 00000000..22df9d9f --- /dev/null +++ b/Backends/SkiaBackend/src/Region.cs @@ -0,0 +1,140 @@ +// Copyright (c) 2021 Jean-Philippe Bruyère +// +// 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 list = new List(); + 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(); + _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 index 00000000..31d84f21 --- /dev/null +++ b/Backends/SkiaBackend/src/Surface.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2018-2021 Jean-Philippe Bruyère +// +// 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 index 00000000..40882bfb --- /dev/null +++ b/Backends/SkiaBackend/src/VulkanBackend.cs @@ -0,0 +1,293 @@ +// Copyright (c) 2021 Jean-Philippe Bruyère +// +// 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); + } + } + + /// + /// Create a new offscreen backend, used in perfTests + /// + /// backend surface width + /// backend surface height + 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 instExts = new List (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 diff --git a/Crow.sln b/Crow.sln index 470b92b3..dc5009cf 100644 --- 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} diff --git a/Crow/Crow.csproj b/Crow/Crow.csproj index 97866d71..f99c239d 100644 --- a/Crow/Crow.csproj +++ b/Crow/Crow.csproj @@ -74,8 +74,9 @@ + - + diff --git a/Crow/src/Interface.cs b/Crow/src/Interface.cs index a3870a3a..8ad67291 100644 --- a/Crow/src/Interface.cs +++ b/Crow/src/Interface.cs @@ -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); diff --git a/Drawing2D/src/Colors.cs b/Drawing2D/src/Colors.cs index 2bd58ada..a20770e6 100644 --- a/Drawing2D/src/Colors.cs +++ b/Drawing2D/src/Colors.cs @@ -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); diff --git a/Drawing2D/src/ISurface.cs b/Drawing2D/src/ISurface.cs index e4940872..7410a172 100644 --- a/Drawing2D/src/ISurface.cs +++ b/Drawing2D/src/ISurface.cs @@ -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); } } diff --git a/Samples/HelloWorld/main.cs b/Samples/HelloWorld/main.cs index e84607b9..20fa9a27 100644 --- a/Samples/HelloWorld/main.cs +++ b/Samples/HelloWorld/main.cs @@ -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 (@""); + app.Initialized += (sender, e) => app.LoadIMLFragment (@"