Browse Source

[Win32] Fix GL-rendering jitter on resize

win32-jitter-hack
Nikita Tsukanov 7 years ago
parent
commit
eb07c4b80b
  1. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  2. 6
      samples/ControlCatalog.NetCore/Program.cs
  3. 1
      src/Avalonia.OpenGL/EglDisplay.cs
  4. 33
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  5. 12
      src/Avalonia.OpenGL/EglInterface.cs
  6. 5
      src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs
  7. 5
      src/Avalonia.Visuals/Platform/IRenderTarget.cs
  8. 5
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  9. 14
      src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs
  10. 32
      src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs
  11. 4
      src/Skia/Avalonia.Skia/GlRenderTarget.cs
  12. 28
      src/Windows/Avalonia.Win32/WindowImpl.cs

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -11,6 +11,7 @@
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001"/>
</ItemGroup> </ItemGroup>

6
samples/ControlCatalog.NetCore/Program.cs

@ -47,7 +47,11 @@ namespace ControlCatalog.NetCore
=> AppBuilder.Configure<App>() => AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.With(new X11PlatformOptions {EnableMultiTouch = true}) .With(new X11PlatformOptions {EnableMultiTouch = true})
.With(new Win32PlatformOptions {EnableMultitouch = true}) .With(new Win32PlatformOptions
{
EnableMultitouch = true,
AllowEglInitialization = true
})
.UseSkia() .UseSkia()
.UseReactiveUI(); .UseReactiveUI();

1
src/Avalonia.OpenGL/EglDisplay.cs

@ -97,6 +97,7 @@ namespace Avalonia.OpenGL
public GlDisplayType Type { get; } public GlDisplayType Type { get; }
public GlInterface GlInterface { get; } public GlInterface GlInterface { get; }
public EglInterface EglInterface => _egl;
public IGlContext CreateContext(IGlContext share) public IGlContext CreateContext(IGlContext share)
{ {
var shareCtx = (EglContext)share; var shareCtx = (EglContext)share;

33
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -26,31 +26,44 @@ namespace Avalonia.OpenGL
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{ {
var glSurface = _display.CreateWindowSurface(_info.Handle); var glSurface = _display.CreateWindowSurface(_info.Handle);
return new RenderTarget(_context, glSurface, _info); return new RenderTarget(_display, _context, glSurface, _info);
} }
class RenderTarget : IGlPlatformSurfaceRenderTarget class RenderTarget : IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{ {
private readonly EglDisplay _display;
private readonly EglContext _context; private readonly EglContext _context;
private readonly EglSurface _glSurface; private readonly EglSurface _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info; private readonly IEglWindowGlPlatformSurfaceInfo _info;
private PixelSize _initialSize;
public RenderTarget(EglContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) public RenderTarget(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info)
{ {
_display = display;
_context = context; _context = context;
_glSurface = glSurface; _glSurface = glSurface;
_info = info; _info = info;
_initialSize = info.Size;
} }
public void Dispose() => _glSurface.Dispose(); public void Dispose() => _glSurface.Dispose();
public bool IsCorrupted => _initialSize != _info.Size;
public IGlPlatformSurfaceRenderingSession BeginDraw() public IGlPlatformSurfaceRenderingSession BeginDraw()
{ {
var l = _context.Lock(); var l = _context.Lock();
try try
{ {
if (IsCorrupted)
throw new RenderTargetCorruptedException();
_context.MakeCurrent(_glSurface); _context.MakeCurrent(_glSurface);
return new Session(_context, _glSurface, _info, l); _display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
return new Session(_display, _context, _glSurface, _info, l);
} }
catch catch
{ {
@ -61,15 +74,19 @@ namespace Avalonia.OpenGL
class Session : IGlPlatformSurfaceRenderingSession class Session : IGlPlatformSurfaceRenderingSession
{ {
private readonly IGlContext _context; private readonly EglContext _context;
private readonly EglSurface _glSurface; private readonly EglSurface _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info; private readonly IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private IDisposable _lock; private IDisposable _lock;
public Session(IGlContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock) IDisposable @lock)
{ {
_context = context; _context = context;
_display = display;
_glSurface = glSurface; _glSurface = glSurface;
_info = info; _info = info;
_lock = @lock; _lock = @lock;
@ -78,7 +95,11 @@ namespace Avalonia.OpenGL
public void Dispose() public void Dispose()
{ {
_context.Display.GlInterface.Flush(); _context.Display.GlInterface.Flush();
_display.EglInterface.WaitGL();
_glSurface.SwapBuffers(); _glSurface.SwapBuffers();
_display.EglInterface.WaitClient();
_display.EglInterface.WaitGL();
_display.EglInterface.WaitNative();
_context.Display.ClearContext(); _context.Display.ClearContext();
_lock.Dispose(); _lock.Dispose();
} }

12
src/Avalonia.OpenGL/EglInterface.cs

@ -107,6 +107,18 @@ namespace Avalonia.OpenGL
[GlEntryPoint("eglGetConfigAttrib")] [GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; } public EglGetConfigAttrib GetConfigAttrib { get; }
public delegate bool EglWaitGL();
[GlEntryPoint("eglWaitGL")]
public EglWaitGL WaitGL { get; }
public delegate bool EglWaitClient();
[GlEntryPoint("eglWaitClient")]
public EglWaitGL WaitClient { get; }
public delegate bool EglWaitNative();
[GlEntryPoint("eglWaitNative")]
public EglWaitGL WaitNative { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty // ReSharper restore UnassignedGetOnlyAutoProperty
} }
} }

5
src/Avalonia.OpenGL/IGlPlatformSurfaceRenderTarget.cs

@ -6,4 +6,9 @@ namespace Avalonia.OpenGL
{ {
IGlPlatformSurfaceRenderingSession BeginDraw(); IGlPlatformSurfaceRenderingSession BeginDraw();
} }
public interface IGlPlatformSurfaceRenderTargetWithCorruptionInfo : IGlPlatformSurfaceRenderTarget
{
bool IsCorrupted { get; }
}
} }

5
src/Avalonia.Visuals/Platform/IRenderTarget.cs

@ -23,4 +23,9 @@ namespace Avalonia.Platform
/// </param> /// </param>
IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer); IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer);
} }
public interface IRenderTargetWithCorruptionInfo : IRenderTarget
{
bool IsCorrupted { get; }
}
} }

