Merge pull request #12501 from pchote/osx-retina

Add HiDPI support for OSX/Windows/Linux
This commit is contained in:
Oliver Brakmann
2017-01-21 22:54:15 +01:00
committed by GitHub
9 changed files with 161 additions and 24 deletions

View File

@@ -43,6 +43,8 @@ namespace OpenRA
IShader CreateShader(string name);
Size WindowSize { get; }
float WindowScale { get; }
event Action<float, float> OnWindowScaleChanged;
void Clear();
void Present();

View File

@@ -29,27 +29,37 @@ namespace OpenRA.Graphics
readonly Face face;
readonly Cache<Pair<char, Color>, GlyphInfo> glyphs;
public SpriteFont(string name, byte[] data, int size, SheetBuilder builder)
float deviceScale;
public SpriteFont(string name, byte[] data, int size, float scale, SheetBuilder builder)
{
if (builder.Type != SheetType.BGRA)
throw new ArgumentException("The sheet builder must create BGRA sheets.", "builder");
deviceScale = scale;
this.size = size;
this.builder = builder;
face = new Face(Library, data, 0);
face.SetPixelSizes((uint)size, (uint)size);
face.SetPixelSizes((uint)(size * deviceScale), (uint)(size * deviceScale));
glyphs = new Cache<Pair<char, Color>, GlyphInfo>(CreateGlyph, Pair<char, Color>.EqualityComparer);
// PERF: Cache these delegates for Measure calls.
Func<char, float> characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance;
lineWidth = line => line.Sum(characterWidth);
lineWidth = line => line.Sum(characterWidth) / deviceScale;
PrecacheColor(Color.White, name);
PrecacheColor(Color.Red, name);
}
public void SetScale(float scale)
{
deviceScale = scale;
face.SetPixelSizes((uint)(size * deviceScale), (uint)(size * deviceScale));
glyphs.Clear();
}
void PrecacheColor(Color c, string name)
{
using (new PerfTimer("PrecacheColor {0} {1}px {2}".F(name, size, c.Name)))
@@ -75,9 +85,10 @@ namespace OpenRA.Graphics
var g = glyphs[Pair.New(s, c)];
Game.Renderer.RgbaSpriteRenderer.DrawSprite(g.Sprite,
new float2(
(int)Math.Round(p.X + g.Offset.X, 0),
p.Y + g.Offset.Y));
p.X += g.Advance;
(int)Math.Round(p.X * deviceScale + g.Offset.X, 0) / deviceScale,
p.Y + g.Offset.Y / deviceScale),
g.Sprite.Size / deviceScale);
p.X += g.Advance / deviceScale;
}
}
@@ -85,10 +96,10 @@ namespace OpenRA.Graphics
{
if (offset > 0)
{
DrawText(text, location + new float2(-offset, 0), bg);
DrawText(text, location + new float2(offset, 0), bg);
DrawText(text, location + new float2(0, -offset), bg);
DrawText(text, location + new float2(0, offset), bg);
DrawText(text, location + new float2(-offset / deviceScale, 0), bg);
DrawText(text, location + new float2(offset / deviceScale, 0), bg);
DrawText(text, location + new float2(0, -offset / deviceScale), bg);
DrawText(text, location + new float2(0, offset / deviceScale), bg);
}
DrawText(text, location, fg);

View File

@@ -39,6 +39,8 @@ namespace OpenRA.Primitives
public bool ContainsKey(T key) { return cache.ContainsKey(key); }
public bool TryGetValue(T key, out U value) { return cache.TryGetValue(key, out value); }
public int Count { get { return cache.Count; } }
public void Clear() { cache.Clear(); }
public ICollection<T> Keys { get { return cache.Keys; } }
public ICollection<U> Values { get { return cache.Values; } }
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() { return cache.GetEnumerator(); }

View File

@@ -86,8 +86,15 @@ namespace OpenRA
fontSheetBuilder.Dispose();
fontSheetBuilder = new SheetBuilder(SheetType.BGRA);
Fonts = modData.Manifest.Fonts.ToDictionary(x => x.Key,
x => new SpriteFont(x.Value.First, modData.DefaultFileSystem.Open(x.Value.First).ReadAllBytes(), x.Value.Second, fontSheetBuilder)).AsReadOnly();
x => new SpriteFont(x.Value.First, modData.DefaultFileSystem.Open(x.Value.First).ReadAllBytes(),
x.Value.Second, Device.WindowScale, fontSheetBuilder)).AsReadOnly();
}
Device.OnWindowScaleChanged += (before, after) =>
{
foreach (var f in Fonts)
f.Value.SetScale(after);
};
}
public void InitializeDepthBuffer(MapGrid mapGrid)

View File

