Browse Source

Refactored GPU acceleration, implemented OpelGlTextureBitmap

pull/3386/head
Nikita Tsukanov 6 years ago
parent
commit
60a2e37e18
  1. 50
      src/Avalonia.Base/Utilities/DisposableLock.cs
  2. 29
      src/Avalonia.Native/GlPlatformFeature.cs
  3. 2
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  4. 33
      src/Avalonia.OpenGL/EglContext.cs
  5. 6
      src/Avalonia.OpenGL/EglDisplay.cs
  6. 14
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  7. 12
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  8. 16
      src/Avalonia.OpenGL/EglInterface.cs
  9. 4674
      src/Avalonia.OpenGL/GlConsts.cs
  10. 57
      src/Avalonia.OpenGL/GlInterface.cs
  11. 6
      src/Avalonia.OpenGL/IGlContext.cs
  12. 1
      src/Avalonia.OpenGL/IGlDisplay.cs
  13. 9
      src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs
  14. 3
      src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs
  15. 13
      src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs
  16. 46
      src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs
  17. 3
      src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs
  18. 21
      src/Avalonia.X11/Glx/Glx.cs
  19. 46
      src/Avalonia.X11/Glx/GlxContext.cs
  20. 53
      src/Avalonia.X11/Glx/GlxDisplay.cs
  21. 10
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  22. 5
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  23. 35
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  24. 4
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  25. 15
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  26. 2
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs
  27. 7
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs
  28. 71
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
  29. 43
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  30. 81
      src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs
  31. 12
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  32. 59
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  33. 2
      src/Skia/Avalonia.Skia/SkiaOptions.cs

50
src/Avalonia.Base/Utilities/DisposableLock.cs

@ -0,0 +1,50 @@
using System;
using System.Threading;
namespace Avalonia.Utilities
{
public class DisposableLock
{
private readonly object _lock = new object();
/// <summary>
/// Tries to take a lock
/// </summary>
/// <returns>IDisposable if succeeded to obtain the lock</returns>
public IDisposable TryLock()
{
if (Monitor.TryEnter(_lock))
return new UnlockDisposable(_lock);
return null;
}
/// <summary>
/// Enters a waiting lock
/// </summary>
public IDisposable Lock()
{
Monitor.Enter(_lock);
return new UnlockDisposable(_lock);
}
private sealed class UnlockDisposable : IDisposable
{
private object _lock;
public UnlockDisposable(object @lock)
{
_lock = @lock;
}
public void Dispose()
{
object @lock = Interlocked.Exchange(ref _lock, null);
if (@lock != null)
{
Monitor.Exit(@lock);
}
}
}
}
}

29
src/Avalonia.Native/GlPlatformFeature.cs

@ -2,21 +2,35 @@
using Avalonia.OpenGL;
using Avalonia.Native.Interop;
using System.Drawing;
using System.Reactive.Disposables;
using Avalonia.Threading;
namespace Avalonia.Native
{
class GlPlatformFeature : IWindowingPlatformGlFeature
{
private GlContext _immediateContext;
public GlPlatformFeature(IAvnGlFeature feature)
{
Display = new GlDisplay(feature.ObtainDisplay());
ImmediateContext = new GlContext(Display, feature.ObtainImmediateContext());
_immediateContext = new GlContext(Display, feature.ObtainImmediateContext());
}
public IGlContext ImmediateContext { get; }
public GlDisplay Display { get; }
IGlDisplay IWindowingPlatformGlFeature.Display => Display;
public IGlContext CreateContext()
{
if (_immediateContext != null)
{
var rv = _immediateContext;
_immediateContext = null;
return rv;
}
throw new PlatformNotSupportedException(
"OSX backend haven't switched to the new model yet, so there are no custom contexts, sorry");
}
}
class GlDisplay : IGlDisplay
@ -58,9 +72,16 @@ namespace Avalonia.Native
public IGlDisplay Display { get; }
public void MakeCurrent()
public IDisposable MakeCurrent()
{
Context.MakeCurrent();
// HACK: OSX backend haven't switched to the new model, so there are only 2 pre-created contexts now
return Disposable.Empty;
}
public void Dispose()
{
// HACK: OSX backend haven't switched to the new model, so there are only 2 pre-created contexts now
}
}

2
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@ -2,10 +2,12 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>

33
src/Avalonia.OpenGL/EglContext.cs

@ -1,6 +1,7 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL
{
@ -28,17 +29,45 @@ namespace Avalonia.OpenGL
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent()
class RestoreContext : IDisposable
{
private readonly EglInterface _egl;
private readonly IntPtr _display;
private IntPtr _context, _read, _draw;
public RestoreContext(EglInterface egl)
{
_egl = egl;
_display = _egl.GetCurrentDisplay();
_context = _egl.GetCurrentContext();
_read = _egl.GetCurrentSurface(EGL_READ);
_draw = _egl.GetCurrentSurface(EGL_DRAW);
}
public void Dispose() => _egl.MakeCurrent(_display, _draw, _read, _context);
}
public IDisposable MakeCurrent()
{
var old = new RestoreContext(_egl);
if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
return old;
}
public void MakeCurrent(EglSurface surface)
public IDisposable MakeCurrent(EglSurface surface)
{
var old = new RestoreContext(_egl);
var surf = surface ?? OffscreenSurface;
if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
return old;
}
public void Dispose()
{
_egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose();
}
}
}

6
src/Avalonia.OpenGL/EglDisplay.cs

@ -169,12 +169,6 @@ namespace Avalonia.OpenGL
return rv;
}
public void ClearContext()
{
if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public EglSurface CreateWindowSurface(IntPtr window)
{
var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE});

14
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@ -5,8 +5,12 @@ namespace Avalonia.OpenGL
{
public class EglGlPlatformFeature : IWindowingPlatformGlFeature
{
public IGlDisplay Display { get; set; }
public IGlContext ImmediateContext { get; set; }
private EglDisplay _display;
public IGlDisplay Display => _display;
public IGlContext CreateContext()
{
return _display.CreateContext(DeferredContext);
}
public EglContext DeferredContext { get; set; }
public static void TryInitialize()
@ -21,12 +25,10 @@ namespace Avalonia.OpenGL
try
{
var disp = new EglDisplay();
var ctx = disp.CreateContext(null);
return new EglGlPlatformFeature
{
Display = disp,
ImmediateContext = ctx,
DeferredContext = (EglContext)disp.CreateContext(ctx)
_display = disp,
DeferredContext = disp.CreateContext(null)
};
}
catch(Exception e)

12
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -58,12 +58,12 @@ namespace Avalonia.OpenGL
{
if (IsCorrupted)
throw new RenderTargetCorruptedException();
_context.MakeCurrent(_glSurface);
var restoreContext = _context.MakeCurrent(_glSurface);
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
return new Session(_display, _context, _glSurface, _info, l);
return new Session(_display, _context, _glSurface, _info, l, restoreContext);
}
catch
{
@ -79,17 +79,19 @@ namespace Avalonia.OpenGL
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private IDisposable _lock;
private readonly IDisposable _restoreContext;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
IDisposable @lock, IDisposable restoreContext)
{
_context = context;
_display = display;
_glSurface = glSurface;
_info = info;
_lock = @lock;
_restoreContext = restoreContext;
}
public void Dispose()
@ -100,7 +102,7 @@ namespace Avalonia.OpenGL
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
_context.Display.ClearContext();
_restoreContext.Dispose();
_lock.Dispose();
}

16
src/Avalonia.OpenGL/EglInterface.cs

@ -87,6 +87,10 @@ namespace Avalonia.OpenGL
IntPtr share, int[] attrs);
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
public delegate bool EglDestroyContext(IntPtr display, IntPtr context);
[GlEntryPoint("eglDestroyContext")]
public EglDestroyContext DestroyContext { get; }
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[GlEntryPoint("eglCreatePbufferSurface")]
@ -96,6 +100,18 @@ namespace Avalonia.OpenGL
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
public delegate IntPtr EglGetCurrentContext();
[GlEntryPoint("eglGetCurrentContext")]
public EglGetCurrentContext GetCurrentContext { get; }
public delegate IntPtr EglGetCurrentDisplay();
[GlEntryPoint("eglGetCurrentDisplay")]
public EglGetCurrentContext GetCurrentDisplay { get; }
public delegate IntPtr EglGetCurrentSurface(int readDraw);
[GlEntryPoint("eglGetCurrentSurface")]
public EglGetCurrentSurface GetCurrentSurface { get; }
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }

4674
src/Avalonia.OpenGL/GlConsts.cs

File diff suppressed because it is too large