5
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -245,6 +245,11 @@ namespace Avalonia.Rendering
{ {
if (context != null) if (context != null)
return context; return context;
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
RenderTarget.Dispose();
RenderTarget = null;
}
if (RenderTarget == null) if (RenderTarget == null)
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
return context = RenderTarget.CreateDrawingContext(this); return context = RenderTarget.CreateDrawingContext(this);

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

@ -7,11 +7,25 @@ namespace Avalonia.Rendering
public class ManagedDeferredRendererLock : IDeferredRendererLock public class ManagedDeferredRendererLock : IDeferredRendererLock
{ {
private readonly object _lock = new object(); private readonly object _lock = new object();
/// <summary>
/// Tries to lock the target surface or window
/// </summary>
/// <returns>IDisposable if succeeded to obtain the lock</returns>
public IDisposable TryLock() public IDisposable TryLock()
{ {
if (Monitor.TryEnter(_lock)) if (Monitor.TryEnter(_lock))
return Disposable.Create(() => Monitor.Exit(_lock)); return Disposable.Create(() => Monitor.Exit(_lock));
return null; return null;
} }
/// <summary>
/// Enters a waiting lock, only use from platform code, not from the renderer
/// </summary>
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
}
} }
} }

32
src/Avalonia.Visuals/Rendering/UiThreadRenderTimer.cs

@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.Reactive.Disposables;
using Avalonia.Threading;
namespace Avalonia.Rendering
{
/// <summary>
/// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms
/// </summary>
public class UiThreadRenderTimer : DefaultRenderTimer
{
public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
{
}
protected override IDisposable StartCore(Action<TimeSpan> tick)
{
bool cancelled = false;
var st = Stopwatch.StartNew();
DispatcherTimer.Run(() =>
{
if (cancelled)
return false;
tick(st.Elapsed);
return !cancelled;
}, TimeSpan.FromSeconds(1.0 / FramesPerSecond), DispatcherPriority.Render);
return Disposable.Create(() => cancelled = true);
}
}
}

4
src/Skia/Avalonia.Skia/GlRenderTarget.cs

@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
internal class GlRenderTarget : IRenderTarget internal class GlRenderTarget : IRenderTargetWithCorruptionInfo
{ {
private readonly GRContext _grContext; private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface; private IGlPlatformSurfaceRenderTarget _surface;
@ -21,6 +21,8 @@ namespace Avalonia.Skia
public void Dispose() => _surface.Dispose(); public void Dispose() => _surface.Dispose();
public bool IsCorrupted => (_surface as IGlPlatformSurfaceRenderTargetWithCorruptionInfo)?.IsCorrupted == true;
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
var session = _surface.BeginDraw(); var session = _surface.BeginDraw();

28
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -33,6 +33,7 @@ namespace Avalonia.Win32
private bool _multitouch; private bool _multitouch;
private TouchDevice _touchDevice = new TouchDevice(); private TouchDevice _touchDevice = new TouchDevice();
private IInputRoot _owner; private IInputRoot _owner;
private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock();
private bool _trackingMouse; private bool _trackingMouse;
private bool _decorated = true; private bool _decorated = true;
private bool _resizable = true; private bool _resizable = true;
@ -150,7 +151,9 @@ namespace Avalonia.Win32
if (customRendererFactory != null) if (customRendererFactory != null)
return customRendererFactory.Create(root, loop); return customRendererFactory.Create(root, loop);
return Win32Platform.UseDeferredRendering ? (IRenderer)new DeferredRenderer(root, loop) : new ImmediateRenderer(root); return Win32Platform.UseDeferredRendering ?
(IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock) :
new ImmediateRenderer(root);
} }
public void Resize(Size value) public void Resize(Size value)
@ -667,18 +670,26 @@ namespace Avalonia.Win32
break; break;
case UnmanagedMethods.WindowsMessage.WM_PAINT: case UnmanagedMethods.WindowsMessage.WM_PAINT:
UnmanagedMethods.PAINTSTRUCT ps; using (_rendererLock.Lock())
if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
{ {
var f = Scaling; UnmanagedMethods.PAINTSTRUCT ps;
var r = ps.rcPaint; if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, (r.bottom - r.top) / f)); {
UnmanagedMethods.EndPaint(_hwnd, ref ps); var f = Scaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
UnmanagedMethods.EndPaint(_hwnd, ref ps);
}
} }
return IntPtr.Zero; return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_SIZE: case UnmanagedMethods.WindowsMessage.WM_SIZE:
using (_rendererLock.Lock())
{
// Do nothing here, just block until the pending frame render is completed on the render thread
}
var size = (UnmanagedMethods.SizeCommand)wParam; var size = (UnmanagedMethods.SizeCommand)wParam;
if (Resized != null && if (Resized != null &&
@ -744,7 +755,8 @@ namespace Avalonia.Win32
} }
} }
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); using (_rendererLock.Lock())
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
} }
static InputModifiers GetMouseModifiers(IntPtr wParam) static InputModifiers GetMouseModifiers(IntPtr wParam)

Loading…
Cancel
Save