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:
@@ -44,6 +44,9 @@ namespace OpenRA.Graphics
|
|||||||
public class Collection
|
public class Collection
|
||||||
{
|
{
|
||||||
public readonly string Image = null;
|
public readonly string Image = null;
|
||||||
|
public readonly string Image2x = null;
|
||||||
|
public readonly string Image3x = null;
|
||||||
|
|
||||||
public readonly int[] PanelRegion = null;
|
public readonly int[] PanelRegion = null;
|
||||||
public readonly PanelSides PanelSides = PanelSides.All;
|
public readonly PanelSides PanelSides = PanelSides.All;
|
||||||
public readonly Dictionary<string, Rectangle> Regions = new Dictionary<string, Rectangle>();
|
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, Sheet> cachedSheets;
|
||||||
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
static Dictionary<string, Dictionary<string, Sprite>> cachedSprites;
|
||||||
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
static Dictionary<string, Sprite[]> cachedPanelSprites;
|
||||||
|
static Dictionary<Collection, Sheet> cachedCollectionSheets;
|
||||||
|
|
||||||
static IReadOnlyFileSystem fileSystem;
|
static IReadOnlyFileSystem fileSystem;
|
||||||
|
static float dpiScale = 1;
|
||||||
|
|
||||||
public static void Initialize(ModData modData)
|
public static void Initialize(ModData modData)
|
||||||
{
|
{
|
||||||
Deinitialize();
|
Deinitialize();
|
||||||
|
|
||||||
|
// Load higher resolution images if available on HiDPI displays
|
||||||
|
if (Game.Renderer != null)
|
||||||
|
dpiScale = Game.Renderer.WindowScale;
|
||||||
|
|
||||||
fileSystem = modData.DefaultFileSystem;
|
fileSystem = modData.DefaultFileSystem;
|
||||||
collections = new Dictionary<string, Collection>();
|
collections = new Dictionary<string, Collection>();
|
||||||
cachedSheets = new Dictionary<string, Sheet>();
|
cachedSheets = new Dictionary<string, Sheet>();
|
||||||
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
cachedSprites = new Dictionary<string, Dictionary<string, Sprite>>();
|
||||||
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
cachedPanelSprites = new Dictionary<string, Sprite[]>();
|
||||||
|
cachedCollectionSheets = new Dictionary<Collection, Sheet>();
|
||||||
|
|
||||||
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
Collections = new ReadOnlyDictionary<string, Collection>(collections);
|
||||||
|
|
||||||
@@ -87,6 +97,7 @@ namespace OpenRA.Graphics
|
|||||||
cachedSheets = null;
|
cachedSheets = null;
|
||||||
cachedSprites = null;
|
cachedSprites = null;
|
||||||
cachedPanelSprites = null;
|
cachedPanelSprites = null;
|
||||||
|
cachedCollectionSheets = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadCollection(string name, MiniYaml yaml)
|
static void LoadCollection(string name, MiniYaml yaml)
|
||||||
@@ -99,16 +110,42 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
static Sheet SheetForCollection(Collection c)
|
static Sheet SheetForCollection(Collection c)
|
||||||
{
|
{
|
||||||
// Cached sheet
|
|
||||||
Sheet sheet;
|
Sheet sheet;
|
||||||
if (cachedSheets.ContainsKey(c.Image))
|
|
||||||
sheet = cachedSheets[c.Image];
|
// 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
|
else
|
||||||
{
|
{
|
||||||
using (var stream = fileSystem.Open(c.Image))
|
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 = new Sheet(SheetType.BGRA, stream);
|
||||||
|
|
||||||
cachedSheets.Add(c.Image, sheet);
|
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||||
|
sheet.DPIScale = sheetScale;
|
||||||
|
|
||||||
|
cachedSheets.Add(sheetImage, sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedCollectionSheets.Add(c, sheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sheet;
|
return sheet;
|
||||||
@@ -239,5 +276,23 @@ namespace OpenRA.Graphics
|
|||||||
var pr = collection.PanelRegion;
|
var pr = collection.PanelRegion;
|
||||||
return new Size(pr[2] + pr[6], pr[3] + pr[7]);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace OpenRA.Graphics
|
|||||||
|
|
||||||
public readonly Size Size;
|
public readonly Size Size;
|
||||||
public readonly SheetType Type;
|
public readonly SheetType Type;
|
||||||
|
public float DPIScale = 1f;
|
||||||
|
|
||||||
public byte[] GetData()
|
public byte[] GetData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ namespace OpenRA.Graphics
|
|||||||
FractionalOffset = Size.Z != 0 ? offset / Size :
|
FractionalOffset = Size.Z != 0 ? offset / Size :
|
||||||
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
|
new float3(offset.X / Size.X, offset.Y / Size.Y, 0);
|
||||||
|
|
||||||
Left = (float)Math.Min(bounds.Left, bounds.Right) / sheet.Size.Width;
|
Left = (float)Math.Min(bounds.Left, bounds.Right) * sheet.DPIScale / sheet.Size.Width;
|
||||||
Top = (float)Math.Min(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
Top = (float)Math.Min(bounds.Top, bounds.Bottom) * sheet.DPIScale / sheet.Size.Height;
|
||||||
Right = (float)Math.Max(bounds.Left, bounds.Right) / sheet.Size.Width;
|
Right = (float)Math.Max(bounds.Left, bounds.Right) * sheet.DPIScale / sheet.Size.Width;
|
||||||
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) / sheet.Size.Height;
|
Bottom = (float)Math.Max(bounds.Top, bounds.Bottom) * sheet.DPIScale / sheet.Size.Height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +61,10 @@ namespace OpenRA.Graphics
|
|||||||
SecondarySheet = secondarySheet;
|
SecondarySheet = secondarySheet;
|
||||||
SecondaryBounds = secondaryBounds;
|
SecondaryBounds = secondaryBounds;
|
||||||
SecondaryChannel = secondaryChannel;
|
SecondaryChannel = secondaryChannel;
|
||||||
SecondaryLeft = (float)Math.Min(secondaryBounds.Left, secondaryBounds.Right) / s.Sheet.Size.Width;
|
SecondaryLeft = (float)Math.Min(secondaryBounds.Left, secondaryBounds.Right) * secondarySheet.DPIScale / s.Sheet.Size.Width;
|
||||||
SecondaryTop = (float)Math.Min(secondaryBounds.Top, secondaryBounds.Bottom) / s.Sheet.Size.Height;
|
SecondaryTop = (float)Math.Min(secondaryBounds.Top, secondaryBounds.Bottom) * secondarySheet.DPIScale / s.Sheet.Size.Height;
|
||||||
SecondaryRight = (float)Math.Max(secondaryBounds.Left, secondaryBounds.Right) / s.Sheet.Size.Width;
|
SecondaryRight = (float)Math.Max(secondaryBounds.Left, secondaryBounds.Right) * secondarySheet.DPIScale / s.Sheet.Size.Width;
|
||||||
SecondaryBottom = (float)Math.Max(secondaryBounds.Top, secondaryBounds.Bottom) / s.Sheet.Size.Height;
|
SecondaryBottom = (float)Math.Max(secondaryBounds.Top, secondaryBounds.Bottom) * secondarySheet.DPIScale / s.Sheet.Size.Height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace OpenRA
|
|||||||
if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite))
|
if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite))
|
||||||
{
|
{
|
||||||
sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24));
|
sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24));
|
||||||
|
sprite.Sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||||
|
|
||||||
Action<DownloadDataCompletedEventArgs> onComplete = i =>
|
Action<DownloadDataCompletedEventArgs> onComplete = i =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
Game.RunAfterTick(() =>
|
Game.RunAfterTick(() =>
|
||||||
{
|
{
|
||||||
|
ChromeProvider.SetDPIScale(after);
|
||||||
|
|
||||||
foreach (var f in Fonts)
|
foreach (var f in Fonts)
|
||||||
f.Value.SetScale(after);
|
f.Value.SetScale(after);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common.LoadScreens
|
|||||||
Stopwatch lastUpdate;
|
Stopwatch lastUpdate;
|
||||||
|
|
||||||
protected Dictionary<string, string> Info { get; private set; }
|
protected Dictionary<string, string> Info { get; private set; }
|
||||||
|
float dpiScale = 1;
|
||||||
Sheet sheet;
|
Sheet sheet;
|
||||||
|
|
||||||
public override void Init(ModData modData, Dictionary<string, string> info)
|
public override void Init(ModData modData, Dictionary<string, string> info)
|
||||||
@@ -40,9 +41,42 @@ namespace OpenRA.Mods.Common.LoadScreens
|
|||||||
if (lastUpdate == null)
|
if (lastUpdate == null)
|
||||||
lastUpdate = Stopwatch.StartNew();
|
lastUpdate = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
// Check for window DPI changes
|
||||||
|
// We can't trust notifications to be working during initialization, so must do this manually
|
||||||
|
var scale = Game.Renderer.WindowScale;
|
||||||
|
if (dpiScale != scale)
|
||||||
|
{
|
||||||
|
dpiScale = scale;
|
||||||
|
|
||||||
|
// Force images to be reloaded on the next display
|
||||||
|
if (sheet != null)
|
||||||
|
sheet.Dispose();
|
||||||
|
|
||||||
|
sheet = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (sheet == null && Info.ContainsKey("Image"))
|
if (sheet == null && Info.ContainsKey("Image"))
|
||||||
using (var stream = ModData.DefaultFileSystem.Open(Info["Image"]))
|
{
|
||||||
|
var key = "Image";
|
||||||
|
float sheetScale = 1;
|
||||||
|
if (dpiScale > 2 && Info.ContainsKey("Image3x"))
|
||||||
|
{
|
||||||
|
key = "Image3x";
|
||||||
|
sheetScale = 3;
|
||||||
|
}
|
||||||
|
else if (dpiScale > 1 && Info.ContainsKey("Image2x"))
|
||||||
|
{
|
||||||
|
key = "Image2x";
|
||||||
|
sheetScale = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = ModData.DefaultFileSystem.Open(Info[key]))
|
||||||
|
{
|
||||||
sheet = new Sheet(SheetType.BGRA, stream);
|
sheet = new Sheet(SheetType.BGRA, stream);
|
||||||
|
sheet.GetTexture().ScaleFilter = TextureScaleFilter.Linear;
|
||||||
|
sheet.DPIScale = sheetScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Game.Renderer.BeginUI();
|
Game.Renderer.BeginUI();
|
||||||
DisplayInner(Game.Renderer, sheet);
|
DisplayInner(Game.Renderer, sheet);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
^Chrome:
|
^Chrome:
|
||||||
Image: chrome.png
|
Image: chrome.png
|
||||||
|
Image2x: chrome-2x.png
|
||||||
|
Image3x: chrome-3x.png
|
||||||
|
|
||||||
logos:
|
logos:
|
||||||
Inherits: ^Chrome
|
Inherits: ^Chrome
|
||||||
|
|||||||
@@ -153,6 +153,8 @@ Hotkeys:
|
|||||||
|
|
||||||
LoadScreen: CncLoadScreen
|
LoadScreen: CncLoadScreen
|
||||||
Image: cnc|uibits/chrome.png
|
Image: cnc|uibits/chrome.png
|
||||||
|
Image2x: cnc|uibits/chrome-2x.png
|
||||||
|
Image3x: cnc|uibits/chrome-3x.png
|
||||||
Text: Loading
|
Text: Loading
|
||||||
|
|
||||||
ServerTraits:
|
ServerTraits:
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
^Glyphs:
|
^Glyphs:
|
||||||
Image: glyphs.png
|
Image: glyphs.png
|
||||||
|
Image2x: glyphs-2x.png
|
||||||
|
Image3x: glyphs-3x.png
|
||||||
|
|
||||||
^LoadScreen:
|
^LoadScreen:
|
||||||
Image: loadscreen.png
|
Image: loadscreen.png
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ Hotkeys:
|
|||||||
|
|
||||||
LoadScreen: LogoStripeLoadScreen
|
LoadScreen: LogoStripeLoadScreen
|
||||||
Image: d2k|uibits/loadscreen.png
|
Image: d2k|uibits/loadscreen.png
|
||||||
|
Image2x: d2k|uibits/loadscreen-2x.png
|
||||||
|
Image3x: d2k|uibits/loadscreen-3x.png
|
||||||
Text: Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats...
|
Text: Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats...
|
||||||
|
|
||||||
ServerTraits:
|
ServerTraits:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
^Chrome:
|
^Chrome:
|
||||||
Image: chrome.png
|
Image: chrome.png
|
||||||
|
Image2x: chrome-2x.png
|
||||||
|
Image3x: chrome-3x.png
|
||||||
|
|
||||||
panel-header:
|
panel-header:
|
||||||
Inherits: ^Chrome
|
Inherits: ^Chrome
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ Notifications:
|
|||||||
|
|
||||||
LoadScreen: ModContentLoadScreen
|
LoadScreen: ModContentLoadScreen
|
||||||
Image: ./mods/modcontent/chrome.png
|
Image: ./mods/modcontent/chrome.png
|
||||||
|
Image2x: ./mods/modcontent/chrome-2x.png
|
||||||
|
Image3x: ./mods/modcontent/chrome-3x.png
|
||||||
|
|
||||||
ChromeMetrics:
|
ChromeMetrics:
|
||||||
common|metrics.yaml
|
common|metrics.yaml
|
||||||
|
|||||||
@@ -6,9 +6,13 @@
|
|||||||
|
|
||||||
^Glyphs:
|
^Glyphs:
|
||||||
Image: glyphs.png
|
Image: glyphs.png
|
||||||
|
Image2x: glyphs-2x.png
|
||||||
|
Image3x: glyphs-3x.png
|
||||||
|
|
||||||
^LoadScreen:
|
^LoadScreen:
|
||||||
Image: loadscreen.png
|
Image: loadscreen.png
|
||||||
|
Image2x: loadscreen-2x.png
|
||||||
|
Image3x: loadscreen-3x.png
|
||||||
|
|
||||||
sidebar-allies:
|
sidebar-allies:
|
||||||
Inherits: ^Sidebar
|
Inherits: ^Sidebar
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ Hotkeys:
|
|||||||
|
|
||||||
LoadScreen: LogoStripeLoadScreen
|
LoadScreen: LogoStripeLoadScreen
|
||||||
Image: ra|uibits/loadscreen.png
|
Image: ra|uibits/loadscreen.png
|
||||||
|
Image2x: ra|uibits/loadscreen-2x.png
|
||||||
|
Image3x: ra|uibits/loadscreen-3x.png
|
||||||
Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms...
|
Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms...
|
||||||
|
|
||||||
ServerTraits:
|
ServerTraits:
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
^Glyphs:
|
^Glyphs:
|
||||||
Image: glyphs.png
|
Image: glyphs.png
|
||||||
|
Image2x: glyphs-2x.png
|
||||||
|
Image3x: glyphs-3x.png
|
||||||
|
|
||||||
^LoadScreen:
|
^LoadScreen:
|
||||||
Image: loadscreen.png
|
Image: loadscreen.png
|
||||||
|
|||||||
Reference in New Issue
Block a user