Support rendering at non-integer display scales:

* 2x and 3x DPI artwork can be specified using
  Image2x and Image3x in chrome.yaml.
* Images are rendered using bilinear interpolation.
* For non-integer screen scales, prefer downscaling
  the next biggest resolution image over upscaling.
This commit is contained in:
Paul Chote
2019-12-25 18:49:47 +00:00
committed by abcdefg30
parent 809b1507a6
commit fd64ad7c89
15 changed files with 130 additions and 17 deletions

View File

@@ -44,6 +44,9 @@ namespace OpenRA.Graphics
public class Collection
{
public readonly string Image = null;
public readonly string Image2x = null;
public readonly string Image3x = null;
public readonly int[] PanelRegion = null;
public readonly PanelSides PanelSides = PanelSides.All;
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
@@ -54,18 +57,25 @@ namespace OpenRA.Graphics
static Dictionary<string, Sheet> cachedSheets;
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
static Dictionary<string, Sprite[]> cachedPanelSprites;
static Dictionary<Collection, Sheet> cachedCollectionSheets;
static IReadOnlyFileSystem fileSystem;
static float dpiScale = 1;
public static void Initialize(ModData modData)
{
Deinitialize();
// Load higher resolution images if available on HiDPI displays
if (Game.Renderer != null)
dpiScale = Game.Renderer.WindowScale;
fileSystem = modData.DefaultFileSystem;
collections = new Dictionary<string, Collection>();
cachedSheets = new Dictionary<string, Sheet>();
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
cachedPanelSprites = new Dictionary<string, Sprite[]>();
cachedCollectionSheets = new Dictionary<Collection, Sheet>();
Collections = new ReadOnlyDictionary<string, Collection>(collections);
@@ -87,6 +97,7 @@ namespace OpenRA.Graphics
cachedSheets = null;
cachedSprites = null;
cachedPanelSprites = null;
cachedCollectionSheets = null;
}
static void LoadCollection(string name, MiniYaml yaml)
@@ -99,16 +110,42 @@ namespace OpenRA.Graphics
static Sheet SheetForCollection(Collection c)
{
// Cached sheet
Sheet sheet;
if (cachedSheets.ContainsKey(c.Image))
sheet = cachedSheets[c.Image];
else
{
using (var stream = fileSystem.Open(c.Image))
sheet = new Sheet(SheetType.BGRA, stream);
cachedSheets.Add(c.Image, sheet);
// Outer cache avoids recalculating image names
if (!cachedCollectionSheets.TryGetValue(c, out sheet))
{
string sheetImage;
float sheetScale;
if (dpiScale > 2 && !string.IsNullOrEmpty(c.Image3x))
{
sheetImage = c.Image3x;
sheetScale = 3;
}
else if (dpiScale > 1 && !string.IsNullOrEmpty(c.Image2x))
{
sheetImage = c.Image2x;
sheetScale = 2;
}
else
{
sheetImage = c.Image;
sheetScale = 1;
}
// Inner cache makes sure we share sheets between collections
if (!cachedSheets.TryGetValue(sheetImage, out sheet))
{
using (var stream = fileSystem.Open(sheetImage))
sheet = new Sheet(SheetType.BGRA, stream);
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
sheet.DPIScale = sheetScale;
cachedSheets.Add(sheetImage, sheet);
}
cachedCollectionSheets.Add(c, sheet);
}
return sheet;
@@ -239,5 +276,23 @@ namespace OpenRA.Graphics
var pr = collection.PanelRegion;
return new Size(pr[2] + pr[6], pr[3] + pr[7]);
}
public static void SetDPIScale(float scale)
{
if (dpiScale == scale)
return;
dpiScale = scale;
// Clear the sprite caches so the new artwork can be loaded
// Sheets are not cleared: we assume that the extra memory overhead
// of having the same sheet in memory in multiple DPIs is better than
// the overhead of having to dispose and reload everything.
// Changing the DPI scale is rare, but if it does happen then there
// is a reasonable chance that it may happen again this session.
cachedSprites.Clear();
cachedPanelSprites.Clear();
cachedCollectionSheets.Clear();
}
}
}

View File

@@ -25,6 +25,7 @@ namespace OpenRA.Graphics
public readonly Size Size;
public readonly SheetType Type;
public float DPIScale = 1f;
public byte[] GetData()
{

View File

@@ -41,10 +41,10 @@ namespace OpenRA.Graphics
FractionalOffset = Size.Z != 0 ? offset / Size :
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
Left = (float)Math.Min(bounds.Left, bounds.Right) * sheet.DPIScale / sheet.Size.Width;
Top = (float)Math.Min(bounds.Top, bounds.Bottom) * sheet.DPIScale / sheet.Size.Height;
Right = (float)Math.Max(bounds.Left, bounds.Right) * sheet.DPIScale / sheet.Size.Width;
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) * sheet.DPIScale / sheet.Size.Height;
}
}
@@ -61,10 +61,10 @@ namespace OpenRA.Graphics
SecondarySheet = secondarySheet;
SecondaryBounds = secondaryBounds;
SecondaryChannel = secondaryChannel;
SecondaryLeft = (float)Math.Min(secondaryBounds.Left, secondaryBounds.Right) / s.Sheet.Size.Width;
SecondaryTop = (float)Math.Min(secondaryBounds.Top, secondaryBounds.Bottom) / s.Sheet.Size.Height;
SecondaryRight = (float)Math.Max(secondaryBounds.Left, secondaryBounds.Right) / s.Sheet.Size.Width;
SecondaryBottom = (float)Math.Max(secondaryBounds.Top, secondaryBounds.Bottom) / s.Sheet.Size.Height;
SecondaryLeft = (float)Math.Min(secondaryBounds.Left, secondaryBounds.Right) * secondarySheet.DPIScale / s.Sheet.Size.Width;
SecondaryTop = (float)Math.Min(secondaryBounds.Top, secondaryBounds.Bottom) * secondarySheet.DPIScale / s.Sheet.Size.Height;
SecondaryRight = (float)Math.Max(secondaryBounds.Left, secondaryBounds.Right) * secondarySheet.DPIScale / s.Sheet.Size.Width;
SecondaryBottom = (float)Math.Max(secondaryBounds.Top, secondaryBounds.Bottom) * secondarySheet.DPIScale / s.Sheet.Size.Height;
}
}

View File

@@ -57,6 +57,7 @@ namespace OpenRA
if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite))
{
sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24));
sprite.Sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
Action<DownloadDataCompletedEventArgs> onComplete = i =>
{

View File

@@ -110,6 +110,8 @@ namespace OpenRA
{
Game.RunAfterTick(() =>
{
ChromeProvider.SetDPIScale(after);
foreach (var f in Fonts)
f.Value.SetScale(after);
});