diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index ea2fe0a99c..28b62136da 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -117,6 +117,19 @@ namespace Avalonia.OpenGL public delegate int GlCheckFramebufferStatus(int target); [GlEntryPoint("glCheckFramebufferStatus")] public GlCheckFramebufferStatus CheckFramebufferStatus { get; } + + public delegate void GlBlitFramebuffer(int srcX0, + int srcY0, + int srcX1, + int srcY1, + int dstX0, + int dstY0, + int dstX1, + int dstY1, + int mask, + int filter); + [GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)] + public GlBlitFramebuffer BlitFramebuffer { get; } public delegate void GlGenRenderbuffers(int count, int[] res); [GlEntryPoint("glGenRenderbuffers")] diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a155fd863b..669da8d03c 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -31,6 +31,7 @@ namespace Avalonia.Skia private bool _disposed; private GRContext _grContext; public GRContext GrContext => _grContext; + private ISkiaGpu _gpu; private readonly SKPaint _strokePaint = new SKPaint(); private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); @@ -65,6 +66,11 @@ namespace Avalonia.Skia /// GPU-accelerated context (optional) /// public GRContext GrContext; + + /// + /// Skia GPU provider context (optional) + /// + public ISkiaGpu Gpu; } /// @@ -79,6 +85,7 @@ namespace Avalonia.Skia _disposables = disposables; _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; + _gpu = createInfo.Gpu; if (_grContext != null) Monitor.Enter(_grContext); Surface = createInfo.Surface; @@ -417,7 +424,7 @@ namespace Avalonia.Skia /// public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateRenderTarget(size); + return CreateRenderTarget( size); } /// @@ -925,7 +932,8 @@ namespace Avalonia.Skia Dpi = _dpi, Format = format, DisableTextLcdRendering = !_canTextUseLcdRendering, - GrContext = _grContext + GrContext = _grContext, + Gpu = _gpu }; return new SurfaceRenderTarget(createInfo); diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 1a7a9b75cf..c06b4af52f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Avalonia.OpenGL.Imaging; using SkiaSharp; @@ -15,6 +16,20 @@ namespace Avalonia.Skia /// Surfaces. /// Created render target or if it fails. ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces); + + /// + /// Creates an offscreen render target surface + /// + /// size in pixels + ISkiaSurface TryCreateSurface(PixelSize size); + } + + public interface ISkiaSurface : IDisposable + { + SKSurface Surface { get; } + bool CanBlit { get; } + void Blit(); + } public interface IOpenGlAwareSkiaGpu : ISkiaGpu diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs new file mode 100644 index 0000000000..40347e2cd1 --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/FboSkiaSurface.cs @@ -0,0 +1,129 @@ +using System; +using Avalonia.OpenGL; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; +namespace Avalonia.Skia +{ + public class FboSkiaSurface : ISkiaSurface + { + private readonly GRContext _grContext; + private readonly IGlContext _glContext; + private readonly PixelSize _pixelSize; + private int _fbo; + private int _depthStencil; + private int _texture; + + private static readonly bool[] TrueFalse = new[] { true, false }; + public FboSkiaSurface(GRContext grContext, IGlContext glContext, PixelSize pixelSize) + { + _grContext = grContext; + _glContext = glContext; + _pixelSize = pixelSize; + var InternalFormat = glContext.Version.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8; + var gl = glContext.GlInterface; + + // Save old bindings + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo); + gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderbuffer); + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + + var arr = new int[2]; + + // Generate FBO + gl.GenFramebuffers(1, arr); + _fbo = arr[0]; + gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); + + // Create a texture to render into + gl.GenTextures(1, arr); + _texture = arr[0]; + gl.BindTexture(GL_TEXTURE_2D, _texture); + gl.TexImage2D(GL_TEXTURE_2D, 0, + InternalFormat, pixelSize.Width, pixelSize.Height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + + var success = false; + foreach (var useStencil8 in TrueFalse) + { + gl.GenRenderbuffers(1, arr); + _depthStencil = arr[0]; + gl.BindRenderbuffer(GL_RENDERBUFFER, _depthStencil); + + if (useStencil8) + { + gl.RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, pixelSize.Width, pixelSize.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + } + else + { + gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, pixelSize.Width, pixelSize.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthStencil); + } + + var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status == GL_FRAMEBUFFER_COMPLETE) + { + success = true; + break; + } + else + { + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer); + gl.DeleteRenderbuffers(1, arr); + } + } + + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderbuffer); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo); + + if (!success) + { + arr[0] = _fbo; + gl.DeleteFramebuffers(1, arr); + arr[0] = _texture; + gl.DeleteTextures(1, arr); + throw new OpenGlException("Unable to create FBO with stencil"); + } + + var target = new GRBackendRenderTarget(pixelSize.Width, pixelSize.Height, 0, 0, + new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); + Surface = SKSurface.Create(_grContext, target, + GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); + CanBlit = gl.BlitFramebuffer != null; + } + + public void Dispose() + { + using (_glContext.EnsureCurrent()) + { + Surface?.Dispose(); + Surface = null; + var gl = _glContext.GlInterface; + if (_fbo != 0) + { + gl.DeleteFramebuffers(1, new[] { _fbo }); + gl.DeleteTextures(1, new[] { _texture }); + gl.DeleteRenderbuffers(1, new[] { _depthStencil }); + _fbo = _texture = _depthStencil = 0; + } + } + } + + public SKSurface Surface { get; private set; } + public bool CanBlit { get; } + public void Blit() + { + var gl = _glContext.GlInterface; + gl.GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, out var oldRead); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + gl.BlitFramebuffer(0, 0, _pixelSize.Width, _pixelSize.Height, 0, 0, _pixelSize.Width, _pixelSize.Height, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, oldRead); + } + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 46d42dfdab..f606929b76 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -43,6 +43,11 @@ namespace Avalonia.Skia return null; } + public ISkiaSurface TryCreateSurface(PixelSize size) + { + return new FboSkiaSurface(_grContext, _glContext, size); + } + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index ef5da5eb08..9992c9ba8c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -8,10 +8,12 @@ namespace Avalonia.Skia /// internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo { + private readonly ISkiaGpu _skiaGpu; private readonly ISkiaGpuRenderTarget _renderTarget; - public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget) + public SkiaGpuRenderTarget(ISkiaGpu skiaGpu, ISkiaGpuRenderTarget renderTarget) { + _skiaGpu = skiaGpu; _renderTarget = renderTarget; } @@ -30,7 +32,8 @@ namespace Avalonia.Skia Surface = session.SkSurface, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, VisualBrushRenderer = visualBrushRenderer, - DisableTextLcdRendering = true + DisableTextLcdRendering = true, + Gpu = _skiaGpu }; return new DrawingContextImpl(nfo, session); diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b9c1cbc673..d6f76a2c20 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -138,7 +138,7 @@ namespace Avalonia.Skia var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces); if (gpuRenderTarget != null) { - return new SkiaGpuRenderTarget(gpuRenderTarget); + return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget); } foreach (var surface in surfaces) diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 27b29c6e1e..fb765cac7f 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -13,10 +13,31 @@ namespace Avalonia.Skia /// internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl { - private readonly SKSurface _surface; + private readonly ISkiaSurface _surface; private readonly SKCanvas _canvas; private readonly bool _disableLcdRendering; private readonly GRContext _grContext; + private readonly ISkiaGpu _gpu; + + class SkiaSurfaceWrapper : ISkiaSurface + { + public SKSurface Surface { get; private set; } + public SKSurface ReadSurface { get; private set; } + public bool CanBlit => false; + public void Blit() => throw new NotSupportedException(); + + public SkiaSurfaceWrapper(SKSurface surface) + { + Surface = ReadSurface = surface; + } + + public void Dispose() + { + Surface?.Dispose(); + Surface = null; + ReadSurface = null; + } + } /// /// Create new surface render target. @@ -29,9 +50,14 @@ namespace Avalonia.Skia _disableLcdRendering = createInfo.DisableTextLcdRendering; _grContext = createInfo.GrContext; - _surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format); + _gpu = createInfo.Gpu; - _canvas = _surface?.Canvas; + _surface = _gpu?.TryCreateSurface(PixelSize); + if (_surface == null) + _surface = new SkiaSurfaceWrapper(CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, + createInfo.Format)); + + _canvas = _surface?.Surface.Canvas; if (_surface == null || _canvas == null) { @@ -70,11 +96,12 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { - Surface = _surface, + Surface = _surface.Surface, Dpi = Dpi, VisualBrushRenderer = visualBrushRenderer, DisableTextLcdRendering = _disableLcdRendering, - GrContext = _grContext + GrContext = _grContext, + Gpu = _gpu }; return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++)); @@ -111,7 +138,12 @@ namespace Avalonia.Skia { using (var image = SnapshotImage()) { - context.Canvas.DrawImage(image, sourceRect, destRect, paint); + _surface.Surface.Canvas.Flush(); + if (context.Canvas.TotalMatrix.IsIdentity && _surface.CanBlit && destRect.Top == 0 && + destRect.Left == 0) + _surface.Blit(); + else + _surface.Surface.Draw(context.Canvas, destRect.Left, destRect.Top, paint); } } @@ -121,7 +153,7 @@ namespace Avalonia.Skia /// Image snapshot. public SKImage SnapshotImage() { - return _surface.Snapshot(); + return _surface.Surface.Snapshot(); } /// @@ -172,6 +204,8 @@ namespace Avalonia.Skia /// GPU-accelerated context (optional) /// public GRContext GrContext; + + public ISkiaGpu Gpu; } } }