57
src/Avalonia.OpenGL/GlInterface.cs

@ -6,7 +6,7 @@ namespace Avalonia.OpenGL
{
public delegate IntPtr GlGetProcAddressDelegate(string procName);
public class GlInterface : GlInterfaceBase
public unsafe class GlInterface : GlInterfaceBase
{
public string Version { get; }
public string Vendor { get; }
@ -70,6 +70,61 @@ namespace Avalonia.OpenGL
[GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
public delegate void GlGenFramebuffers(int count, int[] res);
[GlEntryPoint("glGenFramebuffers")]
public GlGenFramebuffers GenFramebuffers { get; }
public delegate void GlBindFramebuffer(int target, int fb);
[GlEntryPoint("glBindFramebuffer")]
public GlBindFramebuffer BindFramebuffer { get; }
public delegate int GlCheckFramebufferStatus(int target);
[GlEntryPoint("glCheckFramebufferStatus")]
public GlCheckFramebufferStatus CheckFramebufferStatus { get; }
public delegate void GlGenRenderbuffers(int count, int[] res);
[GlEntryPoint("glGenRenderbuffers")]
public GlGenRenderbuffers GenRenderbuffers { get; }
public delegate void GlBindRenderbuffer(int target, int fb);
[GlEntryPoint("glBindRenderbuffer")]
public GlBindRenderbuffer BindRenderbuffer { get; }
public delegate void GlRenderbufferStorage(int target, int internalFormat, int width, int height);
[GlEntryPoint("glRenderbufferStorage")]
public GlRenderbufferStorage RenderbufferStorage { get; }
public delegate void GlFramebufferRenderbuffer(int target, int attachment,
int renderbufferTarget, int renderbuffer);
[GlEntryPoint("glFramebufferRenderbuffer")]
public GlFramebufferRenderbuffer FramebufferRenderbuffer { get; }
public delegate void GlGenTextures(int count, int[] res);
[GlEntryPoint("glGenTextures")]
public GlGenTextures GenTextures { get; }
public delegate void GlBindTexture(int target, int fb);
[GlEntryPoint("glBindTexture")]
public GlBindTexture BindTexture { get; }
public delegate void GlTexImage2D(int target, int level, int internalFormat, int width, int height, int border,
int format, int type, IntPtr data);
[GlEntryPoint("glTexImage2D")]
public GlTexImage2D TexImage2D { get; }
public delegate void GlTexParameteri(int target, int name, int value);
[GlEntryPoint("glTexParameteri")]
public GlTexParameteri TexParameteri { get; }
public delegate void GlFramebufferTexture2D(int target, int attachment,
int texTarget, int texture, int level);
[GlEntryPoint("glFramebufferTexture2D")]
public GlFramebufferTexture2D FramebufferTexture2D { get; }
public delegate void GlDrawBuffers(int n, int[] bufs);
[GlEntryPoint("glDrawBuffers")]
public GlDrawBuffers DrawBuffers { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty
}
}

6
src/Avalonia.OpenGL/IGlContext.cs

@ -1,8 +1,10 @@
using System;
namespace Avalonia.OpenGL
{
public interface IGlContext
public interface IGlContext : IDisposable
{
IGlDisplay Display { get; }
void MakeCurrent();
IDisposable MakeCurrent();
}
}

1
src/Avalonia.OpenGL/IGlDisplay.cs

@ -4,7 +4,6 @@ namespace Avalonia.OpenGL
{
GlDisplayType Type { get; }
GlInterface GlInterface { get; }
void ClearContext();
int SampleCount { get; }
int StencilSize { get; }
}

9
src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs

@ -0,0 +1,9 @@
using Avalonia.OpenGL.Imaging;
namespace Avalonia.OpenGL
{
public interface IOpenGlAwarePlatformRenderInterface
{
IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap();
}
}

3
src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs

@ -2,6 +2,7 @@ namespace Avalonia.OpenGL
{
public interface IWindowingPlatformGlFeature
{
IGlContext ImmediateContext { get; }
IGlDisplay Display { get; }
IGlContext CreateContext();
}
}

13
src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs

@ -0,0 +1,13 @@
using System;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace Avalonia.OpenGL.Imaging
{
public interface IOpenGlTextureBitmapImpl : IBitmapImpl
{
IDisposable Lock();
void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling);
void SetDirty();
}
}

46
src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs

@ -0,0 +1,46 @@
using System;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.OpenGL.Imaging
{
public class OpenGlTextureBitmap : Bitmap, IAffectsRender
{
private IOpenGlTextureBitmapImpl _impl;
static IOpenGlTextureBitmapImpl CreateOrThrow()
{
if (!(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is IOpenGlAwarePlatformRenderInterface
glAware))
throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration");
return glAware.CreateOpenGlTextureBitmap();
}
public OpenGlTextureBitmap()
: base(CreateOrThrow())
{
_impl = (IOpenGlTextureBitmapImpl)PlatformImpl.Item;
}
public IDisposable Lock() => _impl.Lock();
public void SetTexture(int textureId, int internalFormat, PixelSize size, double dpiScaling)
{
_impl.SetBackBuffer(textureId, internalFormat, size, dpiScaling);
SetIsDirty();
}
public void SetIsDirty()
{
if (Dispatcher.UIThread.CheckAccess())
CallInvalidated();
else
Dispatcher.UIThread.Post(CallInvalidated);
}
private void CallInvalidated() => Invalidated?.Invoke(this, EventArgs.Empty);
public event EventHandler Invalidated;
}
}

3
src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs

@ -3,10 +3,11 @@
using System;
using System.Threading;
using Avalonia.Utilities;
namespace Avalonia.Rendering
{
public class ManagedDeferredRendererLock : IDeferredRendererLock
public class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock
{
private readonly object _lock = new object();

21
src/Avalonia.X11/Glx/Glx.cs

@ -15,11 +15,30 @@ namespace Avalonia.X11.Glx
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXGetCurrentContext")]
public GlxGetCurrentContext GetCurrentContext { get; }
public delegate IntPtr GlxGetCurrentContext();
[GlEntryPoint("glXGetCurrentDisplay")]
public GlxGetCurrentDisplay GetCurrentDisplay { get; }
public delegate IntPtr GlxGetCurrentDisplay();
[GlEntryPoint("glXGetCurrentDrawable")]
public GlxGetCurrentDrawable GetCurrentDrawable { get; }
public delegate IntPtr GlxGetCurrentDrawable();
[GlEntryPoint("glXGetCurrentReadDrawable")]
public GlxGetCurrentReadDrawable GetCurrentReadDrawable { get; }
public delegate IntPtr GlxGetCurrentReadDrawable();
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPoint("glXDestroyPbuffer")]
public GlxDestroyPbuffer DestroyPbuffer { get; }
public delegate IntPtr GlxDestroyPbuffer(IntPtr dpy, IntPtr fb);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);

46
src/Avalonia.X11/Glx/GlxContext.cs

@ -10,14 +10,17 @@ namespace Avalonia.X11.Glx
public GlxInterface Glx { get; }
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly bool _ownsPBuffer;
private readonly object _lock = new object();
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid,
bool ownsPBuffer)
{
Handle = handle;
Glx = glx;
_x11 = x11;
_defaultXid = defaultXid;
_ownsPBuffer = ownsPBuffer;
Display = display;
}
@ -29,13 +32,48 @@ namespace Avalonia.X11.Glx
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent() => MakeCurrent(_defaultXid);
public void MakeCurrent(IntPtr xid)
class RestoreContext : IDisposable
{
private GlxInterface _glx;
private IntPtr _defaultDisplay;
private IntPtr _display;
private IntPtr _context;
private IntPtr _read;
private IntPtr _draw;
public RestoreContext(GlxInterface glx, IntPtr defaultDisplay)
{
_glx = glx;
_defaultDisplay = defaultDisplay;
_display = _glx.GetCurrentDisplay();
_context = _glx.GetCurrentContext();
_read = _glx.GetCurrentReadDrawable();
_draw = _glx.GetCurrentDrawable();
}
public void Dispose()
{
var disp = _display == IntPtr.Zero ? _defaultDisplay : _display;
_glx.MakeContextCurrent(disp, _draw, _read, _context);
}
}
public IDisposable MakeCurrent() => MakeCurrent(_defaultXid);
public IDisposable MakeCurrent(IntPtr xid)
{
var old = new RestoreContext(Glx, _x11.Display);
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
throw new OpenGlException("glXMakeContextCurrent failed ");
return old;
}
public void Dispose()
{
Glx.DestroyContext(_x11.Display, Handle);
if (_ownsPBuffer)
Glx.DestroyPbuffer(_x11.Display, _defaultXid);
}
}
}

