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
WinFormBitmapRenderSurfaceAdapter.cs
1using System.Collections.Concurrent;
3using SkiaSharp;
4using SkiaSharp.Views.Desktop;
5
7
12{
13 private readonly SKControl _control;
14 private readonly object _presentSync = new();
15
16 private SKImage? _currentImage;
17 private readonly ConcurrentQueue<SKImage> _toDispose = new();
18 private bool _disposed;
19
25 public WinFormBitmapRenderSurfaceAdapter(SKControl control)
26 : base(Math.Max(1, control.ClientSize.Width), Math.Max(1, control.ClientSize.Height), initialSizeAvailable: control.IsHandleCreated)
27 {
28 _control = control ?? throw new ArgumentNullException(nameof(control));
29
30 _control.PaintSurface += OnPaintSurface;
31 _control.HandleCreated += OnHandleCreated;
32 _control.ClientSizeChanged += OnSizeChanged;
33
34 // If the handle already exists, schedule one initial sync
35 if (_control.IsHandleCreated)
36 _control.BeginInvoke((Action)RefreshDestinationSize);
37 }
38
39 private void OnHandleCreated(object? s, EventArgs e) => RefreshDestinationSize();
40
41 private void OnSizeChanged(object? s, EventArgs e) => RefreshDestinationSize();
42
47 {
48 if (_control.IsDisposed || !_control.IsHandleCreated) return;
49
50 var sz = _control.ClientSize; // ← ClientSize, not Width/Height
51 SetDestinationSize(sz.Width, sz.Height);
52 _control.Invalidate();
53 }
54
61 public override void Present(SKImage bufferImage, SKRectI bufferRect, SKRect destRect)
62 {
63 // No UI target — dispose immediately to avoid leak
64 if (_control.IsDisposed || !_control.IsHandleCreated)
65 {
66 bufferImage.Dispose();
67 return;
68 }
69
70 var sourceRect = SKRectI.Intersect(
71 bufferRect,
72 new SKRectI(0, 0, bufferImage.Width, bufferImage.Height));
73
74 var dirty = global::System.Drawing.Rectangle.FromLTRB(sourceRect.Left, sourceRect.Top, sourceRect.Right, sourceRect.Bottom);
75 // Expand for the adjacent texels sampled by linear filtering, then round physical edges outwards.
76 dirty.Inflate(1, 1);
77 var invalidateRect = global::System.Drawing.Rectangle.Intersect(
78 Presentation.ScreenRectToAdapterRect(dirty), _control.ClientRectangle);
79
80 if (sourceRect == new SKRectI(0, 0, bufferImage.Width, bufferImage.Height))
81 invalidateRect = _control.ClientRectangle;
82
83 if (sourceRect.IsEmpty || invalidateRect.IsEmpty)
84 {
85 bufferImage.Dispose();
86 return;
87 }
88
89 lock (_presentSync)
90 {
91 if (_disposed)
92 {
93 bufferImage.Dispose();
94 return;
95 }
96
97 // Swap into current; old one is disposed after the paint that consumes the newest
98 // complete snapshot. Windows coalesces physical invalidation rectangles.
99 var old = _currentImage;
100 _currentImage = bufferImage;
101 if (!ReferenceEquals(old, _currentImage) && old is not null)
102 _toDispose.Enqueue(old);
103
104 }
105
106 _control.Invalidate(invalidateRect);
107 }
108
109 private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
110 {
111 var canvas = e.Surface.Canvas;
112
113 SKImage? img;
114 lock (_presentSync)
115 {
116 img = _currentImage;
117 }
118
119 try
120 {
121 if (img == null) return;
122
123 // Retain the complete image for OS expose/resize paints with no new dirty patch.
124 DrawImage(canvas, img, SKColors.Black);
125 }
126 finally
127 {
128 DisposeStaleImages();
129 }
130 }
131
132 private void DisposeStaleImages()
133 {
134 while (_toDispose.TryDequeue(out var stale))
135 stale.Dispose();
136 }
137
141 public void Dispose()
142 {
143 SKImage? currentImage;
144 lock (_presentSync)
145 {
146 if (_disposed) return;
147 _disposed = true;
148
149 currentImage = _currentImage;
150 _currentImage = null;
151 }
152
153 if (!_control.IsDisposed)
154 {
155 _control.PaintSurface -= OnPaintSurface;
156 _control.HandleCreated -= OnHandleCreated;
157 _control.ClientSizeChanged -= OnSizeChanged;
158 }
159
160 DisposeStaleImages();
161
162 currentImage?.Dispose();
163 GC.SuppressFinalize(this);
164 }
165}
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...
PresentationTransform Presentation
Current immutable aspect-preserving mapping of Backbuffer ScreenPx to adapter pixels.
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.
void DrawImage(SKCanvas canvas, SKImage image, SKColor clearColor)
Presents a complete image using the same transform as pointer normalization.
void RefreshDestinationSize()
Refreshes the destination size based on the current client size of the control.
override void Present(SKImage bufferImage, SKRectI bufferRect, SKRect destRect)
Presents the specified buffer image to the render surface.
void Dispose()
Releases all resources used by the WinFormBitmapRenderSurfaceAdapter.
WinFormBitmapRenderSurfaceAdapter(SKControl control)
Initializes a new instance of the WinFormBitmapRenderSurfaceAdapter class.