@@ -21,16 +21,28 @@ namespace OpenRA.Platforms.Default
sealed class Sdl2GraphicsDevice : ThreadAffine, IGraphicsDevice
{
readonly Sdl2Input input;
IntPtr context, window;
bool disposed;
public Size WindowSize { get; private set; }
public float WindowScale { get; private set; }
internal Size SurfaceSize { get; private set; }
public event Action<float, float> OnWindowScaleChanged = (before, after) => { };
[DllImport("user32.dll")]
static extern bool SetProcessDPIAware();
public Sdl2GraphicsDevice(Size windowSize, WindowMode windowMode)
{
Console.WriteLine("Using SDL 2 with OpenGL renderer");
WindowSize = windowSize;
// Disable legacy scaling on Windows
if (Platform.CurrentPlatform == PlatformType.Windows)
SetProcessDPIAware();
SDL.SDL_Init(SDL.SDL_INIT_NOPARACHUTE | SDL.SDL_INIT_VIDEO);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8);
@@ -50,8 +62,44 @@ namespace OpenRA.Platforms.Default
Console.WriteLine("Using resolution: {0}x{1}", WindowSize.Width, WindowSize.Height);
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
WindowSize.Width, WindowSize.Height, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL);
WindowSize.Width, WindowSize.Height, windowFlags);
SurfaceSize = WindowSize;
WindowScale = 1;
// Enable high resolution rendering for Retina displays
if (Platform.CurrentPlatform == PlatformType.OSX)
{
// OSX defines the window size in "points", with a device-dependent number of pixels per point.
// The window scale is simply the ratio of GL pixels / window points.
int width, height;
SDL.SDL_GL_GetDrawableSize(window, out width, out height);
SurfaceSize = new Size(width, height);
WindowScale = width * 1f / WindowSize.Width;
}
else if (Platform.CurrentPlatform == PlatformType.Windows)
{
float ddpi, hdpi, vdpi;
if (SDL.SDL_GetDisplayDPI(0, out ddpi, out hdpi, out vdpi) == 0)
{
WindowScale = ddpi / 96;
WindowSize = new Size((int)(SurfaceSize.Width / WindowScale), (int)(SurfaceSize.Height / WindowScale));
}
}
else
{
float scale = 1;
var scaleVariable = Environment.GetEnvironmentVariable("OPENRA_DISPLAY_SCALE");
if (scaleVariable != null && float.TryParse(scaleVariable, out scale))
{
WindowScale = scale;
WindowSize = new Size((int)(SurfaceSize.Width / WindowScale), (int)(SurfaceSize.Height / WindowScale));
}
}
Console.WriteLine("Using window scale {0:F2}", WindowScale);
if (Game.Settings.Game.LockMouseWindow)
GrabWindowMouseFocus();
@@ -93,6 +141,29 @@ namespace OpenRA.Platforms.Default
VerifyThreadAffinity();
try
{
// Pixel double the cursor on non-OSX if the window scale is large enough
// OSX does this for us automatically
if (Platform.CurrentPlatform != PlatformType.OSX && WindowScale > 1.5)
{
var scaledData = new byte[4 * data.Length];
for (var y = 0; y < size.Height * 4; y += 4)
{
for (var x = 0; x < size.Width * 4; x += 4)
{
var a = 4 * (y * size.Width + x);
var b = 4 * ((y + 1) * size.Width + x);
for (var i = 0; i < 4; i++)
{
scaledData[2 * a + i] = scaledData[2 * a + 4 + i] = data[a + i];
scaledData[2 * b + i] = scaledData[2 * b + 4 + i] = data[b + i];
}
}
}
size = new Size(2 * size.Width, 2 * size.Height);
data = scaledData;
}
return new SDL2HardwareCursor(size, data, hotspot);
}
catch (Exception ex)
@@ -114,6 +185,26 @@ namespace OpenRA.Platforms.Default
}
}
internal void WindowSizeChanged()
{
// The ratio between pixels and points can change when moving between displays in OSX
// We need to recalculate our scale to account for the potential change in the actual rendered area
if (Platform.CurrentPlatform == PlatformType.OSX)
{
int width, height;
SDL.SDL_GL_GetDrawableSize(window, out width, out height);
if (width != SurfaceSize.Width || height != SurfaceSize.Height)
{
var oldScale = WindowScale;
SurfaceSize = new Size(width, height);
WindowScale = width * 1f / WindowSize.Width;
OnWindowScaleChanged(oldScale, WindowScale);
}
}
}
sealed class SDL2HardwareCursor : IHardwareCursor
{
public IntPtr Cursor { get; private set; }
@@ -315,7 +406,16 @@ namespace OpenRA.Platforms.Default
if (height < 0)
height = 0;
OpenGL.glScissor(left, WindowSize.Height - (top + height), width, height);
var bottom = WindowSize.Height - (top + height);
if (WindowSize != SurfaceSize)
{
left = (int)Math.Round(WindowScale * left);
bottom = (int)Math.Round(WindowScale * bottom);
width = (int)Math.Round(WindowScale * width);
height = (int)Math.Round(WindowScale * height);
}
OpenGL.glScissor(left, bottom, width, height);
OpenGL.CheckGLError();
OpenGL.glEnable(OpenGL.GL_SCISSOR_TEST);
OpenGL.CheckGLError();
@@ -330,7 +430,7 @@ namespace OpenRA.Platforms.Default
public Bitmap TakeScreenshot()
{
var rect = new Rectangle(Point.Empty, WindowSize);
var rect = new Rectangle(Point.Empty, SurfaceSize);
var bitmap = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var data = bitmap.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
@@ -362,7 +462,7 @@ namespace OpenRA.Platforms.Default
public void PumpInput(IInputHandler inputHandler)
{
VerifyThreadAffinity();
input.PumpInput(inputHandler);
input.PumpInput(this, inputHandler);
}
public string GetClipboardText()

View File

@@ -40,7 +40,17 @@ namespace OpenRA.Platforms.Default
| ((raw & (int)SDL.SDL_Keymod.KMOD_SHIFT) != 0 ? Modifiers.Shift : 0);
}
public void PumpInput(IInputHandler inputHandler)
int2 EventPosition(Sdl2GraphicsDevice device, int x, int y)
{
// On Windows and Linux (X11) events are given in surface coordinates
// These must be scaled to our effective window coordinates
if (Platform.CurrentPlatform != PlatformType.OSX && device.WindowSize != device.SurfaceSize)
return new int2((int)(x / device.WindowScale), (int)(y / device.WindowScale));
return new int2(x, y);
}
public void PumpInput(Sdl2GraphicsDevice device, IInputHandler inputHandler)
{
var mods = MakeModifiers((int)SDL.SDL_GetModState());
var scrollDelta = 0;
@@ -67,6 +77,11 @@ namespace OpenRA.Platforms.Default
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED:
Game.HasInputFocus = true;
break;
// Triggered when moving between displays with different DPI settings
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
device.WindowSizeChanged();
break;
}
break;
@@ -83,8 +98,7 @@ namespace OpenRA.Platforms.Default
var button = MakeButton(e.button.button);
lastButtonBits |= button;
var pos = new int2(e.button.x, e.button.y);
var pos = EventPosition(device, e.button.x, e.button.y);
inputHandler.OnMouseInput(new MouseInput(
MouseInputEvent.Down, button, scrollDelta, pos, mods,
MultiTapDetection.DetectFromMouse(e.button.button, pos)));
@@ -103,7 +117,7 @@ namespace OpenRA.Platforms.Default
var button = MakeButton(e.button.button);
lastButtonBits &= ~button;
var pos = new int2(e.button.x, e.button.y);
var pos = EventPosition(device, e.button.x, e.button.y);
inputHandler.OnMouseInput(new MouseInput(
MouseInputEvent.Up, button, scrollDelta, pos, mods,
MultiTapDetection.InfoFromMouse(e.button.button)));
@@ -113,9 +127,10 @@ namespace OpenRA.Platforms.Default
case SDL.SDL_EventType.SDL_MOUSEMOTION:
{
var pos = EventPosition(device, e.motion.x, e.motion.y);
pendingMotion = new MouseInput(
MouseInputEvent.Move, lastButtonBits, scrollDelta,
new int2(e.motion.x, e.motion.y), mods, 0);
pos, mods, 0);
break;
}

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# OpenRA packaging script for Mac OSX
LAUNCHER_TAG="osx-launcher-20160824"
LAUNCHER_TAG="osx-launcher-20161223"
if [ $# -ne "3" ]; then
echo "Usage: `basename $0` tag files-dir outputdir"

View File

@@ -130,7 +130,7 @@ if (!(Test-Path "SDL2-CS.dll"))
{
echo "Fetching SDL2-CS from GitHub."
$target = Join-Path $pwd.ToString() "SDL2-CS.dll"
(New-Object System.Net.WebClient).DownloadFile("https://github.com/OpenRA/SDL2-CS/releases/download/20151227/SDL2-CS.dll", $target)
(New-Object System.Net.WebClient).DownloadFile("https://github.com/OpenRA/SDL2-CS/releases/download/20161223/SDL2-CS.dll", $target)
}
if (!(Test-Path "OpenAL-CS.dll"))

View File

@@ -100,8 +100,8 @@ fi
if [ ! -f SDL2-CS.dll -o ! -f SDL2-CS.dll.config ]; then
echo "Fetching SDL2-CS from GitHub."
curl -s -L -O https://github.com/OpenRA/SDL2-CS/releases/download/20151227/SDL2-CS.dll
curl -s -L -O https://github.com/OpenRA/SDL2-CS/releases/download/20151227/SDL2-CS.dll.config
curl -s -L -O https://github.com/OpenRA/SDL2-CS/releases/download/20161223/SDL2-CS.dll
curl -s -L -O https://github.com/OpenRA/SDL2-CS/releases/download/20161223/SDL2-CS.dll.config
fi
if [ ! -f OpenAL-CS.dll -o ! -f OpenAL-CS.dll.config ]; then