53
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -17,7 +17,6 @@ namespace Avalonia.X11.Glx
public int SampleCount { get; }
public int StencilSize { get; }
public GlxContext ImmediateContext { get; }
public GlxContext DeferredContext { get; }
public GlxInterface Glx { get; } = new GlxInterface();
public GlxDisplay(X11Info x11)
@ -81,35 +80,39 @@ namespace Avalonia.X11.Glx
})).ToList();
XLib.XFlush(_x11.Display);
ImmediateContext = CreateContext(pbuffers[0],null);
DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)
throw new OpenGlException("GL renderer string is null, aborting");
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
DeferredContext = CreateContext(CreatePBuffer(), null, true);
using (DeferredContext.MakeCurrent())
{
var blacklist = AvaloniaLocator.Current.GetService<X11PlatformOptions>()
?.GlxRendererBlacklist;
if (blacklist != null)
foreach(var item in blacklist)
if (GlInterface.Renderer.Contains(item))
throw new OpenGlException($"Renderer '{GlInterface.Renderer}' is blacklisted by '{item}'");
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)
throw new OpenGlException("GL renderer string is null, aborting");
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
{
var blacklist = AvaloniaLocator.Current.GetService<X11PlatformOptions>()
?.GlxRendererBlacklist;
if (blacklist != null)
foreach (var item in blacklist)
if (GlInterface.Renderer.Contains(item))
throw new OpenGlException(
$"Renderer '{GlInterface.Renderer}' is blacklisted by '{item}'");
}
}
}
IntPtr CreatePBuffer()
{
return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 });
}
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
public GlxContext CreateContext() => CreateContext(DeferredContext);
GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, true);
GlxContext CreateContext(IntPtr defaultXid, IGlContext share, bool ownsPBuffer)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
IntPtr handle = default;
@ -141,7 +144,7 @@ namespace Avalonia.X11.Glx
if (handle == IntPtr.Zero)
throw new OpenGlException("Unable to create direct GLX context");
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid, ownsPBuffer);
}
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);

