Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
AvaloniaBitmapRenderSurfaceAdapter.cs
1using System.Collections.Concurrent;
2using Avalonia;
3using Avalonia.Media.Imaging;
4using Avalonia.Platform;
5using Avalonia.Threading;
7using SkiaSharp;
8
10
18{
19 private readonly AvaloniaBitmapRenderSurfaceControl _control;
20 private readonly object _presentSync = new();
21
22 // Owned by the UI thread: created/replaced in SetBitmap on the UI thread.
23 private WriteableBitmap? _bitmap;
24
25 // Current rendered image and accumulated dirty area; guarded by _presentSync.
26 private SKImage? _currentImage;
27 private SKRectI _pendingSourceRect;
28 private bool _blitScheduled;
29
30 // Images queued for disposal; written from any thread (Present), drained on UI thread.
31 private readonly ConcurrentQueue<SKImage> _toDispose = new();
32
33 private bool _disposed;
34
41 : base(Math.Max(1, (int)control.Bounds.Width), Math.Max(1, (int)control.Bounds.Height), initialSizeAvailable: false)
42 {
43 _control = control ?? throw new ArgumentNullException(nameof(control));
44 _control.SizeChanged += OnSizeChanged;
45 }
46
47 private void OnSizeChanged(object? sender, global::Avalonia.Controls.SizeChangedEventArgs e)
48 {
50 }
51
56 {
57 if (_disposed) return;
58 var w = (int)_control.Bounds.Width;
59 var h = (int)_control.Bounds.Height;
60 if (w > 0 && h > 0)
62 }
63
71 public override void Present(SKImage bufferImage, SKRectI bufferRect, SKRect destRect)
72 {
73 var sourceRect = SKRectI.Intersect(
74 bufferRect,
75 new SKRectI(0, 0, bufferImage.Width, bufferImage.Height));
76
77 if (sourceRect.IsEmpty)
78 {
79 bufferImage.Dispose();
80 return;
81 }
82
83 bool scheduleBlit;
84 lock (_presentSync)
85 {
86 if (_disposed)
87 {
88 bufferImage.Dispose();
89 return;
90 }
91
92 var old = _currentImage;
93 _currentImage = bufferImage;
94 if (!ReferenceEquals(old, _currentImage) && old is not null)
95 _toDispose.Enqueue(old);
96
97 _pendingSourceRect = Union(_pendingSourceRect, sourceRect);
98 scheduleBlit = !_blitScheduled;
99 _blitScheduled = true;
100 }
101
102 // Several presents may arrive before the UI thread runs. One scheduled blit copies the
103 // union of their dirty regions from the newest (complete) backbuffer snapshot.
104 if (scheduleBlit)
105 Dispatcher.UIThread.Post(BlitAndInvalidate, DispatcherPriority.Render);
106 }
107
108 private void BlitAndInvalidate()
109 {
110 SKImage? img;
111 SKRectI sourceRect;
112
113 lock (_presentSync)
114 {
115 if (_disposed)
116 {
117 _blitScheduled = false;
118 return;
119 }
120
121 img = _currentImage;
122 sourceRect = _pendingSourceRect;
123 _pendingSourceRect = SKRectI.Empty;
124 _blitScheduled = false;
125 }
126
127 if (img == null) return;
128
129 try
130 {
131 var w = img.Width;
132 var h = img.Height;
133 if (w <= 0 || h <= 0) return;
134
135 var imageBounds = new SKRectI(0, 0, w, h);
136 sourceRect = SKRectI.Intersect(sourceRect, imageBounds);
137 if (sourceRect.IsEmpty) return;
138
139 // A new bitmap has no retained pixels, so its first copy must cover the full image.
140 var bitmap = _bitmap;
141 if (bitmap == null || bitmap.PixelSize.Width != w || bitmap.PixelSize.Height != h)
142 {
143 bitmap?.Dispose();
144 bitmap = new WriteableBitmap(
145 new PixelSize(w, h),
146 new Vector(96, 96),
147 PixelFormat.Bgra8888,
148 AlphaFormat.Premul);
149
150 _bitmap = bitmap;
151 sourceRect = imageBounds;
152 }
153
154 // Copy only the accumulated dirty patch into its matching location in the bitmap.
155 bool copied;
156 using (var fb = bitmap.Lock())
157 {
158 var info = new SKImageInfo(
159 sourceRect.Width,
160 sourceRect.Height,
161 SKColorType.Bgra8888,
162 SKAlphaType.Premul);
163
164 var destinationOffset = checked((sourceRect.Top * fb.RowBytes) + (sourceRect.Left * 4));
165 var destination = IntPtr.Add(fb.Address, destinationOffset);
166
167 copied = img.ReadPixels(
168 info,
169 destination,
170 fb.RowBytes,
171 sourceRect.Left,
172 sourceRect.Top);
173 }
174
175 if (!copied) return;
176
177 _control.SetBitmap(bitmap);
178 _control.InvalidateVisual();
179 }
180 finally
181 {
182 DisposeStaleImages();
183 }
184 }
185
186 private static SKRectI Union(SKRectI left, SKRectI right)
187 {
188 if (left.IsEmpty) return right;
189 if (right.IsEmpty) return left;
190
191 return new SKRectI(
192 Math.Min(left.Left, right.Left),
193 Math.Min(left.Top, right.Top),
194 Math.Max(left.Right, right.Right),
195 Math.Max(left.Bottom, right.Bottom));
196 }
197
198 private void DisposeStaleImages()
199 {
200 while (_toDispose.TryDequeue(out var stale))
201 stale.Dispose();
202 }
203
207 public void Dispose()
208 {
209 SKImage? currentImage;
210 lock (_presentSync)
211 {
212 if (_disposed) return;
213 _disposed = true;
214
215 currentImage = _currentImage;
216 _currentImage = null;
217 _pendingSourceRect = SKRectI.Empty;
218 _blitScheduled = false;
219 }
220
221 _control.SizeChanged -= OnSizeChanged;
222
223 DisposeStaleImages();
224
225 currentImage?.Dispose();
226
227 _bitmap?.Dispose();
228 _bitmap = null;
229
230 GC.SuppressFinalize(this);
231 }
232}
override void Present(SKImage bufferImage, SKRectI bufferRect, SKRect destRect)
Presents the specified buffer image to the render surface by copying pixels into an Avalonia Writeabl...
void Dispose()
Releases all resources used by the AvaloniaBitmapRenderSurfaceAdapter.
AvaloniaBitmapRenderSurfaceAdapter(AvaloniaBitmapRenderSurfaceControl control)
Initializes a new instance of the AvaloniaBitmapRenderSurfaceAdapter class.
void RefreshDestinationSize()
Refreshes the destination size to match the control's current bounds.
An Avalonia Control that displays game frames produced by the Gondwana engine. Frames are received as...
int Width
Gets the current width of the render surface in pixels.
void SetDestinationSize(int destWidth, int destHeight)
Sets the destination size of the render surface and raises the Resized event if the dimensions have c...
RenderSurfaceAdapterBase(int destWidth, int destHeight)
Initializes a new instance of the RenderSurfaceAdapterBase class with the specified dimensions.
int Height
Gets the current height of the render surface in pixels.