Merge pull request #12501 from pchote/osx-retina
Add HiDPI support for OSX/Windows/Linux
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
2
thirdparty/fetch-thirdparty-deps.ps1
vendored
2
thirdparty/fetch-thirdparty-deps.ps1
vendored
@@ -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"))
|
||||
|
||||
4
thirdparty/fetch-thirdparty-deps.sh
vendored
4
thirdparty/fetch-thirdparty-deps.sh
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user