10
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -43,8 +43,8 @@ namespace Avalonia.X11.Glx
var l = _context.Lock();
try
{
_context.MakeCurrent(_info.Handle);
return new Session(_context, _info, l);
return new Session(_context, _info, l, _context.MakeCurrent(_info.Handle));
}
catch
{
@ -58,13 +58,15 @@ namespace Avalonia.X11.Glx
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IDisposable _lock;
private readonly IDisposable _clearContext;
public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
IDisposable @lock, IDisposable clearContext)
{
_context = context;
_info = info;
_lock = @lock;
_clearContext = clearContext;
}
public void Dispose()
@ -73,7 +75,7 @@ namespace Avalonia.X11.Glx
_context.Glx.WaitGL();
_context.Display.SwapBuffers(_info.Handle);
_context.Glx.WaitX();
_context.Display.ClearContext();
_clearContext.Dispose();
_lock.Dispose();
}

5
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -7,7 +7,9 @@ namespace Avalonia.X11.Glx
class GlxGlPlatformFeature : IWindowingPlatformGlFeature
{
public GlxDisplay Display { get; private set; }
public IGlContext ImmediateContext { get; private set; }
public IGlContext CreateContext() => Display.CreateContext();
IGlDisplay IWindowingPlatformGlFeature.Display => Display;
public GlxContext DeferredContext { get; private set; }
public static bool TryInitialize(X11Info x11)
@ -30,7 +32,6 @@ namespace Avalonia.X11.Glx
return new GlxGlPlatformFeature
{
Display = disp,
ImmediateContext = disp.ImmediateContext,
DeferredContext = disp.DeferredContext
};
}

