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
BackbufferBase.cs
1using System.Drawing;
7using SkiaSharp;
8
10
19public abstract class BackbufferBase : IDisposable
20{
21 private int _width;
22 private int _height;
23
24 private BackbufferBase()
25 { }
26
36 protected BackbufferBase(int width, int height)
37 : this()
38 {
39 _width = width;
40 _height = height;
41 }
42
60 public abstract SKCanvas Canvas { get; }
61
83 protected internal abstract SKImage Snapshot();
84
103 protected internal abstract void BeginFrame();
104
130 protected internal abstract void DrawTileFrame(Tile tile, RectangleF destRectScreen);
131
151 protected internal abstract void EndFrame();
152
156 public SKPaint FogPaint { get; set; } = new()
157 {
158 Color = new SKColor(0, 0, 0, 128),
159 IsAntialias = true
160 };
161
165 public SKPaint GridLinePaint { get; set; } = new()
166 {
167 Color = SKColors.White,
168 IsStroke = true,
169 StrokeWidth = 1
170 };
171
175 public SKPaint CollisionBoxPaint { get; set; } = new()
176 {
177 Color = SKColors.Green,
178 IsStroke = true,
179 StrokeWidth = 1
180 };
181
185 public int Width => Volatile.Read(ref _width);
186
190 public int Height => Volatile.Read(ref _height);
191
213 protected internal virtual void RequestResize(int width, int height)
214 { /* no-op by default */ }
215
234 protected internal event Action<int, int>? SizeChanged;
235
252 protected void UpdateSize(int width, int height)
253 {
254 Volatile.Write(ref _width, width);
255 Volatile.Write(ref _height, height);
256
257 SizeChanged?.Invoke(width, height);
258 }
259
264 protected internal Rectangle DirtyRectangle { get; private set; }
265
266 private SKColor _clearColor = SKColors.Black;
267
271 protected readonly SKPaint _fillPaint = new()
272 {
273 IsAntialias = false,
274 BlendMode = SKBlendMode.Src,
275 Style = SKPaintStyle.Fill,
276 FilterQuality = SKFilterQuality.None,
277 };
278
282 public SKColor ClearColor
283 {
284 get => _clearColor;
285 set
286 {
287 _clearColor = value;
288 _fillPaint.Color = value;
289 }
290 }
291
292 internal void ClearRect(Rectangle rectPx)
293 {
294 if (rectPx.IsEmpty)
295 return;
296
297 _fillPaint.Color = ClearColor;
298
299 // Screen-pixel space
300 Canvas.Save();
301 Canvas.ResetMatrix();
302
303 // Rect is expected to be in the current canvas coordinate space.
304 Canvas.DrawRect(rectPx.ToSKRect(), _fillPaint);
305
306 // GL surfaces always present the full backbuffer and do not maintain dirty regions.
308 AddToBackbufferDirtyRectangle(rectPx);
309
310 Canvas.Restore();
311 }
312
313 internal void DrawDrawables(View view, IEnumerable<IDrawable> drawables, Rectangle clipRect)
314 {
315 if (this is RecordingBackbuffer recording &&
316 drawables is IReadOnlyList<IDrawable> ordered &&
317 recording.TryDrawFixedGridDrawables(view, ordered, clipRect))
318 {
319 return;
320 }
321
322 Canvas.Save();
323 Canvas.ClipRect(clipRect.ToSKRect());
324
325 var tiles = new List<(Tile Tile, WrappedDrawable? Instance)>();
326
327 foreach (var entry in drawables)
328 {
329 var instance = entry as WrappedDrawable;
330 var drawable = instance?.Owner ?? entry;
331 using var scope = instance?.Enter(view);
332 if (!drawable.Visible)
333 continue;
334
335 var destRectScreen = drawable.GetDrawLocationScreen(view);
336 if (drawable is Sprite sprite)
337 {
338 destRectScreen = sprite.ApplyJiggleToDestRect(destRectScreen);
339 }
340
341 bool batched = this is RecordingBackbuffer recorder &&
342 instance is null &&
343 drawable.GetType() == typeof(SceneLayerTile) &&
344 drawable is SceneLayerTile layerTile &&
345 recorder.TryQueueTile(layerTile, destRectScreen);
346
347 if (!batched)
348 {
349 (this as RecordingBackbuffer)?.FlushTileBatch();
350 drawable.Draw(this, destRectScreen);
351 }
352
353 // GPU/GL surfaces always present the complete backbuffer. Avoid calculating visual
354 // bounds just to pass them to dirty-region tracking that the GPU path never consumes.
356 {
357 RectangleF visualBoundsScreen = drawable is Sprite drawnSprite
358 ? drawnSprite.GetVisualBoundsScreen(destRectScreen)
359 : destRectScreen;
360
361 AddToBackbufferDirtyRectangle(visualBoundsScreen.ToPixelAlignedRect());
362 }
363
364 if (drawable is Tile tile)
365 tiles.Add((tile, instance));
366 }
367
368 (this as RecordingBackbuffer)?.FlushTileBatch();
369
370 PostDrawTiles(view, tiles);
371
372 Canvas.Restore();
373 }
374
375 private void PostDrawTiles(View view, List<(Tile Tile, WrappedDrawable? Instance)> tiles)
376 {
377 foreach (var entry in tiles)
378 {
379 var tile = entry.Tile;
380 using var scope = entry.Instance?.Enter(view);
381 var layer = tile.SceneLayer;
382 bool drawFog = tile.EnableFog;
383 bool drawGrid = layer.ShowGridLines && tile.Visible && tile.IsPositionFixed;
384 bool drawCollision = layer.ShowCollisionBoxes && tile.Visible && tile.CollisionsEnabled;
385
386 if (!drawFog && !drawGrid && !drawCollision)
387 continue;
388
389 // Orthogonal grid outlines are rectangles. Avoid building two transient point arrays
390 // and performing four point-wise world-to-screen transforms for every fixed tile.
391 // Rectangle conversion uses the same view transform while preserving the grid cell
392 // bounds (includeOverhang: false) used by the legacy polygon path.
393 if (drawGrid
394 && !drawFog
395 && layer.CoordinateSystemType == Gondwana.Drawing.Coordinates.CoordinateSystemTypes.Orthogonal)
396 {
397 var worldRect = layer.CoordinateSystem.GetPixelRangeForTile(tile, includeOverhang: false);
398 var screenRect = view.WorldRectToScreenRect(layer, worldRect);
399 Canvas.DrawRect(screenRect.ToSKRect(), GridLinePaint);
400
401 if (drawCollision)
402 {
403 var colRectScreen = tile.GetCollisionAreaScreen(view).ToSKRect();
404 Canvas.DrawRect(colRectScreen, CollisionBoxPaint);
405 }
406
407 continue;
408 }
409
410 // Polygon geometry is only needed for fog or non-orthogonal grid outlines.
411 if (drawFog || drawGrid)
412 {
413 var worldPts = tile.OutlinePointsWorld;
414 var ptsScreen = new SKPoint[worldPts.Length];
415
416 for (int i = 0; i < worldPts.Length; i++)
417 {
418 var p = worldPts[i];
419 var sp = view.WorldPxToScreenPx(
420 layer,
421 new PointF(p.X, p.Y)
422 );
423
424 ptsScreen[i] = new SKPoint(sp.X, sp.Y);
425 }
426
427 if (drawFog)
428 {
429 using var path = new SKPath();
430 path.AddPoly(ptsScreen, close: true);
431 Canvas.DrawPath(path, FogPaint);
432 }
433
434 if (drawGrid)
435 {
436 var enclosed = new SKPoint[ptsScreen.Length + 1];
437 Array.Copy(ptsScreen, enclosed, ptsScreen.Length);
438 enclosed[^1] = ptsScreen[0];
439 Canvas.DrawPoints(SKPointMode.Polygon, enclosed, GridLinePaint);
440 }
441 }
442
443 if (drawCollision)
444 {
445 var colRectScreen = tile.GetCollisionAreaScreen(view).ToSKRect();
446 Canvas.DrawRect(colRectScreen, CollisionBoxPaint);
447 }
448 }
449 }
450
458 protected internal void AddToBackbufferDirtyRectangle(Rectangle area)
459 {
460 if (IsGlThreadRendered || area.IsEmpty)
461 return;
462
463 area.Inflate(area.Width, area.Height);
464
465 DirtyRectangle = DirtyRectangle.IsEmpty
466 ? area
467 : Rectangle.Union(DirtyRectangle, area);
468 }
469
481 protected internal void ClearDirtyRectangle()
482 {
483 DirtyRectangle = Rectangle.Empty;
484 }
485
496 public virtual byte[] ToByteArray(SKEncodedImageFormat format = SKEncodedImageFormat.Png, int quality = 100)
497 {
498 using var image = Snapshot();
499 using var data = image.Encode(format, quality);
500 return data.ToArray();
501 }
502
513 public virtual bool IsGlThreadRendered => false;
514
529 public virtual void Dispose()
530 {
531 _fillPaint.Dispose();
532 FogPaint.Dispose();
533 GridLinePaint.Dispose();
534 CollisionBoxPaint.Dispose();
535
536 GC.SuppressFinalize(this);
537 }
538}
Represents a movable visual element on a scene layer.
Definition Sprite.cs:20
Represents an abstract base class for drawable tiles in the Gondwana engine. Provides core functional...
Definition Tile.cs:19
BackbufferBase(int width, int height)
Initializes a new instance of the BackbufferBase class with the specified dimensions.
virtual bool IsGlThreadRendered
Gets a value indicating whether this backbuffer is rendered on the GL thread rather than the engine's...
int Width
Gets the current Backbuffer width in a thread-safe manner.
SKPaint CollisionBoxPaint
Gets or sets the paint settings used to render collision boxes.
void UpdateSize(int width, int height)
Updates the stored backbuffer dimensions and raises the SizeChanged event.
SKPaint FogPaint
Gets or sets the paint object used to render fog effects.
SKPaint GridLinePaint
Gets or sets the paint settings used to render grid lines.
virtual byte[] ToByteArray(SKEncodedImageFormat format=SKEncodedImageFormat.Png, int quality=100)
Converts the current image to a byte array in the specified format and quality.
int Height
Gets the current Backbuffer height in a thread-safe manner.
SKColor ClearColor
Gets or sets the color used to clear the drawing surface.
virtual void Dispose()
Releases all resources used by the BackbufferBase instance.
readonly SKPaint _fillPaint
Paint used to fill cleared regions of the backbuffer with the current ClearColor.
SKCanvas Canvas
Gets the SkiaSharp canvas used for drawing operations on this backbuffer.
Represents a rendered view of a scene, combining a camera position with a viewport configuration to c...
Definition View.cs:14
PointF WorldPxToScreenPx(SceneLayer layer, PointF worldPx)
Converts a world-space pixel position into screen-space coordinates for this view,...
Definition View.cs:278
RectangleF WorldRectToScreenRect(SceneLayer layer, RectangleF worldRect)
Converts a world-space pixel rectangle into a screen-space rectangle for this View,...
Definition View.cs:324
Represents the values stored at a single location on a SceneLayer.
@ Rectangle
Particles are spawned uniformly within a rectangular area. This is the default distribution.