DiamondPanel.GemAtlas.cs
using System;
using Sandbox;
namespace Diamonds;
public sealed partial class DiamondPanel
{
// One shared, runtime-only atlas. Panel ownership releases GPU storage when the last
// canvas closes; restarts reuse it. Palette identity also invalidates art after hotload.
static GemAtlas sharedGemAtlas;
GemAtlas gemAtlas;
bool drawingReferenceGems = false;
sealed class GemAtlas : IDisposable
{
public const int TileWidth = 64, TileHeight = 96, Columns = 5, Rows = 9;
public Texture Texture;
public Fill[] Tiles;
public int Users, Resolution, Builds;
Color[] palette;
public void Ensure( int resolution )
{
if ( Texture?.IsValid == true && palette == Palette && Resolution >= resolution ) return;
resolution = Math.Max( resolution, Resolution );
var texture = Sandbox.Texture.CreateRenderTarget()
.WithSize( Columns * TileWidth * resolution, Rows * TileHeight * resolution )
.WithFormat( ImageFormat.RGBA8888 ).Create();
try
{
using ( var p = Painter.Begin( texture ) )
{
p.Clear( Color.Transparent );
p.Scale( resolution );
for ( int row = 0; row < Rows; row++ )
for ( int color = 0; color < Columns; color++ )
{
int health = row == 0 ? 3 : row <= 4 ? 2 : 1;
var corner = row == 0 ? DiamondBoard.Corner.Top : (DiamondBoard.Corner)((row - 1) % 4);
DrawDiamondVector( p, new Vector2( (color + 0.5f) * TileWidth, (row + 0.5f) * TileHeight ),
color, false, health: health, crackCorner: corner );
}
}
var tiles = new Fill[Columns * Rows];
for ( int row = 0; row < Rows; row++ )
for ( int color = 0; color < Columns; color++ )
// The filled rectangle crops the atlas tile without an extra clip or draw.
tiles[row * Columns + color] = Fill.Image( texture,
width: Columns * TileWidth, height: Rows * TileHeight,
offsetX: -color * TileWidth, offsetY: -row * TileHeight );
Texture?.Dispose();
Texture = texture; Tiles = tiles; Resolution = resolution; palette = Palette; Builds++;
}
catch { texture.Dispose(); throw; }
}
public void Dispose()
{
Texture?.Dispose(); Texture = null; Tiles = null; palette = null;
}
}
bool DrawAtlasGem( Painter p, Vector2 center, int color, float scale, int health, DiamondBoard.Corner corner )
{
var transform = p.Transform;
float density = MathF.Max( new Vector2( transform.M11, transform.M12 ).Length,
new Vector2( transform.M21, transform.M22 ).Length ) * scale;
// Bound texture memory; unusually large zooms retain the resolution-independent artwork.
if ( density > 4 ) return false;
if ( gemAtlas is null )
{
gemAtlas = sharedGemAtlas ??= new GemAtlas();
gemAtlas.Users++;
}
gemAtlas.Ensure( Math.Clamp( (int)MathF.Ceiling( density ), 2, 4 ) );
int row = health >= 3 ? 0 : (health == 2 ? 1 : 5) + (int)corner;
using var scope = p.Scope();
p.Translate( center ); p.Scale( scale );
p.Fill = gemAtlas.Tiles[row * GemAtlas.Columns + color];
p.Stroke = Stroke.None;
p.Rect( new Rect( -GemAtlas.TileWidth * 0.5f, -GemAtlas.TileHeight * 0.5f, GemAtlas.TileWidth, GemAtlas.TileHeight ) );
return true;
}
void ReleaseGemAtlas()
{
if ( gemAtlas is null ) return;
if ( --gemAtlas.Users == 0 )
{
gemAtlas.Dispose();
if ( ReferenceEquals( sharedGemAtlas, gemAtlas ) ) sharedGemAtlas = null;
}
gemAtlas = null;
}
}