35
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -48,7 +48,6 @@ namespace Avalonia.LinuxFramebuffer.Output
private drmModeModeInfo _mode;
private EglDisplay _eglDisplay;
private EglSurface _eglSurface;
private EglContext _immediateContext;
private EglContext _deferredContext;
private IntPtr _currentBo;
private IntPtr _gbmTargetSurface;
@ -129,13 +128,15 @@ namespace Avalonia.LinuxFramebuffer.Output
return _eglDisplay.CreateContext(share, _eglDisplay.CreateWindowSurface(offSurf));
}
_immediateContext = CreateContext(null);
_deferredContext = CreateContext(_immediateContext);
_immediateContext.MakeCurrent(_eglSurface);
_eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
_eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_eglSurface.SwapBuffers();
_deferredContext = CreateContext(null);
using (_deferredContext.MakeCurrent(_eglSurface))
{
_eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
_eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
_eglSurface.SwapBuffers();
}
var bo = gbm_surface_lock_front_buffer(_gbmTargetSurface);
var fbId = GetFbIdForBo(bo);
var connectorId = connector.Id;
@ -156,6 +157,7 @@ namespace Avalonia.LinuxFramebuffer.Output
_eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
_eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
}
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
@ -179,10 +181,12 @@ namespace Avalonia.LinuxFramebuffer.Output
class RenderSession : IGlPlatformSurfaceRenderingSession
{
private readonly DrmOutput _parent;
private readonly IDisposable _clearContext;
public RenderSession(DrmOutput parent)
public RenderSession(DrmOutput parent, IDisposable clearContext)
{
_parent = parent;
_clearContext = clearContext;
}
public void Dispose()
@ -225,7 +229,7 @@ namespace Avalonia.LinuxFramebuffer.Output
gbm_surface_release_buffer(_parent._gbmTargetSurface, _parent._currentBo);
_parent._currentBo = nextBo;
}
_parent._eglDisplay.ClearContext();
_clearContext.Dispose();
}
@ -238,14 +242,15 @@ namespace Avalonia.LinuxFramebuffer.Output
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
_parent._deferredContext.MakeCurrent(_parent._eglSurface);
return new RenderSession(_parent);
return new RenderSession(_parent, _parent._deferredContext.MakeCurrent(_parent._eglSurface));
}
}
IGlContext IWindowingPlatformGlFeature.ImmediateContext => _immediateContext;
public IGlDisplay Display => _eglDisplay;
public IGlContext CreateContext()
{
throw new NotImplementedException();
}
}

4
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -31,8 +31,10 @@ namespace Avalonia.Skia
private double _currentOpacity = 1.0f;
private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform;
private GRContext _grContext;
private bool _disposed;
private GRContext _grContext;
public GRContext GrContext => _grContext;
/// <summary>
/// Context create info.
/// </summary>

15
src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -9,18 +10,18 @@ namespace Avalonia.Skia
/// <summary>
/// Custom Skia gpu instance.
/// </summary>
public interface ICustomSkiaGpu
public interface ISkiaGpu
{
/// <summary>
/// Skia GrContext used.
/// </summary>
GRContext GrContext { get; }
/// <summary>
/// Attempts to create custom render target from given surfaces.
/// </summary>
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ICustomSkiaRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
}
public interface IOpenGlAwareSkiaGpu : ISkiaGpu
{
IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap();
}
}

2
src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderSession.cs

@ -9,7 +9,7 @@ namespace Avalonia.Skia
/// <summary>
/// Custom render session for Skia render target.
/// </summary>
public interface ICustomSkiaRenderSession : IDisposable
public interface ISkiaGpuRenderSession : IDisposable
{
/// <summary>
/// GrContext used by this session.

7
src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/ISkiaGpuRenderTarget.cs

@ -2,18 +2,21 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Custom Skia render target.
/// </summary>
public interface ICustomSkiaRenderTarget : IDisposable
public interface ISkiaGpuRenderTarget : IDisposable
{
/// <summary>
/// Start rendering to this render target.
/// </summary>
/// <returns></returns>
ICustomSkiaRenderSession BeginRendering();
ISkiaGpuRenderSession BeginRenderingSession();
bool IsCorrupted { get; }
}
}

71
src/Skia/Avalonia.Skia/GlRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs

@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
internal class GlRenderTarget : IRenderTargetWithCorruptionInfo
internal class GlRenderTarget : ISkiaGpuRenderTarget
{
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;
@ -22,22 +22,52 @@ namespace Avalonia.Skia
public void Dispose() => _surface.Dispose();
public bool IsCorrupted => (_surface as IGlPlatformSurfaceRenderTargetWithCorruptionInfo)?.IsCorrupted == true;
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
class GlGpuSession : ISkiaGpuRenderSession
{
private readonly GRBackendRenderTarget _backendRenderTarget;
private readonly SKSurface _surface;
private readonly IGlPlatformSurfaceRenderingSession _glSession;
public GlGpuSession(GRContext grContext,
GRBackendRenderTarget backendRenderTarget,
SKSurface surface,
IGlPlatformSurfaceRenderingSession glSession)
{
GrContext = grContext;
_backendRenderTarget = backendRenderTarget;
_surface = surface;
_glSession = glSession;
}
public void Dispose()
{
_surface.Canvas.Flush();
_surface.Dispose();
_backendRenderTarget.Dispose();
GrContext.Flush();
_glSession.Dispose();
}
public GRContext GrContext { get; }
public SKCanvas Canvas => _surface.Canvas;
public double ScaleFactor => _glSession.Scaling;
}
public ISkiaGpuRenderSession BeginRenderingSession()
{
var session = _surface.BeginDraw();
var glSession = _surface.BeginDraw();
bool success = false;
try
{
var disp = session.Display;
var disp = glSession.Display;
var gl = disp.GlInterface;
gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb);
var size = session.Size;
var scaling = session.Scaling;
var size = glSession.Size;
var scaling = glSession.Scaling;
if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
{
session.Dispose();
glSession.Dispose();
throw new InvalidOperationException(
$"Can't create drawing context for surface with {size} size and {scaling} scaling");
}
@ -50,40 +80,21 @@ namespace Avalonia.Skia
{
_grContext.ResetContext();
GRBackendRenderTarget renderTarget =
var renderTarget =
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat()));
var surface = SKSurface.Create(_grContext, renderTarget,
GRSurfaceOrigin.BottomLeft,
GRPixelConfig.Rgba8888.ToColorType());
var nfo = new DrawingContextImpl.CreateInfo
{
GrContext = _grContext,
Canvas = surface.Canvas,
Dpi = SkiaPlatform.DefaultDpi * scaling,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
};
var ctx = new DrawingContextImpl(nfo, Disposable.Create(() =>
{
surface.Canvas.Flush();
surface.Dispose();
renderTarget.Dispose();
_grContext.Flush();
session.Dispose();
}));
success = true;
return ctx;
return new GlGpuSession(_grContext, renderTarget, surface, glSession);
}
}
finally
{
if(!success)
session.Dispose();
glSession.Dispose();
}
}
}

43
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -0,0 +1,43 @@
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
{
class GlSkiaGpu : IOpenGlAwareSkiaGpu
{
private GRContext _grContext;
public GlSkiaGpu(IWindowingPlatformGlFeature gl)
{
var immediateContext = gl.CreateContext();
using (immediateContext.MakeCurrent())
{
var display = gl.Display;
using (var iface = display.Type == GlDisplayType.OpenGL2 ?
GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc)) :
GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
_grContext = GRContext.Create(GRBackend.OpenGL, iface);
}
}
}
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
{
foreach (var surface in surfaces)
{
if (surface is IGlPlatformSurface glSurface)
{
return new GlRenderTarget(_grContext, glSurface);
}
}
return null;
}
public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() => new OpenGlTextureBitmapImpl();
}
}

81
src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs

@ -0,0 +1,81 @@
using System;
using System.IO;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Skia.Helpers;
using Avalonia.Utilities;
using SkiaSharp;
namespace Avalonia.Skia
{
class OpenGlTextureBitmapImpl : IOpenGlTextureBitmapImpl, IDrawableBitmapImpl
{
private DisposableLock _lock = new DisposableLock();
private int _textureId;
private int _internalFormat;
public void Dispose()
{
using (Lock())
{
_textureId = 0;
PixelSize = new PixelSize(1, 1);
Version++;
}
}
public Vector Dpi { get; private set; } = new Vector(96, 96);
public PixelSize PixelSize { get; private set; } = new PixelSize(1, 1);
public int Version { get; private set; } = 0;
public void Save(string fileName) => throw new System.NotSupportedException();
public void Save(Stream stream) => throw new System.NotSupportedException();
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{
// For now silently ignore
if (context.GrContext == null)
return;
using (Lock())
{
if (_textureId == 0)
return;
using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false,
new GRGlTextureInfo(
GlConsts.GL_TEXTURE_2D, (uint)_textureId,
(uint)_internalFormat)))
using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft,
SKColorType.Rgba8888))
{
// Again, silently ignore, if something went wrong it's not our fault
if (surface == null)
return;
using (var snapshot = surface.Snapshot())
context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint);
}
}
}
public IDisposable Lock() => _lock.Lock();
public void SetBackBuffer(int textureId, int internalFormat, PixelSize pixelSize, double dpiScaling)
{
using (_lock.Lock())
{
_textureId = textureId;
_internalFormat = internalFormat;
PixelSize = pixelSize;
Dpi = new Vector(96 * dpiScaling, 96 * dpiScaling);
Version++;
}
}
public void SetDirty()
{
using (_lock.Lock())
Version++;
}
}
}

