diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index 6c13a5ac22..cb79bf219a 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; using Avalonia.OpenGL; +using Avalonia.OpenGL.Controls; using Avalonia.Platform.Interop; using Avalonia.Threading; using static Avalonia.OpenGL.GlConsts; diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 804cf7f8ac..e8b2f065c7 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -16,7 +16,7 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions _options; - private GlPlatformFeature _glFeature; + private AvaloniaNativePlatformOpenGlInterface _platformGl; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); @@ -116,8 +116,8 @@ namespace Avalonia.Native { try { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay())); + AvaloniaLocator.CurrentMutable.Bind() + .ToConstant(_platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay())); } catch (Exception) { @@ -128,7 +128,7 @@ namespace Avalonia.Native public IWindowImpl CreateWindow() { - return new WindowImpl(_factory, _options, _glFeature); + return new WindowImpl(_factory, _options, _platformGl); } public IWindowImpl CreateEmbeddableWindow() diff --git a/src/Avalonia.Native/GlPlatformFeature.cs b/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs similarity index 76% rename from src/Avalonia.Native/GlPlatformFeature.cs rename to src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs index e321db6eda..dbe968b82f 100644 --- a/src/Avalonia.Native/GlPlatformFeature.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs @@ -2,21 +2,20 @@ using Avalonia.OpenGL; using Avalonia.Native.Interop; using System.Drawing; +using Avalonia.OpenGL.Surfaces; using Avalonia.Threading; namespace Avalonia.Native { - class GlPlatformFeature : IWindowingPlatformGlFeature + class AvaloniaNativePlatformOpenGlInterface : IPlatformOpenGlInterface { private readonly IAvnGlDisplay _display; - public GlPlatformFeature(IAvnGlDisplay display) + public AvaloniaNativePlatformOpenGlInterface(IAvnGlDisplay display) { _display = display; var immediate = display.CreateContext(null); - var deferred = display.CreateContext(immediate); - int major, minor; GlInterface glInterface; using (immediate.MakeCurrent()) @@ -33,19 +32,22 @@ namespace Avalonia.Native } GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize); - - ImmediateContext = new GlContext(GlDisplay, immediate, _version); - DeferredContext = new GlContext(GlDisplay, deferred, _version); + MainContext = new GlContext(GlDisplay, null, immediate, _version); } - internal IGlContext ImmediateContext { get; } - public IGlContext MainContext => DeferredContext; - internal GlContext DeferredContext { get; } + internal GlContext MainContext { get; } + public IGlContext PrimaryContext => MainContext; + + public bool CanShareContexts => true; + public bool CanCreateContexts => true; internal GlDisplay GlDisplay; private readonly GlVersion _version; + public IGlContext CreateSharedContext() => new GlContext(GlDisplay, + MainContext, _display.CreateContext(MainContext.Context), _version); + public IGlContext CreateContext() => new GlContext(GlDisplay, - _display.CreateContext(((GlContext)ImmediateContext).Context), _version); + null, _display.CreateContext(null), _version); } class GlDisplay @@ -72,11 +74,13 @@ namespace Avalonia.Native class GlContext : IGlContext { private readonly GlDisplay _display; + private readonly GlContext _sharedWith; public IAvnGlContext Context { get; private set; } - public GlContext(GlDisplay display, IAvnGlContext context, GlVersion version) + public GlContext(GlDisplay display, GlContext sharedWith, IAvnGlContext context, GlVersion version) { _display = display; + _sharedWith = sharedWith; Context = context; Version = version; } @@ -86,6 +90,17 @@ namespace Avalonia.Native public int SampleCount => _display.SampleCount; public int StencilSize => _display.StencilSize; public IDisposable MakeCurrent() => Context.MakeCurrent(); + public IDisposable EnsureCurrent() => MakeCurrent(); + + public bool IsSharedWith(IGlContext context) + { + var c = (GlContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } + public void Dispose() { @@ -108,7 +123,7 @@ namespace Avalonia.Native public IGlPlatformSurfaceRenderingSession BeginDraw() { - var feature = (GlPlatformFeature)AvaloniaLocator.Current.GetService(); + var feature = (AvaloniaNativePlatformOpenGlInterface)AvaloniaLocator.Current.GetService(); return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing()); } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 2d246e08d2..2f98385038 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -9,12 +9,12 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; - private readonly GlPlatformFeature _glFeature; + private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - GlPlatformFeature glFeature, + AvaloniaNativePlatformOpenGlInterface glFeature, IWindowBaseImpl parent) : base(opts, glFeature) { _factory = factory; @@ -23,7 +23,7 @@ namespace Avalonia.Native _parent = parent; using (var e = new PopupEvents(this)) { - var context = _opts.UseGpu ? glFeature?.DeferredContext : null; + var context = _opts.UseGpu ? glFeature?.MainContext : null; Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); } PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 885591495b..11a0ebce61 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -14,19 +14,19 @@ namespace Avalonia.Native { private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; - private readonly GlPlatformFeature _glFeature; + private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; IAvnWindow _native; private double _extendTitleBarHeight = -1; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - GlPlatformFeature glFeature) : base(opts, glFeature) + AvaloniaNativePlatformOpenGlInterface glFeature) : base(opts, glFeature) { _factory = factory; _opts = opts; _glFeature = glFeature; using (var e = new WindowEvents(this)) { - var context = _opts.UseGpu ? glFeature?.DeferredContext : null; + var context = _opts.UseGpu ? glFeature?.MainContext : null; Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 56cf544d9d..5d35c773d7 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -61,7 +61,7 @@ namespace Avalonia.Native private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature) + internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) { _gpu = opts.UseGpu && glFeature != null; _deferredRendering = opts.UseDeferredRendering; diff --git a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs index 8565d99b45..8c9b028164 100644 --- a/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs +++ b/src/Avalonia.OpenGL/Angle/AngleEglInterface.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Platform.Interop; diff --git a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs index 1a42ed90c2..191fb53204 100644 --- a/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs +++ b/src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; - -using static Avalonia.OpenGL.EglConsts; +using Avalonia.OpenGL.Egl; +using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Angle { @@ -52,7 +52,7 @@ namespace Avalonia.OpenGL.Angle } } - private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, info.Display) + private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, false, info.Display) { PlatformApi = info.PlatformApi; } @@ -78,11 +78,11 @@ namespace Avalonia.OpenGL.Angle return d3dDeviceHandle; } - public EglSurface WrapDirect3D11Texture(IntPtr handle) + public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle) { if (PlatformApi != AngleOptions.PlatformApi.DirectX11) throw new InvalidOperationException("Current platform API is " + PlatformApi); - return CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); + return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE }); } } } diff --git a/src/Avalonia.OpenGL/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs similarity index 50% rename from src/Avalonia.OpenGL/OpenGlControlBase.cs rename to src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index 8567dcae20..33773ed8e2 100644 --- a/src/Avalonia.OpenGL/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -3,44 +3,83 @@ using Avalonia.Controls; using Avalonia.Logging; using Avalonia.Media; using Avalonia.OpenGL.Imaging; -using Avalonia.Rendering; -using Avalonia.VisualTree; using static Avalonia.OpenGL.GlConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Controls { public abstract class OpenGlControlBase : Control { private IGlContext _context; - private int _fb, _texture, _renderBuffer; - private OpenGlTextureBitmap _bitmap; - private PixelSize _oldSize; + private int _fb, _depthBuffer; + private OpenGlBitmap _bitmap; + private IOpenGlBitmapAttachment _attachment; + private PixelSize _depthBufferSize; private bool _glFailed; + private bool _initialized; protected GlVersion GlVersion { get; private set; } public sealed override void Render(DrawingContext context) { if(!EnsureInitialized()) return; - + using (_context.MakeCurrent()) { - using (_bitmap.Lock()) - { - var gl = _context.GlInterface; - gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); - if (_oldSize != GetPixelSize()) - ResizeTexture(gl); - - OnOpenGlRender(gl, _fb); - gl.Flush(); - } + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); + EnsureTextureAttachment(); + EnsureDepthBufferAttachment(_context.GlInterface); + if(!CheckFramebufferStatus(_context.GlInterface)) + return; + + OnOpenGlRender(_context.GlInterface, _fb); + _attachment.Present(); } context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds); base.Render(context); } + + private void CheckError(GlInterface gl) + { + int err; + while ((err = gl.GetError()) != GL_NO_ERROR) + Console.WriteLine(err); + } + + void EnsureTextureAttachment() + { + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); + if (_bitmap == null || _attachment == null || _bitmap.PixelSize != GetPixelSize()) + { + _attachment?.Dispose(); + _attachment = null; + _bitmap?.Dispose(); + _bitmap = null; + _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + _attachment = _bitmap.CreateFramebufferAttachment(_context); + } + } + + void EnsureDepthBufferAttachment(GlInterface gl) + { + var size = GetPixelSize(); + if (size == _depthBufferSize && _depthBuffer != 0) + return; + + gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); + if (_depthBuffer != 0) gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + + var oneArr = new int[1]; + gl.GenRenderbuffers(1, oneArr); + _depthBuffer = oneArr[0]; + gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); + gl.RenderbufferStorage(GL_RENDERBUFFER, + GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, + size.Width, size.Height); + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer); + gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer); + } - void DoCleanup(bool callUserDeinit) + void DoCleanup() { if (_context != null) { @@ -50,16 +89,19 @@ namespace Avalonia.OpenGL gl.BindTexture(GL_TEXTURE_2D, 0); gl.BindFramebuffer(GL_FRAMEBUFFER, 0); gl.DeleteFramebuffers(1, new[] { _fb }); - using (_bitmap.Lock()) - _bitmap.SetTexture(0, 0, new PixelSize(1, 1), 1); - gl.DeleteTextures(1, new[] { _texture }); - gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); - _bitmap.Dispose(); + gl.DeleteRenderbuffers(1, new[] { _depthBuffer }); + _attachment?.Dispose(); + _attachment = null; + _bitmap?.Dispose(); + _bitmap = null; try { - if (callUserDeinit) + if (_initialized) + { + _initialized = false; OnOpenGlDeinit(_context.GlInterface, _fb); + } } finally { @@ -72,11 +114,11 @@ namespace Avalonia.OpenGL protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - DoCleanup(true); + DoCleanup(); base.OnDetachedFromVisualTree(e); } - bool EnsureInitialized() + private bool EnsureInitializedCore() { if (_context != null) return true; @@ -84,34 +126,43 @@ namespace Avalonia.OpenGL if (_glFailed) return false; - var feature = AvaloniaLocator.Current.GetService(); + var feature = AvaloniaLocator.Current.GetService(); if (feature == null) return false; + if (!feature.CanShareContexts) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: current platform does not support multithreaded context sharing"); + return false; + } try { - _context = feature.CreateContext(); - + _context = feature.CreateSharedContext(); } catch (Exception e) { Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e); - _glFailed = true; return false; } GlVersion = _context.Version; try { - _bitmap = new OpenGlTextureBitmap(); + _bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96)); + if (!_bitmap.SupportsContext(_context)) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: unable to create OpenGlBitmap: OpenGL context is not compatible"); + return false; + } } catch (Exception e) { _context.Dispose(); _context = null; Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", - "Unable to initialize OpenGL: unable to create OpenGlTextureBitmap: {exception}", e); - _glFailed = true; + "Unable to initialize OpenGL: unable to create OpenGlBitmap: {exception}", e); return false; } @@ -119,80 +170,55 @@ namespace Avalonia.OpenGL { try { - _oldSize = GetPixelSize(); + _depthBufferSize = GetPixelSize(); var gl = _context.GlInterface; var oneArr = new int[1]; gl.GenFramebuffers(1, oneArr); _fb = oneArr[0]; gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); - - gl.GenTextures(1, oneArr); - _texture = oneArr[0]; - ResizeTexture(gl); - - gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + EnsureDepthBufferAttachment(gl); + EnsureTextureAttachment(); - var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) - { - int code; - while ((code = gl.GetError()) != 0) - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", - "Unable to initialize OpenGL FBO: {code}", code); - - _glFailed = true; - return false; - } + return CheckFramebufferStatus(gl); } catch(Exception e) { Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", "Unable to initialize OpenGL FBO: {exception}", e); - _glFailed = true; + return false; } - - if (!_glFailed) - OnOpenGlInit(_context.GlInterface, _fb); } + } - if (_glFailed) + private bool CheckFramebufferStatus(GlInterface gl) + { + var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { - DoCleanup(false); + int code; + while ((code = gl.GetError()) != 0) + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL FBO: {code}", code); + return false; } return true; } - void ResizeTexture(GlInterface gl) + private bool EnsureInitialized() { - var size = GetPixelSize(); - - gl.GetIntegerv( GL_TEXTURE_BINDING_2D, out var oldTexture); - gl.BindTexture(GL_TEXTURE_2D, _texture); - gl.TexImage2D(GL_TEXTURE_2D, 0, - GlVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8, - size.Width, size.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.BindTexture(GL_TEXTURE_2D, oldTexture); - - gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer); - gl.DeleteRenderbuffers(1, new[] { _renderBuffer }); - var oneArr = new int[1]; - gl.GenRenderbuffers(1, oneArr); - _renderBuffer = oneArr[0]; - gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); - gl.RenderbufferStorage(GL_RENDERBUFFER, - GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, - size.Width, size.Height); - gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer); - gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer); - using (_bitmap.Lock()) - _bitmap.SetTexture(_texture, GL_RGBA8, size, 1); + if (_initialized) + return true; + _glFailed = !(_initialized = EnsureInitializedCore()); + if (_glFailed) + return false; + using (_context.MakeCurrent()) + OnOpenGlInit(_context.GlInterface, _fb); + return true; } - PixelSize GetPixelSize() + private PixelSize GetPixelSize() { var scaling = VisualRoot.RenderScaling; return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), diff --git a/src/Avalonia.OpenGL/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs similarity index 99% rename from src/Avalonia.OpenGL/EglConsts.cs rename to src/Avalonia.OpenGL/Egl/EglConsts.cs index 8e44004f2d..58f5f1cef5 100644 --- a/src/Avalonia.OpenGL/EglConsts.cs +++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs @@ -1,6 +1,6 @@ // ReSharper disable UnusedMember.Global // ReSharper disable IdentifierTypo -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public static class EglConsts { diff --git a/src/Avalonia.OpenGL/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs similarity index 55% rename from src/Avalonia.OpenGL/EglContext.cs rename to src/Avalonia.OpenGL/Egl/EglContext.cs index 871665e857..5365354418 100644 --- a/src/Avalonia.OpenGL/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -1,23 +1,25 @@ using System; using System.Reactive.Disposables; using System.Threading; -using static Avalonia.OpenGL.EglConsts; +using static Avalonia.OpenGL.Egl.EglConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglContext : IGlContext { private readonly EglDisplay _disp; private readonly EglInterface _egl; + private readonly EglContext _sharedWith; private readonly object _lock = new object(); - public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, EglSurface offscreenSurface, + public EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, Func offscreenSurface, GlVersion version, int sampleCount, int stencilSize) { _disp = display; _egl = egl; + _sharedWith = sharedWith; Context = ctx; - OffscreenSurface = offscreenSurface; + OffscreenSurface = offscreenSurface(this); Version = version; SampleCount = sampleCount; StencilSize = stencilSize; @@ -33,21 +35,17 @@ namespace Avalonia.OpenGL public int StencilSize { get; } public EglDisplay Display => _disp; - public IDisposable Lock() - { - Monitor.Enter(_lock); - return Disposable.Create(() => Monitor.Exit(_lock)); - } - class RestoreContext : IDisposable { private readonly EglInterface _egl; + private readonly object _l; private readonly IntPtr _display; private IntPtr _context, _read, _draw; - public RestoreContext(EglInterface egl, IntPtr defDisplay) + public RestoreContext(EglInterface egl, IntPtr defDisplay, object l) { _egl = egl; + _l = l; _display = _egl.GetCurrentDisplay(); if (_display == IntPtr.Zero) _display = defDisplay; @@ -59,29 +57,52 @@ namespace Avalonia.OpenGL public void Dispose() { _egl.MakeCurrent(_display, _draw, _read, _context); + Monitor.Exit(_l); } } - public IDisposable MakeCurrent() + public IDisposable MakeCurrent() => MakeCurrent(OffscreenSurface); + + public IDisposable MakeCurrent(EglSurface surface) { - var old = new RestoreContext(_egl, _disp.Handle); - _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context)) - throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); - return old; + Monitor.Enter(_lock); + var success = false; + try + { + var old = new RestoreContext(_egl, _disp.Handle, _lock); + var surf = surface ?? OffscreenSurface; + _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) + throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); + success = true; + return old; + } + finally + { + if(!success) + Monitor.Enter(_lock); + } } - public IDisposable MakeCurrent(EglSurface surface) + public IDisposable EnsureCurrent() { - var old = new RestoreContext(_egl, _disp.Handle); - var surf = surface ?? OffscreenSurface; - _egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context)) - throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl); - return old; + if(IsCurrent) + return Disposable.Empty; + return MakeCurrent(); } + public bool IsSharedWith(IGlContext context) + { + var c = (EglContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } + + public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context; + public void Dispose() { _egl.DestroyContext(_disp.Handle, Context); diff --git a/src/Avalonia.OpenGL/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs similarity index 79% rename from src/Avalonia.OpenGL/EglDisplay.cs rename to src/Avalonia.OpenGL/Egl/EglDisplay.cs index 7f41e75d6a..fd3de854f5 100644 --- a/src/Avalonia.OpenGL/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -1,26 +1,25 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; -using static Avalonia.OpenGL.EglConsts; +using static Avalonia.OpenGL.Egl.EglConsts; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglDisplay { private readonly EglInterface _egl; + public bool SupportsSharing { get; } private readonly IntPtr _display; private readonly IntPtr _config; private readonly int[] _contextAttributes; private readonly int _surfaceType; public IntPtr Handle => _display; + public IntPtr Config => _config; private int _sampleCount; private int _stencilSize; private GlVersion _version; - public EglDisplay(EglInterface egl) : this(egl, -1, IntPtr.Zero, null) + public EglDisplay(EglInterface egl, bool supportsSharing) : this(egl, supportsSharing, -1, IntPtr.Zero, null) { } @@ -45,15 +44,16 @@ namespace Avalonia.OpenGL return display; } - public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs) - : this(egl, CreateDisplay(egl, platformType, platformDisplay, attrs)) + public EglDisplay(EglInterface egl, bool supportsSharing, int platformType, IntPtr platformDisplay, int[] attrs) + : this(egl, supportsSharing, CreateDisplay(egl, platformType, platformDisplay, attrs)) { } - public EglDisplay(EglInterface egl, IntPtr display) + public EglDisplay(EglInterface egl, bool supportsSharing, IntPtr display) { _egl = egl; + SupportsSharing = supportsSharing; _display = display; if(_display == IntPtr.Zero) throw new ArgumentException(); @@ -136,7 +136,12 @@ namespace Avalonia.OpenGL throw new OpenGlException("No suitable EGL config was found"); } - public EglDisplay() : this(new EglInterface()) + public EglDisplay() : this(false) + { + + } + + public EglDisplay(bool supportsSharing) : this(new EglInterface(), supportsSharing) { } @@ -144,6 +149,9 @@ namespace Avalonia.OpenGL public EglInterface EglInterface => _egl; public EglContext CreateContext(IGlContext share) { + if (share != null && !SupportsSharing) + throw new NotSupportedException("Context sharing is not supported by this display"); + if((_surfaceType|EGL_PBUFFER_BIT) == 0) throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces"); var shareCtx = (EglContext)share; @@ -158,37 +166,22 @@ namespace Avalonia.OpenGL }); if (surf == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl); - var rv = new EglContext(this, _egl, ctx, new EglSurface(this, _egl, surf), + var rv = new EglContext(this, _egl, shareCtx, ctx, context => new EglSurface(this, context, surf), _version, _sampleCount, _stencilSize); return rv; } public EglContext CreateContext(EglContext share, EglSurface offscreenSurface) { + if (share != null && !SupportsSharing) + throw new NotSupportedException("Context sharing is not supported by this display"); + var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes); if (ctx == IntPtr.Zero) throw OpenGlException.GetFormattedException("eglCreateContext", _egl); - var rv = new EglContext(this, _egl, ctx, offscreenSurface, _version, _sampleCount, _stencilSize); + var rv = new EglContext(this, _egl, share, ctx, _ => offscreenSurface, _version, _sampleCount, _stencilSize); rv.MakeCurrent(null); return rv; } - - public EglSurface CreateWindowSurface(IntPtr window) - { - var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE}); - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl); - return new EglSurface(this, _egl, s); - } - - public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) - { - var s = _egl.CreatePbufferFromClientBuffer(_display, bufferType, handle, - _config, attribs); - - if (s == IntPtr.Zero) - throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", _egl); - return new EglSurface(this, _egl, s); - } } } diff --git a/src/Avalonia.OpenGL/EglErrors.cs b/src/Avalonia.OpenGL/Egl/EglErrors.cs similarity index 96% rename from src/Avalonia.OpenGL/EglErrors.cs rename to src/Avalonia.OpenGL/Egl/EglErrors.cs index bfe46f2b69..d89bbb499f 100644 --- a/src/Avalonia.OpenGL/EglErrors.cs +++ b/src/Avalonia.OpenGL/Egl/EglErrors.cs @@ -1,4 +1,4 @@ -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public enum EglErrors { diff --git a/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs new file mode 100644 index 0000000000..3d58660d47 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs @@ -0,0 +1,54 @@ +using Avalonia.OpenGL.Surfaces; + +namespace Avalonia.OpenGL.Egl +{ + public class EglGlPlatformSurface : EglGlPlatformSurfaceBase + { + private readonly EglPlatformOpenGlInterface _egl; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + + public EglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base() + { + _egl = egl; + _info = info; + } + + public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() + { + var glSurface = _egl.CreateWindowSurface(_info.Handle); + return new RenderTarget(_egl, glSurface, _info); + } + + class RenderTarget : EglPlatformSurfaceRenderTargetBase + { + private readonly EglPlatformOpenGlInterface _egl; + private EglSurface _glSurface; + private readonly IEglWindowGlPlatformSurfaceInfo _info; + private PixelSize _currentSize; + + public RenderTarget(EglPlatformOpenGlInterface egl, + EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(egl) + { + _egl = egl; + _glSurface = glSurface; + _info = info; + _currentSize = info.Size; + } + + public override void Dispose() => _glSurface.Dispose(); + + public override IGlPlatformSurfaceRenderingSession BeginDraw() + { + if (_info.Size != _currentSize || _glSurface == null) + { + _glSurface?.Dispose(); + _glSurface = null; + _glSurface = _egl.CreateWindowSurface(_info.Handle); + _currentSize = _info.Size; + } + return base.BeginDraw(_glSurface, _info); + } + } + } +} + diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs similarity index 67% rename from src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs rename to src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs index 00c7c4796c..4ea6766de2 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurfaceBase.cs +++ b/src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs @@ -1,6 +1,7 @@ using System; +using Avalonia.OpenGL.Surfaces; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface { @@ -14,19 +15,15 @@ namespace Avalonia.OpenGL public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(); } - public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo + public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTarget { - private readonly EglDisplay _display; - private readonly EglContext _context; + private readonly EglPlatformOpenGlInterface _egl; - protected EglPlatformSurfaceRenderTargetBase(EglDisplay display, EglContext context) + protected EglPlatformSurfaceRenderTargetBase(EglPlatformOpenGlInterface egl) { - _display = display; - _context = context; + _egl = egl; } - public abstract bool IsCorrupted { get; } - public virtual void Dispose() { @@ -37,22 +34,25 @@ namespace Avalonia.OpenGL protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false) { - var l = _context.Lock(); + + var restoreContext = _egl.PrimaryEglContext.MakeCurrent(surface); + var success = false; try { - if (IsCorrupted) - throw new RenderTargetCorruptedException(); - var restoreContext = _context.MakeCurrent(surface); - _display.EglInterface.WaitClient(); - _display.EglInterface.WaitGL(); - _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); - - return new Session(_display, _context, surface, info, l, restoreContext, onFinish, isYFlipped); + var egli = _egl.Display.EglInterface; + egli.WaitClient(); + egli.WaitGL(); + egli.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); + + _egl.PrimaryContext.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0); + + success = true; + return new Session(_egl.Display, _egl.PrimaryEglContext, surface, info, restoreContext, onFinish, isYFlipped); } - catch + finally { - l.Dispose(); - throw; + if(!success) + restoreContext.Dispose(); } } @@ -62,21 +62,19 @@ namespace Avalonia.OpenGL private readonly EglSurface _glSurface; private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; private readonly EglDisplay _display; - private readonly IDisposable _lock; private readonly IDisposable _restoreContext; private readonly Action _onFinish; public Session(EglDisplay display, EglContext context, EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable restoreContext, Action onFinish, bool isYFlipped) + IDisposable restoreContext, Action onFinish, bool isYFlipped) { IsYFlipped = isYFlipped; _context = context; _display = display; _glSurface = glSurface; _info = info; - _lock = @lock; _restoreContext = restoreContext; _onFinish = onFinish; } @@ -90,7 +88,6 @@ namespace Avalonia.OpenGL _display.EglInterface.WaitGL(); _display.EglInterface.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE); _restoreContext.Dispose(); - _lock.Dispose(); _onFinish?.Invoke(); } diff --git a/src/Avalonia.OpenGL/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs similarity index 99% rename from src/Avalonia.OpenGL/EglInterface.cs rename to src/Avalonia.OpenGL/Egl/EglInterface.cs index 666c0d8351..8055226042 100644 --- a/src/Avalonia.OpenGL/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using Avalonia.Platform; using Avalonia.Platform.Interop; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglInterface : GlInterfaceBase { diff --git a/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..476f65a774 --- /dev/null +++ b/src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs @@ -0,0 +1,72 @@ +using System; +using Avalonia.Logging; +using static Avalonia.OpenGL.Egl.EglConsts; + +namespace Avalonia.OpenGL.Egl +{ + public class EglPlatformOpenGlInterface : IPlatformOpenGlInterface + { + public EglDisplay Display { get; private set; } + public bool CanCreateContexts => true; + public bool CanShareContexts => Display.SupportsSharing; + + public EglContext PrimaryEglContext { get; } + public IGlContext PrimaryContext => PrimaryEglContext; + + public EglPlatformOpenGlInterface(EglDisplay display) + { + Display = display; + PrimaryEglContext = display.CreateContext(null); + } + + public static void TryInitialize() + { + var feature = TryCreate(); + if (feature != null) + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + } + + public static EglPlatformOpenGlInterface TryCreate() => TryCreate(() => new EglDisplay()); + public static EglPlatformOpenGlInterface TryCreate(Func displayFactory) + { + try + { + return new EglPlatformOpenGlInterface(displayFactory()); + } + catch(Exception e) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); + return null; + } + } + + public IGlContext CreateContext() => Display.CreateContext(null); + public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext); + + + public EglSurface CreateWindowSurface(IntPtr window) + { + using (PrimaryContext.MakeCurrent()) + { + var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window, + new[] { EGL_NONE, EGL_NONE }); + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreateWindowSurface", Display.EglInterface); + return new EglSurface(Display, PrimaryEglContext, s); + } + } + + public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs) + { + using (PrimaryContext.MakeCurrent()) + { + var s = Display.EglInterface.CreatePbufferFromClientBuffer(Display.Handle, bufferType, handle, + Display.Config, attribs); + + if (s == IntPtr.Zero) + throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", Display.EglInterface); + return new EglSurface(Display, PrimaryEglContext, s); + } + } + } +} diff --git a/src/Avalonia.OpenGL/EglSurface.cs b/src/Avalonia.OpenGL/Egl/EglSurface.cs similarity index 57% rename from src/Avalonia.OpenGL/EglSurface.cs rename to src/Avalonia.OpenGL/Egl/EglSurface.cs index 5ac56a00e3..a93751ca9e 100644 --- a/src/Avalonia.OpenGL/EglSurface.cs +++ b/src/Avalonia.OpenGL/Egl/EglSurface.cs @@ -1,22 +1,25 @@ using System; using System.Runtime.InteropServices; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Egl { public class EglSurface : SafeHandle { private readonly EglDisplay _display; + private readonly EglContext _context; private readonly EglInterface _egl; - public EglSurface(EglDisplay display, EglInterface egl, IntPtr surface) : base(surface, true) + public EglSurface(EglDisplay display, EglContext context, IntPtr surface) : base(surface, true) { _display = display; - _egl = egl; + _context = context; + _egl = display.EglInterface; } protected override bool ReleaseHandle() { - _egl.DestroySurface(_display.Handle, handle); + using (_context.MakeCurrent()) + _egl.DestroySurface(_display.Handle, handle); return true; } diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs deleted file mode 100644 index 7e9383432c..0000000000 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Avalonia.Logging; - -namespace Avalonia.OpenGL -{ - public class EglGlPlatformFeature : IWindowingPlatformGlFeature - { - private EglDisplay _display; - public EglDisplay Display => _display; - public IGlContext CreateContext() - { - return _display.CreateContext(DeferredContext); - } - public EglContext DeferredContext { get; private set; } - public IGlContext MainContext => DeferredContext; - - public static void TryInitialize() - { - var feature = TryCreate(); - if (feature != null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - } - - public static EglGlPlatformFeature TryCreate() => TryCreate(() => new EglDisplay()); - public static EglGlPlatformFeature TryCreate(Func displayFactory) - { - try - { - var disp = displayFactory(); - return new EglGlPlatformFeature - { - _display = disp, - DeferredContext = disp.CreateContext(null) - }; - } - catch(Exception e) - { - Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e); - return null; - } - } - } -} diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs deleted file mode 100644 index 21fadff19e..0000000000 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Threading; - -namespace Avalonia.OpenGL -{ - public class EglGlPlatformSurface : EglGlPlatformSurfaceBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - - public EglGlPlatformSurface(EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base() - { - _display = context.Display; - _context = context; - _info = info; - } - - public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() - { - var glSurface = _display.CreateWindowSurface(_info.Handle); - return new RenderTarget(_display, _context, glSurface, _info); - } - - class RenderTarget : EglPlatformSurfaceRenderTargetBase - { - private readonly EglDisplay _display; - private readonly EglContext _context; - private readonly EglSurface _glSurface; - private readonly IEglWindowGlPlatformSurfaceInfo _info; - private PixelSize _initialSize; - - public RenderTarget(EglDisplay display, EglContext context, - EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(display, context) - { - _display = display; - _context = context; - _glSurface = glSurface; - _info = info; - _initialSize = info.Size; - } - - public override void Dispose() => _glSurface.Dispose(); - - public override bool IsCorrupted => _initialSize != _info.Size; - - public override IGlPlatformSurfaceRenderingSession BeginDraw() => base.BeginDraw(_glSurface, _info); - } - } -} - diff --git a/src/Avalonia.OpenGL/GlInterface.cs b/src/Avalonia.OpenGL/GlInterface.cs index 23188e7dbf..ea2fe0a99c 100644 --- a/src/Avalonia.OpenGL/GlInterface.cs +++ b/src/Avalonia.OpenGL/GlInterface.cs @@ -82,6 +82,9 @@ namespace Avalonia.OpenGL [GlEntryPoint("glFlush")] public Action Flush { get; } + + [GlEntryPoint("glFinish")] + public Action Finish { get; } public delegate IntPtr GlGetString(int v); [GlEntryPoint("glGetString")] @@ -144,6 +147,10 @@ namespace Avalonia.OpenGL [GlEntryPoint("glBindTexture")] public GlBindTexture BindTexture { get; } + public delegate void GlActiveTexture(int texture); + [GlEntryPoint("glActiveTexture")] + public GlActiveTexture ActiveTexture { get; } + public delegate void GlDeleteTextures(int count, int[] textures); [GlEntryPoint("glDeleteTextures")] public GlDeleteTextures DeleteTextures { get; } @@ -154,6 +161,12 @@ namespace Avalonia.OpenGL [GlEntryPoint("glTexImage2D")] public GlTexImage2D TexImage2D { get; } + public delegate void GlCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, + int width, int height); + + [GlEntryPoint("glCopyTexSubImage2D")] + public GlCopyTexSubImage2D CopyTexSubImage2D { get; } + public delegate void GlTexParameteri(int target, int name, int value); [GlEntryPoint("glTexParameteri")] public GlTexParameteri TexParameteri { get; } diff --git a/src/Avalonia.OpenGL/IGlContext.cs b/src/Avalonia.OpenGL/IGlContext.cs index eb4313fba9..50868db873 100644 --- a/src/Avalonia.OpenGL/IGlContext.cs +++ b/src/Avalonia.OpenGL/IGlContext.cs @@ -9,5 +9,7 @@ namespace Avalonia.OpenGL int SampleCount { get; } int StencilSize { get; } IDisposable MakeCurrent(); + IDisposable EnsureCurrent(); + bool IsSharedWith(IGlContext context); } } diff --git a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs index 30f83745ad..fdb9162164 100644 --- a/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs +++ b/src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs @@ -4,6 +4,6 @@ namespace Avalonia.OpenGL { public interface IOpenGlAwarePlatformRenderInterface { - IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap(); + IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); } } diff --git a/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs new file mode 100644 index 0000000000..5ee5df1e85 --- /dev/null +++ b/src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs @@ -0,0 +1,13 @@ +namespace Avalonia.OpenGL +{ + public interface IPlatformOpenGlInterface + { + IGlContext PrimaryContext { get; } + IGlContext CreateSharedContext(); + bool CanShareContexts { get; } + bool CanCreateContexts { get; } + IGlContext CreateContext(); + /*IGlContext TryCreateContext(GlVersion version); + */ + } +} diff --git a/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs b/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs deleted file mode 100644 index b91496f42b..0000000000 --- a/src/Avalonia.OpenGL/IWindowingPlatformGlFeature.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Avalonia.OpenGL -{ - public interface IWindowingPlatformGlFeature - { - IGlContext CreateContext(); - IGlContext MainContext { get; } - } -} diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs new file mode 100644 index 0000000000..aef4f601be --- /dev/null +++ b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +namespace Avalonia.OpenGL.Imaging +{ + public interface IOpenGlBitmapImpl : IBitmapImpl + { + IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback); + bool SupportsContext(IGlContext context); + } + + public interface IOpenGlBitmapAttachment : IDisposable + { + void Present(); + } +} diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs deleted file mode 100644 index e5f3691569..0000000000 --- a/src/Avalonia.OpenGL/Imaging/IOpenGlTextureBitmapImpl.cs +++ /dev/null @@ -1,13 +0,0 @@ -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(); - } -} diff --git a/src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs similarity index 54% rename from src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs rename to src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs index 558eae8fdf..7af44cd624 100644 --- a/src/Avalonia.OpenGL/Imaging/OpenGlTextureBitmap.cs +++ b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs @@ -6,32 +6,30 @@ using Avalonia.Threading; namespace Avalonia.OpenGL.Imaging { - public class OpenGlTextureBitmap : Bitmap, IAffectsRender + public class OpenGlBitmap : Bitmap, IAffectsRender { - private IOpenGlTextureBitmapImpl _impl; - static IOpenGlTextureBitmapImpl CreateOrThrow() + private IOpenGlBitmapImpl _impl; + + public OpenGlBitmap(PixelSize size, Vector dpi) + : base(CreateOrThrow(size, dpi)) { - if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface - glAware)) - throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); - return glAware.CreateOpenGlTextureBitmap(); + _impl = (IOpenGlBitmapImpl)PlatformImpl.Item; } - public OpenGlTextureBitmap() - : base(CreateOrThrow()) + static IOpenGlBitmapImpl CreateOrThrow(PixelSize size, Vector dpi) { - _impl = (IOpenGlTextureBitmapImpl)PlatformImpl.Item; + if (!(AvaloniaLocator.Current.GetService() is IOpenGlAwarePlatformRenderInterface + glAware)) + throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration"); + return glAware.CreateOpenGlBitmap(size, dpi); } - public IDisposable Lock() => _impl.Lock(); + public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) => + _impl.CreateFramebufferAttachment(context, SetIsDirty); - public void SetTexture(int textureId, int internalFormat, PixelSize size, double dpiScaling) - { - _impl.SetBackBuffer(textureId, internalFormat, size, dpiScaling); - SetIsDirty(); - } - - public void SetIsDirty() + public bool SupportsContext(IGlContext context) => _impl.SupportsContext(context); + + void SetIsDirty() { if (Dispatcher.UIThread.CheckAccess()) CallInvalidated(); diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs index d3cd7d059e..196f507ad8 100644 --- a/src/Avalonia.OpenGL/OpenGlException.cs +++ b/src/Avalonia.OpenGL/OpenGlException.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.OpenGL.Egl; namespace Avalonia.OpenGL { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurface.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs similarity index 77% rename from src/Avalonia.OpenGL/IGlPlatformSurface.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs index 22d36b4472..875c215336 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs @@ -1,4 +1,4 @@ -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurface { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs similarity index 89% rename from src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs index d198d46e5c..f89b6f04f5 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderTarget.cs @@ -1,6 +1,6 @@ using System; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurfaceRenderTarget : IDisposable { diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs similarity index 86% rename from src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs rename to src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs index 89911a20a8..da06eab1e7 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs +++ b/src/Avalonia.OpenGL/Surfaces/IGlPlatformSurfaceRenderingSession.cs @@ -1,6 +1,6 @@ using System; -namespace Avalonia.OpenGL +namespace Avalonia.OpenGL.Surfaces { public interface IGlPlatformSurfaceRenderingSession : IDisposable { diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index 0349a6e26e..e9cb88cb8f 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -8,18 +8,21 @@ namespace Avalonia.X11.Glx { public IntPtr Handle { get; } public GlxInterface Glx { get; } + private readonly GlxContext _sharedWith; 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, + public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, + GlxContext sharedWith, GlVersion version, int sampleCount, int stencilSize, X11Info x11, IntPtr defaultXid, bool ownsPBuffer) { Handle = handle; Glx = glx; + _sharedWith = sharedWith; _x11 = x11; _defaultXid = defaultXid; _ownsPBuffer = ownsPBuffer; @@ -37,25 +40,21 @@ namespace Avalonia.X11.Glx public int SampleCount { get; } public int StencilSize { get; } - public IDisposable Lock() - { - Monitor.Enter(_lock); - return Disposable.Create(() => Monitor.Exit(_lock)); - } - class RestoreContext : IDisposable { private GlxInterface _glx; private IntPtr _defaultDisplay; + private readonly object _l; private IntPtr _display; private IntPtr _context; private IntPtr _read; private IntPtr _draw; - public RestoreContext(GlxInterface glx, IntPtr defaultDisplay) + public RestoreContext(GlxInterface glx, IntPtr defaultDisplay, object l) { _glx = glx; _defaultDisplay = defaultDisplay; + _l = l; _display = _glx.GetCurrentDisplay(); _context = _glx.GetCurrentContext(); _read = _glx.GetCurrentReadDrawable(); @@ -66,19 +65,49 @@ namespace Avalonia.X11.Glx { var disp = _display == IntPtr.Zero ? _defaultDisplay : _display; _glx.MakeContextCurrent(disp, _draw, _read, _context); + Monitor.Exit(_l); } } public IDisposable MakeCurrent() => MakeCurrent(_defaultXid); + public IDisposable EnsureCurrent() + { + if(IsCurrent) + return Disposable.Empty; + return MakeCurrent(); + } + + public bool IsSharedWith(IGlContext context) + { + var c = (GlxContext)context; + return c == this + || c._sharedWith == this + || _sharedWith == context + || _sharedWith != null && _sharedWith == c._sharedWith; + } 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; + Monitor.Enter(_lock); + var success = false; + try + { + var old = new RestoreContext(Glx, _x11.Display, _lock); + if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle)) + throw new OpenGlException("glXMakeContextCurrent failed "); + + success = true; + return old; + } + finally + { + if (!success) + Monitor.Exit(_lock); + } } + public bool IsCurrent => Glx.GetCurrentContext() == Handle; + public void Dispose() { Glx.DestroyContext(_x11.Display, Handle); diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index b82895d12c..fa8c866c09 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -113,9 +113,9 @@ namespace Avalonia.X11.Glx } - public GlxContext CreateContext() => CreateContext(DeferredContext); - - GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, + public GlxContext CreateContext() => CreateContext(); + + public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share, share.SampleCount, share.StencilSize, true); GlxContext CreateContext(IntPtr defaultXid, IGlContext share, @@ -144,7 +144,7 @@ namespace Avalonia.X11.Glx if (handle != IntPtr.Zero) { _version = profile; - return new GlxContext(new GlxInterface(), handle, this, profile, + return new GlxContext(new GlxInterface(), handle, this, (GlxContext)share, profile, sampleCount, stencilSize, _x11, defaultXid, ownsPBuffer); } diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs index ae6b0eb353..cb4ab4aca0 100644 --- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs +++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs @@ -1,5 +1,8 @@ using System; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; +using static Avalonia.OpenGL.GlConsts; namespace Avalonia.X11.Glx { @@ -40,33 +43,26 @@ namespace Avalonia.X11.Glx public IGlPlatformSurfaceRenderingSession BeginDraw() { - var l = _context.Lock(); - try - { - - return new Session(_context, _info, l, _context.MakeCurrent(_info.Handle)); - } - catch - { - l.Dispose(); - throw; - } + var oldContext = _context.MakeCurrent(_info.Handle); + + // Reset to default FBO first + _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, 0); + + return new Session(_context, _info, oldContext); } class Session : IGlPlatformSurfaceRenderingSession { private readonly GlxContext _context; private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info; - private IDisposable _lock; private readonly IDisposable _clearContext; public IGlContext Context => _context; public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info, - IDisposable @lock, IDisposable clearContext) + IDisposable clearContext) { _context = context; _info = info; - _lock = @lock; _clearContext = clearContext; } @@ -77,7 +73,6 @@ namespace Avalonia.X11.Glx _context.Display.SwapBuffers(_info.Handle); _context.Glx.WaitX(); _clearContext.Dispose(); - _lock.Dispose(); } public PixelSize Size => _info.Size; diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index ad3a54bcc1..6735a32ffe 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -5,31 +5,34 @@ using Avalonia.OpenGL; namespace Avalonia.X11.Glx { - class GlxGlPlatformFeature : IWindowingPlatformGlFeature + class GlxPlatformOpenGlInterface : IPlatformOpenGlInterface { public GlxDisplay Display { get; private set; } + public bool CanCreateContexts => true; + public bool CanShareContexts => true; public IGlContext CreateContext() => Display.CreateContext(); + public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryContext); public GlxContext DeferredContext { get; private set; } - public IGlContext MainContext => DeferredContext; + public IGlContext PrimaryContext => DeferredContext; public static bool TryInitialize(X11Info x11, IList glProfiles) { var feature = TryCreate(x11, glProfiles); if (feature != null) { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); return true; } return false; } - public static GlxGlPlatformFeature TryCreate(X11Info x11, IList glProfiles) + public static GlxPlatformOpenGlInterface TryCreate(X11Info x11, IList glProfiles) { try { var disp = new GlxDisplay(x11, glProfiles); - return new GlxGlPlatformFeature + return new GlxPlatformOpenGlInterface { Display = disp, DeferredContext = disp.DeferredContext diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index d7bd81db98..c6db146f7b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -7,6 +7,7 @@ using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.X11; @@ -70,9 +71,9 @@ namespace Avalonia.X11 if (options.UseGpu) { if (options.UseEGL) - EglGlPlatformFeature.TryInitialize(); + EglPlatformOpenGlInterface.TryInitialize(); else - GlxGlPlatformFeature.TryInitialize(Info, Options.GlProfiles); + GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0c0b942bcd..2cd3b973d8 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -12,6 +12,7 @@ using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; @@ -65,7 +66,7 @@ namespace Avalonia.X11 _touch = new TouchDevice(); _keyboard = platform.KeyboardDevice; - var glfeature = AvaloniaLocator.Current.GetService(); + var glfeature = AvaloniaLocator.Current.GetService(); XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); @@ -87,13 +88,13 @@ namespace Avalonia.X11 // OpenGL seems to be do weird things to it's current window which breaks resize sometimes _useRenderWindow = glfeature != null; - var glx = glfeature as GlxGlPlatformFeature; + var glx = glfeature as GlxPlatformOpenGlInterface; if (glx != null) visualInfo = *glx.Display.VisualInfo; else if (glfeature == null) visualInfo = _x11.TransparentVisualInfo; - var egl = glfeature as EglGlPlatformFeature; + var egl = glfeature as EglPlatformOpenGlInterface; var visual = IntPtr.Zero; var depth = 24; @@ -168,7 +169,7 @@ namespace Avalonia.X11 if (egl != null) surfaces.Insert(0, - new EglGlPlatformSurface(egl.DeferredContext, + new EglGlPlatformSurface(egl, new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); if (glx != null) surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext, diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index db37e4af0b..8801f71f9a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -32,8 +32,8 @@ namespace Avalonia.LinuxFramebuffer void Initialize() { Threading = new InternalPlatformThreadingInterface(); - if (_fb is IWindowingPlatformGlFeature glFeature) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(glFeature); + if (_fb is IGlOutputBackend gl) + AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); AvaloniaLocator.CurrentMutable .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(60)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 7a5d20fc83..72eed9e543 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -4,6 +4,8 @@ using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform.Interop; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; @@ -11,13 +13,16 @@ using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; namespace Avalonia.LinuxFramebuffer.Output { - public unsafe class DrmOutput : IOutputBackend, IGlPlatformSurface, IWindowingPlatformGlFeature + public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface { private DrmCard _card; private readonly EglGlPlatformSurface _eglPlatformSurface; public PixelSize PixelSize => _mode.Resolution; public double Scaling { get; set; } - public IGlContext MainContext => _deferredContext; + public IGlContext PrimaryContext => _deferredContext; + + private EglPlatformOpenGlInterface _platformGl; + public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl; public DrmOutput(string path = null) { @@ -132,10 +137,9 @@ namespace Avalonia.LinuxFramebuffer.Output if(_gbmTargetSurface == null) throw new InvalidOperationException("Unable to create GBM surface"); - - - _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), 0x31D7, device, null); - _eglSurface = _eglDisplay.CreateWindowSurface(_gbmTargetSurface); + _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null); + _platformGl = new EglPlatformOpenGlInterface(_eglDisplay); + _eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface); EglContext CreateContext(EglContext share) @@ -144,7 +148,7 @@ namespace Avalonia.LinuxFramebuffer.Output GbmBoFlags.GBM_BO_USE_RENDERING); if (offSurf == null) throw new InvalidOperationException("Unable to create 1x1 sized GBM surface"); - return _eglDisplay.CreateContext(share, _eglDisplay.CreateWindowSurface(offSurf)); + return _eglDisplay.CreateContext(share, _platformGl.CreateWindowSurface(offSurf)); } _deferredContext = CreateContext(null); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs new file mode 100644 index 0000000000..7bc73d590c --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs @@ -0,0 +1,9 @@ +using Avalonia.OpenGL; + +namespace Avalonia.LinuxFramebuffer.Output +{ + public interface IGlOutputBackend : IOutputBackend + { + public IPlatformOpenGlInterface PlatformOpenGlInterface { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 987e2c089c..1a7a9b75cf 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -19,6 +19,6 @@ namespace Avalonia.Skia public interface IOpenGlAwareSkiaGpu : ISkiaGpu { - IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap(); + IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 081db5d26a..6df8df9a4c 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -1,6 +1,7 @@ using System; using System.Reactive.Disposables; using Avalonia.OpenGL; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; using SkiaSharp; diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs index 9278de2137..46d42dfdab 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Avalonia.OpenGL; using Avalonia.OpenGL.Imaging; +using Avalonia.OpenGL.Surfaces; using SkiaSharp; namespace Avalonia.Skia @@ -8,10 +9,12 @@ namespace Avalonia.Skia class GlSkiaGpu : IOpenGlAwareSkiaGpu { private GRContext _grContext; + private IGlContext _glContext; - public GlSkiaGpu(IWindowingPlatformGlFeature gl, long? maxResourceBytes) + public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes) { - var context = gl.MainContext; + var context = openGl.PrimaryContext; + _glContext = context; using (context.MakeCurrent()) { using (var iface = context.Version.Type == GlProfileType.OpenGL ? @@ -40,6 +43,6 @@ namespace Avalonia.Skia return null; } - public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() => new OpenGlTextureBitmapImpl(); + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi); } } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs new file mode 100644 index 0000000000..2ebf7c680b --- /dev/null +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Imaging; +using Avalonia.Utilities; +using SkiaSharp; +using static Avalonia.OpenGL.GlConsts; + +namespace Avalonia.Skia +{ + class GlOpenGlBitmapImpl : IOpenGlBitmapImpl, IDrawableBitmapImpl + { + private readonly IGlContext _context; + private readonly object _lock = new object(); + private IGlPresentableOpenGlSurface _surface; + + public GlOpenGlBitmapImpl(IGlContext context, PixelSize pixelSize, Vector dpi) + { + _context = context; + PixelSize = pixelSize; + Dpi = dpi; + } + + public Vector Dpi { get; } + public PixelSize PixelSize { get; } + public int Version { get; private set; } + public void Save(string fileName) => throw new NotSupportedException(); + + public void Save(Stream stream) => throw new NotSupportedException(); + + public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) + { + lock (_lock) + { + if (_surface == null) + return; + using (_surface.Lock()) + { + using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false, + new GRGlTextureInfo( + GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(), + (uint)_surface.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 IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback) + { + if (!SupportsContext(context)) + throw new OpenGlException("Context is not supported for texture sharing"); + return new SharedOpenGlBitmapAttachment(this, context, presentCallback); + } + + public bool SupportsContext(IGlContext context) + { + // TODO: negotiated platform surface sharing + return _context.IsSharedWith(context); + } + + public void Dispose() + { + + } + + internal void Present(IGlPresentableOpenGlSurface surface) + { + lock (_lock) + { + _surface = surface; + } + } + } + + interface IGlPresentableOpenGlSurface : IDisposable + { + int GetTextureId(); + int InternalFormat { get; } + IDisposable Lock(); + } + + class SharedOpenGlBitmapAttachment : IOpenGlBitmapAttachment, IGlPresentableOpenGlSurface + { + private readonly GlOpenGlBitmapImpl _bitmap; + private readonly IGlContext _context; + private readonly Action _presentCallback; + private readonly int _fbo; + private readonly int _texture; + private readonly int _frontBuffer; + private bool _disposed; + private readonly DisposableLock _lock = new DisposableLock(); + + public SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback) + { + _bitmap = bitmap; + _context = context; + _presentCallback = presentCallback; + using (_context.EnsureCurrent()) + { + var glVersion = _context.Version; + InternalFormat = glVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8; + + _context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo); + if (_fbo == 0) + throw new OpenGlException("Current FBO is 0"); + + { + var gl = _context.GlInterface; + + var textures = new int[2]; + gl.GenTextures(2, textures); + _texture = textures[0]; + _frontBuffer = textures[1]; + + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + foreach (var t in textures) + { + gl.BindTexture(GL_TEXTURE_2D, t); + gl.TexImage2D(GL_TEXTURE_2D, 0, + InternalFormat, + _bitmap.PixelSize.Width, _bitmap.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); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + + } + } + } + + public void Present() + { + using (_context.MakeCurrent()) + { + if (_disposed) + throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment)); + + var gl = _context.GlInterface; + + gl.Finish(); + using (Lock()) + { + gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo); + gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture); + gl.GetIntegerv(GL_ACTIVE_TEXTURE, out var oldActive); + + gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); + gl.BindTexture(GL_TEXTURE_2D, _frontBuffer); + gl.ActiveTexture(GL_TEXTURE0); + + gl.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _bitmap.PixelSize.Width, + _bitmap.PixelSize.Height); + + gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo); + gl.BindTexture(GL_TEXTURE_2D, oldTexture); + gl.ActiveTexture(oldActive); + + gl.Finish(); + } + } + + _bitmap.Present(this); + _presentCallback(); + } + + public void Dispose() + { + var gl = _context.GlInterface; + _bitmap.Present(null); + + if(_disposed) + return; + using (_context.MakeCurrent()) + using (Lock()) + { + if(_disposed) + return; + _disposed = true; + gl.DeleteTextures(2, new[] { _texture, _frontBuffer }); + } + } + + int IGlPresentableOpenGlSurface.GetTextureId() + { + return _frontBuffer; + } + + public int InternalFormat { get; } + + public IDisposable Lock() => _lock.Lock(); + } +} diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs deleted file mode 100644 index 8d007e35f3..0000000000 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGlTextureBitmapImpl.cs +++ /dev/null @@ -1,81 +0,0 @@ -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++; - } - } -} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index c4f70df7c0..b9c1cbc673 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -30,7 +30,7 @@ namespace Avalonia.Skia return; } - var gl = AvaloniaLocator.Current.GetService(); + var gl = AvaloniaLocator.Current.GetService(); if (gl != null) _skiaGpu = new GlSkiaGpu(gl, maxResourceBytes); } @@ -256,10 +256,10 @@ namespace Avalonia.Skia } - public IOpenGlTextureBitmapImpl CreateOpenGlTextureBitmap() + public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) { if (_skiaGpu is IOpenGlAwareSkiaGpu glAware) - return glAware.CreateOpenGlTextureBitmap(); + return glAware.CreateOpenGlBitmap(size, dpi); if (_skiaGpu == null) throw new PlatformNotSupportedException("GPU acceleration is not available"); throw new PlatformNotSupportedException( diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index bd188ad53a..fbc56e7703 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -1,26 +1,27 @@ using Avalonia.OpenGL; using Avalonia.OpenGL.Angle; +using Avalonia.OpenGL.Egl; namespace Avalonia.Win32 { static class Win32GlManager { /// This property is initialized if drawing platform requests OpenGL support - public static EglGlPlatformFeature EglFeature { get; private set; } + public static EglPlatformOpenGlInterface EglPlatformInterface { get; private set; } private static bool s_attemptedToInitialize; public static void Initialize() { - AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => + AvaloniaLocator.CurrentMutable.Bind().ToFunc(() => { if (!s_attemptedToInitialize) { - EglFeature = EglGlPlatformFeature.TryCreate(() => new AngleWin32EglDisplay()); + EglPlatformInterface = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); s_attemptedToInitialize = true; } - return EglFeature; + return EglPlatformInterface; }); } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9c6bce1c90..cb85e14e5a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -7,6 +7,8 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Input; @@ -103,8 +105,8 @@ namespace Avalonia.Win32 CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); - if (Win32GlManager.EglFeature != null) - _gl = new EglGlPlatformSurface(Win32GlManager.EglFeature.DeferredContext, this); + if (Win32GlManager.EglPlatformInterface != null) + _gl = new EglGlPlatformSurface(Win32GlManager.EglPlatformInterface, this); Screen = new ScreenImpl(); diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs index 635df43407..f9c787b6a8 100644 --- a/src/iOS/Avalonia.iOS/EaglDisplay.cs +++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs @@ -1,15 +1,18 @@ using System; +using System.Reactive.Disposables; using Avalonia.OpenGL; using OpenGLES; using OpenTK.Graphics.ES30; namespace Avalonia.iOS { - class EaglFeature : IWindowingPlatformGlFeature + class EaglFeature : IPlatformOpenGlInterface { + public IGlContext PrimaryContext => Context; + public IGlContext CreateSharedContext() => throw new NotSupportedException(); + public bool CanShareContexts => false; + public bool CanCreateContexts => false; public IGlContext CreateContext() => throw new System.NotSupportedException(); - - public IGlContext MainContext => Context; public GlContext Context { get; } = new GlContext(); } @@ -61,9 +64,18 @@ namespace Avalonia.iOS return new ResetContext(old); } + public IDisposable EnsureCurrent() + { + if(EAGLContext.CurrentContext == Context) + return Disposable.Empty; + return MakeCurrent(); + } + + public bool IsSharedWith(IGlContext context) => false; + public GlVersion Version { get; } = new GlVersion(GlProfileType.OpenGLES, 3, 0); public GlInterface GlInterface { get; } public int SampleCount { get; } = 0; public int StencilSize { get; } = 9; } -} \ No newline at end of file +} diff --git a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs index 64912b8ae3..5e5e1da949 100644 --- a/src/iOS/Avalonia.iOS/EaglLayerSurface.cs +++ b/src/iOS/Avalonia.iOS/EaglLayerSurface.cs @@ -2,6 +2,7 @@ using System; using System.Threading; using Avalonia.OpenGL; +using Avalonia.OpenGL.Surfaces; using CoreAnimation; using OpenTK.Graphics.ES30; @@ -91,4 +92,4 @@ namespace Avalonia.iOS } } } -} \ No newline at end of file +} diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index b484559ff3..28bccb6637 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -26,7 +26,7 @@ namespace Avalonia.iOS var keyboard = new KeyboardDevice(); var softKeyboard = new SoftKeyboardHelper(); AvaloniaLocator.CurrentMutable - .Bind().ToConstant(GlFeature) + .Bind().ToConstant(GlFeature) .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl())