-
Notifications
You must be signed in to change notification settings - Fork 30
/
RfbRenderTarget.cs
128 lines (109 loc) · 4.4 KB
/
RfbRenderTarget.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System;
using System.Collections.Immutable;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using MarcusW.VncClient.Avalonia.Adapters;
using MarcusW.VncClient.Avalonia.Adapters.Rendering;
using MarcusW.VncClient.Rendering;
using IRenderTarget = MarcusW.VncClient.Rendering.IRenderTarget;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace MarcusW.VncClient.Avalonia
{
/// <summary>
/// A control that provides access to a target framebuffer for rendering frames onto it.
/// </summary>
/// <remarks>
/// This control ignores the VNC multi-screen feature and therefore renders all screens (the whole framebuffer) in one piece.
/// </remarks>
public class RfbRenderTarget : Control, IRenderTarget, IDisposable
{
private volatile WriteableBitmap? _bitmap;
private readonly object _bitmapReplacementLock = new object();
private volatile bool _disposed;
/// <inheritdoc />
public virtual IFramebufferReference GrabFramebufferReference(Size size, IImmutableSet<Screen> layout)
{
if (_disposed)
throw new ObjectDisposedException(nameof(RfbRenderTarget));
PixelSize requiredPixelSize = Conversions.GetPixelSize(size);
// Creation of a new buffer necessary?
// No synchronization necessary, because the only conflicting write operation is
// in this same method and the field is marked volatile to avoid caching issues.
// ReSharper disable once InconsistentlySynchronizedField
bool sizeChanged = _bitmap == null || _bitmap.PixelSize != requiredPixelSize;
WriteableBitmap bitmap;
if (sizeChanged)
{
// Create new bitmap with required size and the format that is preferred by the current platform (therefore 'null').
// TODO: Detect DPI dynamically
bitmap = new WriteableBitmap(requiredPixelSize, new Vector(96.0f, 96.0f), null);
// Wait for the rendering being finished before replacing the bitmap
lock (_bitmapReplacementLock)
{
_bitmap?.Dispose();
_bitmap = bitmap;
}
}
else
{
// ReSharper disable once InconsistentlySynchronizedField
bitmap = _bitmap!;
}
// Lock framebuffer and return as converted reference
// ReSharper disable once InconsistentlySynchronizedField
ILockedFramebuffer lockedFramebuffer = bitmap.Lock();
return new AvaloniaFramebufferReference(lockedFramebuffer, () => Dispatcher.UIThread.Post(() => {
if (sizeChanged)
InvalidateMeasure();
InvalidateVisual();
}));
}
/// <inheritdoc />
public override void Render(DrawingContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
// Ensure the bitmap does not get disposed or replaced during rendering
lock (_bitmapReplacementLock)
{
if (_bitmap == null)
return;
var rect = new Rect(_bitmap.Size);
context.DrawImage(_bitmap, rect);
}
}
/// <inheritdoc />
protected override global::Avalonia.Size MeasureOverride(global::Avalonia.Size availableSize)
{
// Retrieve bitmap size
global::Avalonia.Size? bitmapSize;
lock (_bitmapReplacementLock)
bitmapSize = _bitmap?.Size;
// Has any bitmap been rendered yet?
if (!bitmapSize.HasValue)
return global::Avalonia.Size.Empty;
// Request the size of the current bitmap
return bitmapSize.Value;
}
/// <inheritdoc />
public void Dispose() => Dispose(true);
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
lock (_bitmapReplacementLock)
{
_bitmap?.Dispose();
_bitmap = null;
}
}
_disposed = true;
}
}
}