12
src/Skia/Avalonia.Skia/CustomRenderTarget.cs → src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -7,13 +7,13 @@ using Avalonia.Rendering;
namespace Avalonia.Skia
{
/// <summary>
/// Adapts <see cref="ICustomSkiaRenderTarget"/> to be used within Skia rendering pipeline.
/// Adapts <see cref="ISkiaGpuRenderTarget"/> to be used within our rendering pipeline.
/// </summary>
internal class CustomRenderTarget : IRenderTarget
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
{
private readonly ICustomSkiaRenderTarget _renderTarget;
private readonly ISkiaGpuRenderTarget _renderTarget;
public CustomRenderTarget(ICustomSkiaRenderTarget renderTarget)
public SkiaGpuRenderTarget(ISkiaGpuRenderTarget renderTarget)
{
_renderTarget = renderTarget;
}
@ -25,7 +25,7 @@ namespace Avalonia.Skia
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
ICustomSkiaRenderSession session = _renderTarget.BeginRendering();
var session = _renderTarget.BeginRenderingSession();
var nfo = new DrawingContextImpl.CreateInfo
{
@ -38,5 +38,7 @@ namespace Avalonia.Skia
return new DrawingContextImpl(nfo, session);
}
public bool IsCorrupted => _renderTarget.IsCorrupted;
}
}

59
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -2,11 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using SkiaSharp;
@ -15,35 +18,21 @@ namespace Avalonia.Skia
/// <summary>
/// Skia platform render interface.
/// </summary>
internal class PlatformRenderInterface : IPlatformRenderInterface
internal class PlatformRenderInterface : IPlatformRenderInterface, IOpenGlAwarePlatformRenderInterface
{
private readonly ICustomSkiaGpu _customSkiaGpu;
private readonly ISkiaGpu _skiaGpu;
private GRContext GrContext { get; }
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu)
public PlatformRenderInterface(ISkiaGpu skiaGpu)
{
if (customSkiaGpu != null)
if (skiaGpu != null)
{
_customSkiaGpu = customSkiaGpu;
GrContext = _customSkiaGpu.GrContext;
_skiaGpu = skiaGpu;
return;
}
var gl = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
if (gl != null)
{
var display = gl.ImmediateContext.Display;
gl.ImmediateContext.MakeCurrent();
using (var iface = display.Type == GlDisplayType.OpenGL2
? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
}
}
if (gl != null)
_skiaGpu = new GlSkiaGpu(gl);
}
/// <inheritdoc />
@ -119,26 +108,18 @@ namespace Avalonia.Skia
/// <inheritdoc />
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
if (_customSkiaGpu != null)
if (!(surfaces is IList))
surfaces = surfaces.ToList();
var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null)
{
ICustomSkiaRenderTarget customRenderTarget = _customSkiaGpu.TryCreateRenderTarget(surfaces);
if (customRenderTarget != null)
{
return new CustomRenderTarget(customRenderTarget);
}
return new SkiaGpuRenderTarget(gpuRenderTarget);
}
foreach (var surface in surfaces)
{
if (surface is IGlPlatformSurface glSurface && GrContext != null)
{
return new GlRenderTarget(GrContext, glSurface);
}
if (surface is IFramebufferPlatformSurface framebufferSurface)
{
return new FramebufferRenderTarget(framebufferSurface);
}
}
throw new NotSupportedException(
@ -241,5 +222,15 @@ namespace Avalonia.Skia
return new GlyphRunImpl(paint, textBlob);
}
}
public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap()
{
if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
return glAware.CreateOpenGlTextureBitmap();
if (_skiaGpu == null)
throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
}
}

2
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -14,6 +14,6 @@ namespace Avalonia
/// <summary>
/// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
/// </summary>
public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; }
public Func<ISkiaGpu> CustomGpuFactory { get; set; }
}
}

Loading…
Cancel
Save