Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg	(revision 19922)
+++ binaries/data/config/default.cfg	(working copy)
@@ -56,6 +56,9 @@ yres = 0
 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth)
 bpp = 0
 
+; Render at high-dpi resolution if screen supports it. Will be much slower.
+high_dpi = false
+
 ; Preferred display (for multidisplay setups, only works with SDL 2.0)
 display = 0
Index: source/gui/CGUI.cpp
===================================================================
--- source/gui/CGUI.cpp	(revision 19922)
+++ source/gui/CGUI.cpp	(working copy)
@@ -1,1735 +1,1735 @@
 /* Copyright (C) 2017 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "precompiled.h"
 
 #include <stdarg.h>
 #include <string>
 
 #include "GUI.h"
 
 // Types - when including them into the engine.
 #include "CButton.h"
 #include "CChart.h"
 #include "CCheckBox.h"
 #include "CDropDown.h"
 #include "CImage.h"
 #include "CInput.h"
 #include "CList.h"
 #include "COList.h"
 #include "CProgressBar.h"
 #include "CRadioButton.h"
 #include "CSlider.h"
 #include "CText.h"
 #include "CTooltip.h"
 #include "MiniMap.h"
 
 #include "graphics/FontMetrics.h"
 #include "graphics/ShaderManager.h"
 #include "graphics/TextRenderer.h"
 #include "i18n/L10n.h"
 #include "lib/bits.h"
 #include "lib/input.h"
 #include "lib/sysdep/sysdep.h"
 #include "lib/timer.h"
 #include "lib/utf8.h"
 #include "ps/CLogger.h"
 #include "ps/Filesystem.h"
 #include "ps/GameSetup/Config.h"
 #include "ps/Globals.h"
 #include "ps/Hotkey.h"
 #include "ps/Profile.h"
 #include "ps/Pyrogenesis.h"
 #include "ps/XML/Xeromyces.h"
 #include "renderer/Renderer.h"
 #include "scripting/ScriptFunctions.h"
 #include "scriptinterface/ScriptInterface.h"
 
 extern int g_yres;
 
 const double SELECT_DBLCLICK_RATE = 0.5;
 const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion
 
 InReaction CGUI::HandleEvent(const SDL_Event_* ev)
 {
 	InReaction ret = IN_PASS;
 
 	if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP)
 	{
 		const char* hotkey = static_cast<const char*>(ev->ev.user.data1);
 
 		std::map<CStr, std::vector<IGUIObject*> >::iterator it = m_HotkeyObjects.find(hotkey);
 		if (it != m_HotkeyObjects.end())
 			for (IGUIObject* const& obj : it->second)
 			{
 				// Update hotkey status before sending the event,
 				// else the status will be outdated when processing the GUI event.
 				HotkeyInputHandler(ev);
 				ret = IN_HANDLED;
 
 				if (ev->ev.type == SDL_HOTKEYDOWN)
 					obj->SendEvent(GUIM_PRESSED, "press");
 				else
 					obj->SendEvent(GUIM_RELEASED, "release");
 			}
 	}
 
 	else if (ev->ev.type == SDL_MOUSEMOTION)
 	{
 		// Yes the mouse position is stored as float to avoid
-		//  constant conversions when operating in a
-		//  float-based environment.
-		m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale);
+		// constant conversions when operating in a
+		// float-based environment.
+		m_MousePos = CPos(static_cast<float>(ev->ev.motion.x) / g_GuiScale * g_DpiScale, static_cast<float>(ev->ev.motion.y) / g_GuiScale * g_DpiScale);
 
 		SGUIMessage msg(GUIM_MOUSE_MOTION);
 		GUI<SGUIMessage>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
 										&IGUIObject::HandleMessage,
 										msg);
 	}
 
 	// Update m_MouseButtons. (BUTTONUP is handled later.)
 	else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
 	{
 		switch (ev->ev.button.button)
 		{
 		case SDL_BUTTON_LEFT:
 		case SDL_BUTTON_RIGHT:
 		case SDL_BUTTON_MIDDLE:
 			m_MouseButtons |= Bit<unsigned int>(ev->ev.button.button);
 			break;
 		default:
 			break;
 		}
 	}
 
 	// Update m_MousePos (for delayed mouse button events)
 	CPos oldMousePos = m_MousePos;
 	if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
 	{
-		m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
+		m_MousePos = CPos(static_cast<float>(ev->ev.button.x) / g_GuiScale * g_DpiScale, static_cast<float>(ev->ev.button.y) / g_GuiScale * g_DpiScale);
 	}
 
 	// Only one object can be hovered
 	IGUIObject* pNearest = NULL;
 
 	// TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress!
 	try
 	{
 		PROFILE("mouse events");
 		// TODO Gee: Optimizations needed!
 		//  these two recursive function are quite overhead heavy.
 
 		// pNearest will after this point at the hovered object, possibly NULL
 		pNearest = FindObjectUnderMouse();
 
 		// Now we'll call UpdateMouseOver on *all* objects,
 		//  we'll input the one hovered, and they will each
 		//  update their own data and send messages accordingly
 		GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST,
 			m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
 
 		if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
 		{
 			switch (ev->ev.button.button)
 			{
 			case SDL_BUTTON_LEFT:
 				// Focus the clicked object (or focus none if nothing clicked on)
 				SetFocusedObject(pNearest);
 
 				if (pNearest)
 					ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress");
 				break;
 
 			case SDL_BUTTON_RIGHT:
 				if (pNearest)
 					ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress");
 				break;
 
 			default:
 				break;
 			}
 		}
 		else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest)
 		{
 			if (ev->ev.wheel.y < 0)
 				ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown");
 			else if (ev->ev.wheel.y > 0)
 				ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup");
 		}
 		else if (ev->ev.type == SDL_MOUSEBUTTONUP)
 		{
 			switch (ev->ev.button.button)
 			{
 			case SDL_BUTTON_LEFT:
 				if (pNearest)
 				{
 					double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT];
 					pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time();
 
 					if (timeElapsed < SELECT_DBLCLICK_RATE)
 						ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick");
 					else
 						ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease");
 				}
 				break;
 			case SDL_BUTTON_RIGHT:
 				if (pNearest)
 				{
 					double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT];
 					pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time();
 
 					if (timeElapsed < SELECT_DBLCLICK_RATE)
 						ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick");
 					else
 						ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease");
 				}
 				break;
 			}
 
 			// Reset all states on all visible objects
 			GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject,
 									&IGUIObject::ResetStates);
 
 			// Since the hover state will have been reset, we reload it.
 			GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST,
 				m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest);
 		}
 	}
 	catch (PSERROR_GUI& e)
 	{
 		UNUSED2(e);
 		debug_warn(L"CGUI::HandleEvent error");
 		// TODO Gee: Handle
 	}
 
 	// BUTTONUP's effect on m_MouseButtons is handled after
 	// everything else, so that e.g. 'press' handlers (activated
 	// on button up) see which mouse button had been pressed.
 	if (ev->ev.type == SDL_MOUSEBUTTONUP)
 	{
 		switch (ev->ev.button.button)
 		{
 		case SDL_BUTTON_LEFT:
 		case SDL_BUTTON_RIGHT:
 		case SDL_BUTTON_MIDDLE:
 			m_MouseButtons &= ~Bit<unsigned int>(ev->ev.button.button);
 			break;
 		default:
 			break;
 		}
 	}
 
 	// Restore m_MousePos (for delayed mouse button events)
 	if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
 		m_MousePos = oldMousePos;
 
 	// Handle keys for input boxes
 	if (GetFocusedObject())
 	{
 		if ((ev->ev.type == SDL_KEYDOWN &&
 		     ev->ev.key.keysym.sym != SDLK_ESCAPE &&
 		     !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] &&
 		     !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) ||
 		    ev->ev.type == SDL_HOTKEYDOWN ||
 		    ev->ev.type == SDL_TEXTINPUT ||
 		    ev->ev.type == SDL_TEXTEDITING)
 		{
 			ret = GetFocusedObject()->ManuallyHandleEvent(ev);
 		}
 		// else will return IN_PASS because we never used the button.
 	}
 
 	return ret;
 }
 
 void CGUI::TickObjects()
 {
 	CStr action = "tick";
 	GUI<CStr>::RecurseObject(0, m_BaseObject,
 							&IGUIObject::ScriptEvent, action);
 
 	m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this);
 }
 
 void CGUI::SendEventToAll(const CStr& EventName)
 {
 	// janwas 2006-03-03: spoke with Ykkrosh about EventName case.
 	// when registering, case is converted to lower - this avoids surprise
 	// if someone were to get the case wrong and then not notice their
 	// handler is never called. however, until now, the other end
 	// (sending events here) wasn't converting to lower case,
 	// leading to a similar problem.
 	// now fixed; case is irrelevant since all are converted to lower.
 	GUI<CStr>::RecurseObject(0, m_BaseObject,
 		&IGUIObject::ScriptEvent, EventName.LowerCase());
 }
 
 CGUI::CGUI(const shared_ptr<ScriptRuntime>& runtime)
 	: m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0)
 {
 	m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime));
 	GuiScriptingInit(*m_ScriptInterface);
 	m_ScriptInterface->LoadGlobalScripts();
 	m_BaseObject = new CGUIDummyObject;
 	m_BaseObject->SetGUI(this);
 }
 
 CGUI::~CGUI()
 {
 	Destroy();
 
 	if (m_BaseObject)
 		delete m_BaseObject;
 }
 
 IGUIObject* CGUI::ConstructObject(const CStr& str)
 {
 	if (m_ObjectTypes.count(str) > 0)
 		return (*m_ObjectTypes[str])();
 	else
 	{
 		// Error reporting will be handled with the NULL return.
 		return NULL;
 	}
 }
 
 void CGUI::Initialize()
 {
 	// Add base types!
 	//  You can also add types outside the GUI to extend the flexibility of the GUI.
 	//  Pyrogenesis though will have all the object types inserted from here.
 	AddObjectType("empty",			&CGUIDummyObject::ConstructObject);
 	AddObjectType("button",			&CButton::ConstructObject);
 	AddObjectType("image",			&CImage::ConstructObject);
 	AddObjectType("text",			&CText::ConstructObject);
 	AddObjectType("checkbox",		&CCheckBox::ConstructObject);
 	AddObjectType("radiobutton",	&CRadioButton::ConstructObject);
 	AddObjectType("progressbar",	&CProgressBar::ConstructObject);
 	AddObjectType("minimap",        &CMiniMap::ConstructObject);
 	AddObjectType("input",			&CInput::ConstructObject);
 	AddObjectType("list",			&CList::ConstructObject);
 	AddObjectType("olist",			&COList::ConstructObject);
 	AddObjectType("dropdown",		&CDropDown::ConstructObject);
 	AddObjectType("tooltip",		&CTooltip::ConstructObject);
 	AddObjectType("chart",			&CChart::ConstructObject);
 	AddObjectType("slider",			&CSlider::ConstructObject);
 }
 
 void CGUI::Draw()
 {
 	// Clear the depth buffer, so the GUI is
 	// drawn on top of everything else
 	glClear(GL_DEPTH_BUFFER_BIT);
 
 	try
 	{
 		// Recurse IGUIObject::Draw() with restriction: hidden
 		//  meaning all hidden objects won't call Draw (nor will it recurse its children)
 		GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw);
 	}
 	catch (PSERROR_GUI& e)
 	{
 		LOGERROR("GUI draw error: %s", e.what());
 	}
 }
 
 void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping))
 {
 	// If the sprite doesn't exist (name == ""), don't bother drawing anything
 	if (Sprite.IsEmpty())
 		return;
 
 	// TODO: Clipping?
 
 	Sprite.Draw(Rect, CellID, m_Sprites, Z);
 }
 
 void CGUI::Destroy()
 {
 	// We can use the map to delete all
 	//  now we don't want to cancel all if one Destroy fails
 	for (const std::pair<CStr, IGUIObject*>& p : m_pAllObjects)
 	{
 		try
 		{
 			p.second->Destroy();
 		}
 		catch (PSERROR_GUI& e)
 		{
 			UNUSED2(e);
 			debug_warn(L"CGUI::Destroy error");
 			// TODO Gee: Handle
 		}
 
 		delete p.second;
 	}
 	m_pAllObjects.clear();
 
 	for (const std::pair<CStr, CGUISprite*>& p : m_Sprites)
 		delete p.second;
 	m_Sprites.clear();
 	m_Icons.clear();
 }
 
 void CGUI::UpdateResolution()
 {
 	// Update ALL cached
 	GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize);
 }
 
 void CGUI::AddObject(IGUIObject* pObject)
 {
 	try
 	{
 		// Add CGUI pointer
 		GUI<CGUI*>::RecurseObject(0, pObject, &IGUIObject::SetGUI, this);
 
 		m_BaseObject->AddChild(pObject);
 
 		// Cache tree
 		GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
 
 		SGUIMessage msg(GUIM_LOAD);
 		GUI<SGUIMessage>::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg);
 	}
 	catch (PSERROR_GUI&)
 	{
 		throw;
 	}
 }
 
 void CGUI::UpdateObjects()
 {
 	// We'll fill a temporary map until we know everything succeeded
 	map_pObjects AllObjects;
 
 	try
 	{
 		// Fill freshly
 		GUI<map_pObjects>::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects);
 	}
 	catch (PSERROR_GUI&)
 	{
 		throw;
 	}
 
 	// Else actually update the real one
 	m_pAllObjects.swap(AllObjects);
 }
 
 bool CGUI::ObjectExists(const CStr& Name) const
 {
 	return m_pAllObjects.count(Name) != 0;
 }
 
 IGUIObject* CGUI::FindObjectByName(const CStr& Name) const
 {
 	map_pObjects::const_iterator it = m_pAllObjects.find(Name);
 	if (it == m_pAllObjects.end())
 		return NULL;
 	else
 		return it->second;
 }
 
 IGUIObject* CGUI::FindObjectUnderMouse() const
 {
 	IGUIObject* pNearest = NULL;
 
 	GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject,
 		&IGUIObject::ChooseMouseOverAndClosest, pNearest);
 
 	return pNearest;
 }
 
 void CGUI::SetFocusedObject(IGUIObject* pObject)
 {
 	if (pObject == m_FocusedObject)
 		return;
 
 	if (m_FocusedObject)
 	{
 		SGUIMessage msg(GUIM_LOST_FOCUS);
 		m_FocusedObject->HandleMessage(msg);
 	}
 
 	m_FocusedObject = pObject;
 
 	if (m_FocusedObject)
 	{
 		SGUIMessage msg(GUIM_GOT_FOCUS);
 		m_FocusedObject->HandleMessage(msg);
 	}
 }
 
 // private struct used only in GenerateText(...)
 struct SGenerateTextImage
 {
 	float m_YFrom,			// The image's starting location in Y
 		  m_YTo,			// The image's end location in Y
 		  m_Indentation;	// The image width in other words
 
 	// Some help functions
 	// TODO Gee: CRect => CPoint ?
 	void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall& SpriteCall,
 						 const float width, const float y,
 						 const CSize& Size, const CStr& TextureName,
 						 const float BufferZone, const int CellID)
 	{
 		// TODO Gee: Temp hardcoded values
 		SpriteCall.m_Area.top = y+BufferZone;
 		SpriteCall.m_Area.bottom = y+BufferZone + Size.cy;
 
 		if (Left)
 		{
 			SpriteCall.m_Area.left = BufferZone;
 			SpriteCall.m_Area.right = Size.cx+BufferZone;
 		}
 		else
 		{
 			SpriteCall.m_Area.left = width-BufferZone - Size.cx;
 			SpriteCall.m_Area.right = width-BufferZone;
 		}
 
 		SpriteCall.m_CellID = CellID;
 		SpriteCall.m_Sprite = TextureName;
 
 		m_YFrom = SpriteCall.m_Area.top-BufferZone;
 		m_YTo = SpriteCall.m_Area.bottom+BufferZone;
 		m_Indentation = Size.cx+BufferZone*2;
 	}
 };
 
 SGUIText CGUI::GenerateText(const CGUIString& string, const CStrW& FontW, const float& Width, const float& BufferZone, const IGUIObject* pObject)
 {
 	SGUIText Text;
 
 	CStrIntern Font(FontW.ToUTF8());
 
 	if (string.m_Words.empty())
 		return Text;
 
 	float x = BufferZone, y = BufferZone; // drawing pointer
 	int from = 0;
 	bool done = false;
 
 	bool FirstLine = true;	// Necessary because text in the first line is shorter
 							// (it doesn't count the line spacing)
 
 	// Images on the left or the right side.
 	std::vector<SGenerateTextImage> Images[2];
 	int pos_last_img = -1;	// Position in the string where last img (either left or right) were encountered.
 							//  in order to avoid duplicate processing.
 
 	// Easier to read.
 	bool WordWrapping = (Width != 0);
 
 	// get the alignment type for the control we are computing the text for since
 	// we are computing the horizontal alignment in this method in order to not have
 	// to run through the TextCalls a second time in the CalculateTextPosition method again
 	EAlign align;
 	GUI<EAlign>::GetSetting(pObject, "text_align", align);
 
 	// Go through string word by word
 	for (int i = 0; i < (int)string.m_Words.size()-1 && !done; ++i)
 	{
 		// Pre-process each line one time, so we know which floating images
 		//  will be added for that line.
 
 		// Generated stuff is stored in Feedback.
 		CGUIString::SFeedback Feedback;
 
 		// Preliminary line_height, used for word-wrapping with floating images.
 		float prelim_line_height = 0.f;
 
 		// Width and height of all text calls generated.
 		string.GenerateTextCall(this, Feedback, Font,
 								string.m_Words[i], string.m_Words[i+1],
 								FirstLine);
 
 		// Loop through our images queues, to see if images has been added.
 
 		// Check if this has already been processed.
 		//  Also, floating images are only applicable if Word-Wrapping is on
 		if (WordWrapping && i > pos_last_img)
 		{
 			// Loop left/right
 			for (int j = 0; j < 2; ++j)
 			{
 				for (const CStr& imgname : Feedback.m_Images[j])
 				{
 					SGUIText::SSpriteCall SpriteCall;
 					SGenerateTextImage Image;
 
 					// Y is if no other floating images is above, y. Else it is placed
 					//  after the last image, like a stack downwards.
 					float _y;
 					if (!Images[j].empty())
 						_y = std::max(y, Images[j].back().m_YTo);
 					else
 						_y = y;
 
 					// Get Size from Icon database
 					SGUIIcon icon = GetIcon(imgname);
 
 					CSize size = icon.m_Size;
 					Image.SetupSpriteCall((j == CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID);
 
 					// Check if image is the lowest thing.
 					Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo);
 
 					Images[j].push_back(Image);
 					Text.m_SpriteCalls.push_back(SpriteCall);
 				}
 			}
 		}
 
 		pos_last_img = std::max(pos_last_img, i);
 
 		x += Feedback.m_Size.cx;
 		prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy);
 
 		// If Width is 0, then there's no word-wrapping, disable NewLine.
 		if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2)
 		{
 			// Change 'from' to 'i', but first keep a copy of its value.
 			int temp_from = from;
 			from = i;
 
 			static const int From = 0, To = 1;
 			//int width_from=0, width_to=width;
 			float width_range[2];
 			width_range[From] = BufferZone;
 			width_range[To] = Width - BufferZone;
 
 			// Floating images are only applicable if word-wrapping is enabled.
 			if (WordWrapping)
 			{
 				// Decide width of the line. We need to iterate our floating images.
 				//  this won't be exact because we're assuming the line_height
 				//  will be as our preliminary calculation said. But that may change,
 				//  although we'd have to add a couple of more loops to try straightening
 				//  this problem out, and it is very unlikely to happen noticeably if one
 				//  structures his text in a stylistically pure fashion. Even if not, it
 				//  is still quite unlikely it will happen.
 				// Loop through left and right side, from and to.
 				for (int j = 0; j < 2; ++j)
 				{
 					for (const SGenerateTextImage& img : Images[j])
 					{
 						// We're working with two intervals here, the image's and the line height's.
 						//  let's find the union of these two.
 						float union_from, union_to;
 
 						union_from = std::max(y, img.m_YFrom);
 						union_to = std::min(y+prelim_line_height, img.m_YTo);
 
 						// The union is not empty
 						if (union_to > union_from)
 						{
 							if (j == From)
 								width_range[From] = std::max(width_range[From], img.m_Indentation);
 							else
 								width_range[To] = std::min(width_range[To], Width - img.m_Indentation);
 						}
 					}
 				}
 			}
 
 			// Reset X for the next loop
 			x = width_range[From];
 
 			// Now we'll do another loop to figure out the height and width of
 			//  the line (the height of the largest character and the width is
 			//  the sum of all of the individual widths). This
 			//  couldn't be determined in the first loop (main loop)
 			//  because it didn't regard images, so we don't know
 			//  if all characters processed, will actually be involved
 			//  in that line.
 			float line_height = 0.f;
 			float line_width = 0.f;
 			for (int j = temp_from; j <= i; ++j)
 			{
 				// We don't want to use Feedback now, so we'll have to use
 				//  another.
 				CGUIString::SFeedback Feedback2;
 
 				// Don't attach object, it'll suppress the errors
 				//  we want them to be reported in the final GenerateTextCall()
 				//  so that we don't get duplicates.
 				string.GenerateTextCall(this, Feedback2, Font,
 										string.m_Words[j], string.m_Words[j+1],
 										FirstLine);
 
 				// Append X value.
 				x += Feedback2.m_Size.cx;
 
 				if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine)
 					break;
 
 				// Let line_height be the maximum m_Height we encounter.
 				line_height = std::max(line_height, Feedback2.m_Size.cy);
 
 				line_width += Feedback2.m_Size.cx;
 
 				if (WordWrapping && Feedback2.m_NewLine)
 					break;
 			}
 
 			float dx = 0.f;
 			// compute offset based on what kind of alignment
 			switch (align)
 			{
 			case EAlign_Left:
 				// don't add an offset
 				dx = 0.f;
 				break;
 
 			case EAlign_Center:
 				dx = ((width_range[To] - width_range[From]) - line_width) / 2;
 				break;
 
 			case EAlign_Right:
 				dx = width_range[To] - line_width;
 				break;
 
 			default:
 				debug_warn(L"Broken EAlign in CGUI::GenerateText()");
 				break;
 			}
 			// Reset x once more
 			x = width_range[From];
 			// Move down, because font drawing starts from the baseline
 			y += line_height;
 
 			// Do the real processing now
 			for (int j = temp_from; j <= i; ++j)
 			{
 				// We don't want to use Feedback now, so we'll have to use
 				//  another one.
 				CGUIString::SFeedback Feedback2;
 
 				// Defaults
 				string.GenerateTextCall(this, Feedback2, Font,
 										string.m_Words[j], string.m_Words[j+1],
 										FirstLine, pObject);
 
 				// Iterate all and set X/Y values
 				// Since X values are not set, we need to make an internal
 				//  iteration with an increment that will append the internal
 				//  x, that is what x_pointer is for.
 				float x_pointer = 0.f;
 
 				for (SGUIText::STextCall& tc : Feedback2.m_TextCalls)
 				{
 					tc.m_Pos = CPos(dx + x + x_pointer, y);
 
 					x_pointer += tc.m_Size.cx;
 
 					if (tc.m_pSpriteCall)
 						tc.m_pSpriteCall->m_Area += tc.m_Pos - CSize(0, tc.m_pSpriteCall->m_Area.GetHeight());
 				}
 
 				// Append X value.
 				x += Feedback2.m_Size.cx;
 
 				Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone);
 
 				// The first word overrides the width limit, what we
 				//  do, in those cases, are just drawing that word even
 				//  though it'll extend the object.
 				if (WordWrapping) // only if word-wrapping is applicable
 				{
 					if (Feedback2.m_NewLine)
 					{
 						from = j+1;
 
 						// Sprite call can exist within only a newline segment,
 						//  therefore we need this.
 						Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
 						break;
 					}
 					else if (x > width_range[To] && j == temp_from)
 					{
 						from = j+1;
 						// do not break, since we want it to be added to m_TextCalls
 					}
 					else if (x > width_range[To])
 					{
 						from = j;
 						break;
 					}
 				}
 
 				// Add the whole Feedback2.m_TextCalls to our m_TextCalls.
 				Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end());
 				Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end());
 
 				if (j == (int)string.m_Words.size()-2)
 					done = true;
 			}
 
 			// Reset X
 			x = 0.f;
 
 			// Update height of all
 			Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone);
 
 			FirstLine = false;
 
 			// Now if we entered as from = i, then we want
 			//  i being one minus that, so that it will become
 			//  the same i in the next loop. The difference is that
 			//  we're on a new line now.
 			i = from-1;
 		}
 	}
 
 	return Text;
 }
 
 void CGUI::DrawText(SGUIText& Text, const CColor& DefaultColor, const CPos& pos, const float& z, const CRect& clipping)
 {
 	CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
 
 	tech->BeginPass();
 
 	bool isClipped = (clipping != CRect());
 	if (isClipped)
 	{
 		glEnable(GL_SCISSOR_TEST);
 		glScissor(
 			clipping.left * g_GuiScale,
 			g_yres - clipping.bottom * g_GuiScale,
 			clipping.GetWidth() * g_GuiScale,
 			clipping.GetHeight() * g_GuiScale);
 	}
 
 	CTextRenderer textRenderer(tech->GetShader());
 	textRenderer.SetClippingRect(clipping);
 	textRenderer.Translate(0.0f, 0.0f, z);
 
 	for (const SGUIText::STextCall& tc : Text.m_TextCalls)
 	{
 		// If this is just a placeholder for a sprite call, continue
 		if (tc.m_pSpriteCall)
 			continue;
 
 		CColor color = tc.m_UseCustomColor ? tc.m_Color : DefaultColor;
 
 		textRenderer.Color(color);
 		textRenderer.Font(tc.m_Font);
 		textRenderer.Put((float)(int)(pos.x + tc.m_Pos.x), (float)(int)(pos.y + tc.m_Pos.y), &tc.m_String);
 	}
 
 	textRenderer.Render();
 
 	for (const SGUIText::SSpriteCall& sc : Text.m_SpriteCalls)
 		DrawSprite(sc.m_Sprite, sc.m_CellID, z, sc.m_Area + pos);
 
 	if (isClipped)
 		glDisable(GL_SCISSOR_TEST);
 
 	tech->EndPass();
 }
 
 bool CGUI::GetPreDefinedColor(const CStr& name, CColor& Output) const
 {
 	std::map<CStr, CColor>::const_iterator cit = m_PreDefinedColors.find(name);
 	if (cit == m_PreDefinedColors.end())
 		return false;
 
 	Output = cit->second;
 	return true;
 }
 
 /**
  * @callgraph
  */
 void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set<VfsPath>& Paths)
 {
 	Paths.insert(Filename);
 
 	CXeromyces XeroFile;
 	if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK)
 		return;
 
 	XMBElement node = XeroFile.GetRoot();
 
 	CStr root_name(XeroFile.GetElementString(node.GetNodeName()));
 	try
 	{
 		if (root_name == "objects")
 		{
 			Xeromyces_ReadRootObjects(node, &XeroFile, Paths);
 
 			// Re-cache all values so these gets cached too.
 			//UpdateResolution();
 		}
 		else if (root_name == "sprites")
 			Xeromyces_ReadRootSprites(node, &XeroFile);
 		else if (root_name == "styles")
 			Xeromyces_ReadRootStyles(node, &XeroFile);
 		else if (root_name == "setup")
 			Xeromyces_ReadRootSetup(node, &XeroFile);
 		else
 			debug_warn(L"CGUI::LoadXmlFile error");
 	}
 	catch (PSERROR_GUI& e)
 	{
 		LOGERROR("Errors loading GUI file %s (%u)", Filename.string8(), e.getCode());
 		return;
 	}
 }
 
 //===================================================================
 //	XML Reading Xeromyces Specific Sub-Routines
 //===================================================================
 
 void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set<VfsPath>& Paths)
 {
 	int el_script = pFile->GetElementID("script");
 
 	std::vector<std::pair<CStr, CStr> > subst;
 
 	// Iterate main children
 	//  they should all be <object> or <script> elements
 	for (XMBElement child : Element.GetChildNodes())
 	{
 		if (child.GetNodeName() == el_script)
 			// Execute the inline script
 			Xeromyces_ReadScript(child, pFile, Paths);
 		else
 			// Read in this whole object into the GUI
 			Xeromyces_ReadObject(child, pFile, m_BaseObject, subst, Paths, 0);
 	}
 }
 
 void CGUI::Xeromyces_ReadRootSprites(XMBElement Element, CXeromyces* pFile)
 {
 	for (XMBElement child : Element.GetChildNodes())
 		Xeromyces_ReadSprite(child, pFile);
 }
 
 void CGUI::Xeromyces_ReadRootStyles(XMBElement Element, CXeromyces* pFile)
 {
 	for (XMBElement child : Element.GetChildNodes())
 		Xeromyces_ReadStyle(child, pFile);
 }
 
 void CGUI::Xeromyces_ReadRootSetup(XMBElement Element, CXeromyces* pFile)
 {
 	for (XMBElement child : Element.GetChildNodes())
 	{
 		CStr name(pFile->GetElementString(child.GetNodeName()));
 
 		if (name == "scrollbar")
 			Xeromyces_ReadScrollBarStyle(child, pFile);
 		else if (name == "icon")
 			Xeromyces_ReadIcon(child, pFile);
 		else if (name == "tooltip")
 			Xeromyces_ReadTooltip(child, pFile);
 		else if (name == "color")
 			Xeromyces_ReadColor(child, pFile);
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 }
 
 void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector<std::pair<CStr, CStr> >& NameSubst, boost::unordered_set<VfsPath>& Paths, u32 nesting_depth)
 {
 	ENSURE(pParent);
 
 	XMBAttributeList attributes = Element.GetAttributes();
 
 	CStr type(attributes.GetNamedItem(pFile->GetAttributeID("type")));
 	if (type.empty())
 		type = "empty";
 
 	// Construct object from specified type
 	//  henceforth, we need to do a rollback before aborting.
 	//  i.e. releasing this object
 	IGUIObject* object = ConstructObject(type);
 
 	if (!object)
 	{
 		LOGERROR("GUI: Unrecognized object type \"%s\"", type.c_str());
 		return;
 	}
 
 	// Cache some IDs for element attribute names, to avoid string comparisons
 	#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
 	#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
 	ELMT(object);
 	ELMT(action);
 	ELMT(repeat);
 	ELMT(translatableAttribute);
 	ELMT(translate);
 	ELMT(attribute);
 	ELMT(keep);
 	ELMT(include);
 	ATTR(style);
 	ATTR(type);
 	ATTR(name);
 	ATTR(hotkey);
 	ATTR(z);
 	ATTR(on);
 	ATTR(file);
 	ATTR(directory);
 	ATTR(id);
 	ATTR(context);
 
 	//
 	//	Read Style and set defaults
 	//
 	//	If the setting "style" is set, try loading that setting.
 	//
 	//	Always load default (if it's available) first!
 	//
 	CStr argStyle(attributes.GetNamedItem(attr_style));
 
 	if (m_Styles.count("default") == 1)
 		object->LoadStyle(*this, "default");
 
 	if (!argStyle.empty())
 	{
 		if (m_Styles.count(argStyle) == 0)
 			LOGERROR("GUI: Trying to use style '%s' that doesn't exist.", argStyle.c_str());
 		else
 			object->LoadStyle(*this, argStyle);
 	}
 
 	bool NameSet = false;
 	bool ManuallySetZ = false;
 
 	CStrW inclusionPath;
 	CStr hotkeyTag;
 
 	for (XMBAttribute attr : attributes)
 	{
 		// If value is "null", then it is equivalent as never being entered
 		if (CStr(attr.Value) == "null")
 			continue;
 
 		// Ignore "type" and "style", we've already checked it
 		if (attr.Name == attr_type || attr.Name == attr_style)
 			continue;
 
 		if (attr.Name == attr_name)
 		{
 			CStr name(attr.Value);
 
 			for (const std::pair<CStr, CStr>& sub : NameSubst)
 				name.Replace(sub.first, sub.second);
 
 			object->SetName(name);
 			NameSet = true;
 			continue;
 		}
 
 		if (attr.Name == attr_hotkey)
 			hotkeyTag = attr.Value;
 
 		if (attr.Name == attr_z)
 			ManuallySetZ = true;
 
 		if (object->SetSetting(pFile->GetAttributeString(attr.Name), attr.Value.FromUTF8(), true) != PSRETURN_OK)
 			LOGERROR("GUI: (object: %s) Can't set \"%s\" to \"%s\"", object->GetPresentableName(), pFile->GetAttributeString(attr.Name), attr.Value);
 	}
 
 	// Check if name isn't set, generate an internal name in that case.
 	if (!NameSet)
 	{
 		object->SetName("__internal(" + CStr::FromInt(m_InternalNameNumber) + ")");
 		++m_InternalNameNumber;
 	}
 
 	if (!hotkeyTag.empty())
 		m_HotkeyObjects[hotkeyTag].push_back(object);
 
 	CStrW caption(Element.GetText().FromUTF8());
 	if (!caption.empty())
 		object->SetSetting("caption", caption, true);
 
 	for (XMBElement child : Element.GetChildNodes())
 	{
 		// Check what name the elements got
 		int element_name = child.GetNodeName();
 
 		if (element_name == elmt_object)
 		{
 			// Call this function on the child
 			Xeromyces_ReadObject(child, pFile, object, NameSubst, Paths, nesting_depth);
 		}
 		else if (element_name == elmt_action)
 		{
 			// Scripted <action> element
 
 			// Check for a 'file' parameter
 			CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8());
 
 			CStr code;
 
 			// If there is a file, open it and use it as the code
 			if (!filename.empty())
 			{
 				Paths.insert(filename);
 				CVFSFile scriptfile;
 				if (scriptfile.Load(g_VFS, filename) != PSRETURN_OK)
 				{
 					LOGERROR("Error opening GUI script action file '%s'", utf8_from_wstring(filename));
 					throw PSERROR_GUI_JSOpenFailed();
 				}
 
 				code = scriptfile.DecodeUTF8(); // assume it's UTF-8
 			}
 
 			XMBElementList grandchildren = child.GetChildNodes();
 			if (!grandchildren.empty()) // The <action> element contains <keep> and <translate> tags.
 				for (XMBElement grandchild : grandchildren)
 				{
 					if (grandchild.GetNodeName() == elmt_translate)
 						code += g_L10n.Translate(grandchild.GetText());
 					else if (grandchild.GetNodeName() == elmt_keep)
 						code += grandchild.GetText();
 				}
 			else // It???s pure JavaScript code.
 				// Read the inline code (concatenating to the file code, if both are specified)
 				code += CStr(child.GetText());
 
 			CStr action = CStr(child.GetAttributes().GetNamedItem(attr_on));
 
 			// We need to set the GUI this object belongs to because RegisterScriptHandler requires an associated GUI.
 			object->SetGUI(this);
 			object->RegisterScriptHandler(action.LowerCase(), code, this);
 		}
 		else if (element_name == elmt_repeat)
 		{
 			Xeromyces_ReadRepeat(child, pFile, object, NameSubst, Paths, nesting_depth);
 		}
 		else if (element_name == elmt_translatableAttribute)
 		{
 			// This is an element in the form ???<translatableAttribute id="attributeName">attributeValue</translatableAttribute>???.
 			CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
 			if (attributeName.empty())
 			{
 				LOGERROR("GUI: ???translatableAttribute??? XML element with empty ???id??? XML attribute found. (object: %s)", object->GetPresentableName().c_str());
 				continue;
 			}
 
 			CStr value(child.GetText());
 			if (value.empty())
 				continue;
 
 			CStr context(child.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
 			if (!context.empty())
 			{
 				CStr translatedValue(g_L10n.TranslateWithContext(context, value));
 				object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
 			}
 			else
 			{
 				CStr translatedValue(g_L10n.Translate(value));
 				object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
 			}
 		}
 		else if (element_name == elmt_attribute)
 		{
 			// This is an element in the form ???<attribute id="attributeName"><keep>Don???t translate this part
 			// </keep><translate>but translate this one.</translate></attribute>???.
 			CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
 			if (attributeName.empty())
 			{
 				LOGERROR("GUI: ???attribute??? XML element with empty ???id??? XML attribute found. (object: %s)", object->GetPresentableName().c_str());
 				continue;
 			}
 
 			CStr translatedValue;
 
 			for (XMBElement grandchild : child.GetChildNodes())
 			{
 				if (grandchild.GetNodeName() == elmt_translate)
 					translatedValue += g_L10n.Translate(grandchild.GetText());
 				else if (grandchild.GetNodeName() == elmt_keep)
 					translatedValue += grandchild.GetText();
 			}
 			object->SetSetting(attributeName, translatedValue.FromUTF8(), true);
 		}
 		else if (element_name == elmt_include)
 		{
 			CStrW filename(child.GetAttributes().GetNamedItem(attr_file).FromUTF8());
 			CStrW directory(child.GetAttributes().GetNamedItem(attr_directory).FromUTF8());
 			if (!filename.empty())
 			{
 				if (!directory.empty())
 					LOGWARNING("GUI: Include element found with file name (%s) and directory name (%s). Only the file will be processed.", utf8_from_wstring(filename), utf8_from_wstring(directory));
 
 				Paths.insert(filename);
 
 				CXeromyces XeroIncluded;
 				if (XeroIncluded.Load(g_VFS, filename, "gui") != PSRETURN_OK)
 				{
 					LOGERROR("GUI: Error reading included XML: '%s'", utf8_from_wstring(filename));
 					continue;
 				}
 
 				XMBElement node = XeroIncluded.GetRoot();
 				if (node.GetNodeName() != XeroIncluded.GetElementID("object"))
 				{
 					LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", utf8_from_wstring(filename));
 					continue;
 				}
 
 				if (nesting_depth+1 >= MAX_OBJECT_DEPTH)
 				{
 					LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(filename));
 					continue;
 				}
 
 				Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1);
 			}
 			else if (!directory.empty())
 			{
 				if (nesting_depth+1 >= MAX_OBJECT_DEPTH)
 				{
 					LOGERROR("GUI: Too many nested GUI includes. Probably caused by a recursive include attribute. Abort rendering '%s'.", utf8_from_wstring(directory));
 					continue;
 				}
 
 				VfsPaths pathnames;
 				vfs::GetPathnames(g_VFS, directory, L"*.xml", pathnames);
 				for (const VfsPath& path : pathnames)
 				{
 					// as opposed to loading scripts, don't care if it's loaded before
 					// one might use the same parts of the GUI in different situations
 					Paths.insert(path);
 					CXeromyces XeroIncluded;
 					if (XeroIncluded.Load(g_VFS, path, "gui") != PSRETURN_OK)
 					{
 						LOGERROR("GUI: Error reading included XML: '%s'", path.string8());
 						continue;
 					}
 
 					XMBElement node = XeroIncluded.GetRoot();
 					if (node.GetNodeName() != XeroIncluded.GetElementID("object"))
 					{
 						LOGERROR("GUI: Error reading included XML: '%s', root element must have be of type 'object'.", path.string8());
 						continue;
 					}
 					Xeromyces_ReadObject(node, &XeroIncluded, object, NameSubst, Paths, nesting_depth+1);
 				}
 
 			}
 			else
 				LOGERROR("GUI: 'include' XML element must have valid 'file' or 'directory' attribute found. (object %s)", object->GetPresentableName().c_str());
 		}
 		else
 		{
 			// Try making the object read the tag.
 			if (!object->HandleAdditionalChildren(child, pFile))
 				LOGERROR("GUI: (object: %s) Reading unknown children for its type", object->GetPresentableName().c_str());
 		}
 	}
 
 	if (!ManuallySetZ)
 	{
 		// Set it automatically to 10 plus its parents
 		bool absolute;
 		GUI<bool>::GetSetting(object, "absolute", absolute);
 
 		if (absolute)
 			// If the object is absolute, we'll have to get the parent's Z buffered,
 			// and add to that!
 			GUI<float>::SetSetting(object, "z", pParent->GetBufferedZ() + 10.f, true);
 		else
 			// If the object is relative, then we'll just store Z as "10"
 			GUI<float>::SetSetting(object, "z", 10.f, true);
 	}
 
 	try
 	{
 		if (pParent == m_BaseObject)
 			AddObject(object);
 		else
 			pParent->AddChild(object);
 	}
 	catch (PSERROR_GUI& e)
 	{
 		LOGERROR("GUI error: %s", e.what());
 	}
 }
 
 void CGUI::Xeromyces_ReadRepeat(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector<std::pair<CStr, CStr> >& NameSubst, boost::unordered_set<VfsPath>& Paths, u32 nesting_depth)
 {
 	#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
 	#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
 	ELMT(object);
 	ATTR(count);
 	ATTR(var);
 
 	XMBAttributeList attributes = Element.GetAttributes();
 
 	int count = CStr(attributes.GetNamedItem(attr_count)).ToInt();
 	CStr var("["+attributes.GetNamedItem(attr_var)+"]");
 	if (var.size() < 3)
 		var = "[n]";
 
 	for (int n = 0; n < count; ++n)
 	{
 		NameSubst.emplace_back(var, "[" + CStr::FromInt(n) + "]");
 
 		XERO_ITER_EL(Element, child)
 		{
 			if (child.GetNodeName() == elmt_object)
 				Xeromyces_ReadObject(child, pFile, pParent, NameSubst, Paths, nesting_depth);
 		}
 		NameSubst.pop_back();
 	}
 }
 
 void CGUI::Xeromyces_ReadScript(XMBElement Element, CXeromyces* pFile, boost::unordered_set<VfsPath>& Paths)
 {
 	// Check for a 'file' parameter
 	CStrW file(Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("file")).FromUTF8());
 
 	// If there is a file specified, open and execute it
 	if (!file.empty())
 	{
 		Paths.insert(file);
 		try
 		{
 			m_ScriptInterface->LoadGlobalScriptFile(file);
 		}
 		catch (PSERROR_Scripting& e)
 		{
 			LOGERROR("GUI: Error executing script %s: %s", utf8_from_wstring(file), e.what());
 		}
 	}
 
 	// If it has a directory attribute, read all JS files in that directory
 	CStrW directory(Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("directory")).FromUTF8());
 	if (!directory.empty())
 	{
 		VfsPaths pathnames;
 		vfs::GetPathnames(g_VFS, directory, L"*.js", pathnames);
 		for (const VfsPath& path : pathnames)
 		{
 			// Only load new files (so when the insert succeeds)
 			if (Paths.insert(path).second)
 				try
 				{
 					m_ScriptInterface->LoadGlobalScriptFile(path);
 				}
 				catch (PSERROR_Scripting& e)
 				{
 					LOGERROR("GUI: Error executing script %s: %s", path.string8(), e.what());
 				}
 		}
 	}
 
 	// Execute inline scripts
 	try
 	{
 		CStr code(Element.GetText());
 		if (!code.empty())
 			m_ScriptInterface->LoadGlobalScript(L"Some XML file", code.FromUTF8());
 	}
 	catch (PSERROR_Scripting& e)
 	{
 		LOGERROR("GUI: Error executing inline script: %s", e.what());
 	}
 }
 
 void CGUI::Xeromyces_ReadSprite(XMBElement Element, CXeromyces* pFile)
 {
 	CGUISprite* Sprite = new CGUISprite;
 
 	// Get name, we know it exists because of DTD requirements
 	CStr name = Element.GetAttributes().GetNamedItem(pFile->GetAttributeID("name"));
 
 	if (m_Sprites.find(name) != m_Sprites.end())
 		LOGWARNING("GUI sprite name '%s' used more than once; first definition will be discarded", name.c_str());
 
 	SGUIImageEffects* effects = NULL;
 
 	for (XMBElement child : Element.GetChildNodes())
 	{
 		CStr ElementName(pFile->GetElementString(child.GetNodeName()));
 
 		if (ElementName == "image")
 			Xeromyces_ReadImage(child, pFile, *Sprite);
 		else if (ElementName == "effect")
 		{
 			if (effects)
 				LOGERROR("GUI <sprite> must not have more than one <effect>");
 			else
 			{
 				effects = new SGUIImageEffects;
 				Xeromyces_ReadEffects(child, pFile, *effects);
 			}
 		}
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 
 	// Apply the effects to every image (unless the image overrides it with
 	// different effects)
 	if (effects)
 		for (SGUIImage* const& img : Sprite->m_Images)
 			if (!img->m_Effects)
 				img->m_Effects = new SGUIImageEffects(*effects); // do a copy just so it can be deleted correctly later
 
 	delete effects;
 
 	m_Sprites[name] = Sprite;
 }
 
 void CGUI::Xeromyces_ReadImage(XMBElement Element, CXeromyces* pFile, CGUISprite& parent)
 {
 	SGUIImage* Image = new SGUIImage;
 
 	Image->m_TextureSize = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
 	Image->m_Size = CClientArea(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
 
 	// TODO Gee: Setup defaults here (or maybe they are in the SGUIImage ctor)
 
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name(pFile->GetAttributeString(attr.Name));
 		CStrW attr_value(attr.Value.FromUTF8());
 
 		if (attr_name == "texture")
 		{
 			Image->m_TextureName = VfsPath("art/textures/ui") / attr_value;
 		}
 		else if (attr_name == "size")
 		{
 			CClientArea ca;
 			if (!GUI<CClientArea>::ParseString(attr_value, ca))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_Size = ca;
 		}
 		else if (attr_name == "texture_size")
 		{
 			CClientArea ca;
 			if (!GUI<CClientArea>::ParseString(attr_value, ca))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_TextureSize = ca;
 		}
 		else if (attr_name == "real_texture_placement")
 		{
 			CRect rect;
 			if (!GUI<CRect>::ParseString(attr_value, rect))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_TexturePlacementInFile = rect;
 		}
 		else if (attr_name == "cell_size")
 		{
 			CSize size;
 			if (!GUI<CSize>::ParseString(attr_value, size))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_CellSize = size;
 		}
 		else if (attr_name == "fixed_h_aspect_ratio")
 		{
 			float val;
 			if (!GUI<float>::ParseString(attr_value, val))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_FixedHAspectRatio = val;
 		}
 		else if (attr_name == "round_coordinates")
 		{
 			bool b;
 			if (!GUI<bool>::ParseString(attr_value, b))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_RoundCoordinates = b;
 		}
 		else if (attr_name == "wrap_mode")
 		{
 			if (attr_value == L"repeat")
 				Image->m_WrapMode = GL_REPEAT;
 			else if (attr_value == L"mirrored_repeat")
 				Image->m_WrapMode = GL_MIRRORED_REPEAT;
 			else if (attr_value == L"clamp_to_edge")
 				Image->m_WrapMode = GL_CLAMP_TO_EDGE;
 			else
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 		}
 		else if (attr_name == "z_level")
 		{
 			float z_level;
 			if (!GUI<float>::ParseString(attr_value, z_level))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_DeltaZ = z_level/100.f;
 		}
 		else if (attr_name == "backcolor")
 		{
 			CColor color;
 			if (!GUI<CColor>::ParseString(attr_value, color))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_BackColor = color;
 		}
 		else if (attr_name == "bordercolor")
 		{
 			CColor color;
 			if (!GUI<CColor>::ParseString(attr_value, color))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_BorderColor = color;
 		}
 		else if (attr_name == "border")
 		{
 			bool b;
 			if (!GUI<bool>::ParseString(attr_value, b))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else
 				Image->m_Border = b;
 		}
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 
 	// Look for effects
 	for (XMBElement child : Element.GetChildNodes())
 	{
 		CStr ElementName(pFile->GetElementString(child.GetNodeName()));
 		if (ElementName == "effect")
 		{
 			if (Image->m_Effects)
 				LOGERROR("GUI <image> must not have more than one <effect>");
 			else
 			{
 				Image->m_Effects = new SGUIImageEffects;
 				Xeromyces_ReadEffects(child, pFile, *Image->m_Effects);
 			}
 		}
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 
 	parent.AddImage(Image);
 }
 
 void CGUI::Xeromyces_ReadEffects(XMBElement Element, CXeromyces* pFile, SGUIImageEffects& effects)
 {
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name(pFile->GetAttributeString(attr.Name));
 		CStrW attr_value(attr.Value.FromUTF8());
 
 		if (attr_name == "add_color")
 		{
 			CColor color;
 			if (!GUI<int>::ParseColor(attr_value, color, 0))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, utf8_from_wstring(attr_value));
 			else effects.m_AddColor = color;
 		}
 		else if (attr_name == "grayscale")
 			effects.m_Greyscale = true;
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 }
 
 void CGUI::Xeromyces_ReadStyle(XMBElement Element, CXeromyces* pFile)
 {
 	SGUIStyle style;
 	CStr name;
 
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name(pFile->GetAttributeString(attr.Name));
 
 		// The "name" setting is actually the name of the style
 		//  and not a new default
 		if (attr_name == "name")
 			name = attr.Value;
 		else
 			style.m_SettingsDefaults[attr_name] = attr.Value.FromUTF8();
 	}
 
 	m_Styles[name] = style;
 }
 
 void CGUI::Xeromyces_ReadScrollBarStyle(XMBElement Element, CXeromyces* pFile)
 {
 	SGUIScrollBarStyle scrollbar;
 	CStr name;
 
 	// Setup some defaults.
 	scrollbar.m_MinimumBarSize = 0.f;
 	// Using 1.0e10 as a substitute for infinity
 	scrollbar.m_MaximumBarSize = 1.0e10;
 	scrollbar.m_UseEdgeButtons = false;
 
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name = pFile->GetAttributeString(attr.Name);
 		CStr attr_value(attr.Value);
 
 		if (attr_value == "null")
 			continue;
 
 		if (attr_name == "name")
 			name = attr_value;
 		else if (attr_name == "show_edge_buttons")
 		{
 			bool b;
 			if (!GUI<bool>::ParseString(attr_value.FromUTF8(), b))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
 			else
 				scrollbar.m_UseEdgeButtons = b;
 		}
 		else if (attr_name == "width")
 		{
 			float f;
 			if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
 			else
 				scrollbar.m_Width = f;
 		}
 		else if (attr_name == "minimum_bar_size")
 		{
 			float f;
 			if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
 			else
 				scrollbar.m_MinimumBarSize = f;
 		}
 		else if (attr_name == "maximum_bar_size")
 		{
 			float f;
 			if (!GUI<float>::ParseString(attr_value.FromUTF8(), f))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name, attr_value);
 			else
 				scrollbar.m_MaximumBarSize = f;
 		}
 		else if (attr_name == "sprite_button_top")
 			scrollbar.m_SpriteButtonTop = attr_value;
 		else if (attr_name == "sprite_button_top_pressed")
 			scrollbar.m_SpriteButtonTopPressed = attr_value;
 		else if (attr_name == "sprite_button_top_disabled")
 			scrollbar.m_SpriteButtonTopDisabled = attr_value;
 		else if (attr_name == "sprite_button_top_over")
 			scrollbar.m_SpriteButtonTopOver = attr_value;
 		else if (attr_name == "sprite_button_bottom")
 			scrollbar.m_SpriteButtonBottom = attr_value;
 		else if (attr_name == "sprite_button_bottom_pressed")
 			scrollbar.m_SpriteButtonBottomPressed = attr_value;
 		else if (attr_name == "sprite_button_bottom_disabled")
 			scrollbar.m_SpriteButtonBottomDisabled = attr_value;
 		else if (attr_name == "sprite_button_bottom_over")
 			scrollbar.m_SpriteButtonBottomOver = attr_value;
 		else if (attr_name == "sprite_back_vertical")
 			scrollbar.m_SpriteBackVertical = attr_value;
 		else if (attr_name == "sprite_bar_vertical")
 			scrollbar.m_SpriteBarVertical = attr_value;
 		else if (attr_name == "sprite_bar_vertical_over")
 			scrollbar.m_SpriteBarVerticalOver = attr_value;
 		else if (attr_name == "sprite_bar_vertical_pressed")
 			scrollbar.m_SpriteBarVerticalPressed = attr_value;
 	}
 
 	m_ScrollBarStyles[name] = scrollbar;
 }
 
 void CGUI::Xeromyces_ReadIcon(XMBElement Element, CXeromyces* pFile)
 {
 	SGUIIcon icon;
 	CStr name;
 
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name(pFile->GetAttributeString(attr.Name));
 		CStr attr_value(attr.Value);
 
 		if (attr_value == "null")
 			continue;
 
 		if (attr_name == "name")
 			name = attr_value;
 		else if (attr_name == "sprite")
 			icon.m_SpriteName = attr_value;
 		else if (attr_name == "size")
 		{
 			CSize size;
 			if (!GUI<CSize>::ParseString(attr_value.FromUTF8(), size))
 				LOGERROR("Error parsing '%s' (\"%s\") inside <icon>.", attr_name, attr_value);
 			else
 				icon.m_Size = size;
 		}
 		else if (attr_name == "cell_id")
 		{
 			int cell_id;
 			if (!GUI<int>::ParseString(attr_value.FromUTF8(), cell_id))
 				LOGERROR("GUI: Error parsing '%s' (\"%s\") inside <icon>.", attr_name, attr_value);
 			else
 				icon.m_CellID = cell_id;
 		}
 		else
 			debug_warn(L"Invalid data - DTD shouldn't allow this");
 	}
 
 	m_Icons[name] = icon;
 }
 
 void CGUI::Xeromyces_ReadTooltip(XMBElement Element, CXeromyces* pFile)
 {
 	IGUIObject* object = new CTooltip;
 
 	for (XMBAttribute attr : Element.GetAttributes())
 	{
 		CStr attr_name(pFile->GetAttributeString(attr.Name));
 		CStr attr_value(attr.Value);
 
 		if (attr_name == "name")
 			object->SetName("__tooltip_" + attr_value);
 		else
 			object->SetSetting(attr_name, attr_value.FromUTF8());
 	}
 
 	AddObject(object);
 }
 
 void CGUI::Xeromyces_ReadColor(XMBElement Element, CXeromyces* pFile)
 {
 	XMBAttributeList attributes = Element.GetAttributes();
 
 	CColor color;
 	CStr name = attributes.GetNamedItem(pFile->GetAttributeID("name"));
 
 	// Try parsing value
 	CStr value(Element.GetText());
 	if (value.empty())
 		return;
 
 	// Try setting color to value
 	if (!color.ParseString(value))
 	{
 		LOGERROR("GUI: Unable to create custom color '%s'. Invalid color syntax.", name.c_str());
 		return;
 	}
 
 	m_PreDefinedColors[name] = color;
 }
Index: source/gui/scripting/GuiScriptConversions.cpp
===================================================================
--- source/gui/scripting/GuiScriptConversions.cpp	(revision 19922)
+++ source/gui/scripting/GuiScriptConversions.cpp	(working copy)
@@ -1,145 +1,147 @@
 /* Copyright (C) 2017 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "precompiled.h"
 
 #include "scriptinterface/ScriptConversions.h"
 
 #include "gui/IGUIObject.h"
 #include "lib/external_libraries/libsdl.h"
 #include "ps/Hotkey.h"
 #include "maths/Vector2D.h"
 
 #define SET(obj, name, value) STMT(JS::RootedValue v_(cx); AssignOrToJSVal(cx, &v_, (value)); JS_SetProperty(cx, obj, (name), v_))
 	// ignore JS_SetProperty return value, because errors should be impossible
 	// and we can't do anything useful in the case of errors anyway
 
+extern float g_DpiScale;
+
 template<> void ScriptInterface::ToJSVal<SDL_Event_>(JSContext* cx, JS::MutableHandleValue ret, SDL_Event_ const& val)
 {
 	JSAutoRequest rq(cx);
 	const char* typeName;
 
 	switch (val.ev.type)
 	{
 	case SDL_WINDOWEVENT: typeName = "windowevent"; break;
 	case SDL_KEYDOWN: typeName = "keydown"; break;
 	case SDL_KEYUP: typeName = "keyup"; break;
 	case SDL_MOUSEMOTION: typeName = "mousemotion"; break;
 	case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break;
 	case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break;
 	case SDL_QUIT: typeName = "quit"; break;
 	case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break;
 	case SDL_HOTKEYUP: typeName = "hotkeyup"; break;
 	default: typeName = "(unknown)"; break;
 	}
 
 	JS::RootedObject obj(cx, JS_NewPlainObject(cx));
 	if (!obj)
 	{
 		ret.setUndefined();
 		return;
 	}
 
 	SET(obj, "type", typeName);
 
 	switch (val.ev.type)
 	{
 	case SDL_KEYDOWN:
 	case SDL_KEYUP:
 	{
 		// SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h)
 		// SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h)
 
 		JS::RootedObject keysym(cx, JS_NewPlainObject(cx));
 		if (!keysym)
 		{
 			ret.setUndefined();
 			return;
 		}
 		JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym));
 		JS_SetProperty(cx, obj, "keysym", keysymVal);
 
 		// SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h)
 		SET(keysym, "sym", (int)val.ev.key.keysym.sym);
 		// SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h)
 		{
 			SET(keysym, "unicode", JS::UndefinedHandleValue);
 		}
 		// TODO: scripts have no idea what all the key/mod enum values are;
 		// we should probably expose them as constants if we expect scripts to use them
 
 		break;
 	}
 	case SDL_MOUSEMOTION:
 	{
 		// SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h)
 		// SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h)
-		SET(obj, "x", (int)val.ev.motion.x);
-		SET(obj, "y", (int)val.ev.motion.y);
+		SET(obj, "x", static_cast<int>(val.ev.motion.x * g_DpiScale));
+		SET(obj, "y", static_cast<int>(val.ev.motion.y * g_DpiScale));
 		// SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h)
 		// SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h)
 		break;
 	}
 	case SDL_MOUSEBUTTONDOWN:
 	case SDL_MOUSEBUTTONUP:
 	{
 		// SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h)
 		SET(obj, "button", (int)val.ev.button.button);
 		SET(obj, "state", (int)val.ev.button.state);
-		SET(obj, "x", (int)val.ev.button.x);
-		SET(obj, "y", (int)val.ev.button.y);
+		SET(obj, "x", static_cast<int>(val.ev.button.x * g_DpiScale));
+		SET(obj, "y", static_cast<int>(val.ev.button.y * g_DpiScale));
 		SET(obj, "clicks", (int)val.ev.button.clicks);
 		break;
 	}
 	case SDL_HOTKEYDOWN:
 	case SDL_HOTKEYUP:
 	{
 		SET(obj, "hotkey", static_cast<const char*>(val.ev.user.data1));
 		break;
 	}
 	}
 
 	ret.setObject(*obj);
 }
 
 template<> void ScriptInterface::ToJSVal<IGUIObject*>(JSContext* UNUSED(cx), JS::MutableHandleValue ret, IGUIObject* const& val)
 {
 	if (val == NULL)
 		ret.setNull();
 	else
 		ret.setObject(*val->GetJSObject());
 }
 
 template<> void ScriptInterface::ToJSVal<CGUIString>(JSContext* cx, JS::MutableHandleValue ret, const CGUIString& val)
 {
 	ScriptInterface::ToJSVal(cx, ret, val.GetOriginalString());
 }
 
 template<> bool ScriptInterface::FromJSVal<CGUIString>(JSContext* cx, JS::HandleValue v, CGUIString& out)
 {
 	std::wstring val;
 	if (!ScriptInterface::FromJSVal(cx, v, val))
 		return false;
 	out.SetValue(val);
 	return true;
 }
 
 // define some vectors
 JSVAL_VECTOR(CVector2D)
 JSVAL_VECTOR(std::vector<CVector2D>)
 JSVAL_VECTOR(CGUIString)
 
Index: source/ps/GameSetup/Config.cpp
===================================================================
--- source/ps/GameSetup/Config.cpp	(revision 19922)
+++ source/ps/GameSetup/Config.cpp	(working copy)
@@ -1,211 +1,212 @@
 /* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "precompiled.h"
 
 #include "Config.h"
 
 #include "lib/timer.h"
 #include "ps/CConsole.h"
 #include "ps/CLogger.h"
 #include "ps/ConfigDB.h"
 #include "ps/GameSetup/CmdLineArgs.h"
 
 // (these variables are documented in the header.)
 
 const wchar_t g_DefaultCursor[] = L"default-arrow";
 
 CStrW g_CursorName = g_DefaultCursor;
 
 bool g_NoGLS3TC = false;
 bool g_NoGLAutoMipmap = false;
 bool g_NoGLVBO = false;
 
 bool g_PauseOnFocusLoss = false;
 
 bool g_RenderActors = true;
 
 bool g_Shadows = false;
 bool g_ShadowPCF = false;
 
 bool g_WaterUgly = false;
 bool g_WaterFancyEffects = false;
 bool g_WaterRealDepth = false;
 bool g_WaterRefraction = false;
 bool g_WaterReflection = false;
 bool g_WaterShadows = false;
 
 bool g_Particles = false;
 bool g_Silhouettes = false;
 bool g_ShowSky = false;
 
 bool g_PreferGLSL = false;
 bool g_PostProc = false;
 bool g_SmoothLOS = false;
 
 float g_Gamma = 1.0f;
 
 CStr g_RenderPath = "default";
 
 int g_xres, g_yres;
 float g_GuiScale = 1.0f;
+float g_DpiScale = 1.0f;
 bool g_VSync = false;
 
 bool g_Quickstart = false;
 bool g_DisableAudio = false;
 
 // flag to switch on drawing terrain overlays
 bool g_ShowPathfindingOverlay = false;
 
 // flag to switch on triangulation pathfinding
 bool g_TriPathfind = false;
 
 
 // If non-empty, specified map will be automatically loaded
 CStr g_AutostartMap = "";
 
 
 //----------------------------------------------------------------------------
 // config
 //----------------------------------------------------------------------------
 
 // Fill in the globals from the config files.
 static void LoadGlobals()
 {
 	CFG_GET_VAL("vsync", g_VSync);
 
 	CFG_GET_VAL("nos3tc", g_NoGLS3TC);
 	CFG_GET_VAL("noautomipmap", g_NoGLAutoMipmap);
 	CFG_GET_VAL("novbo", g_NoGLVBO);
 	CFG_GET_VAL("pauseonfocusloss", g_PauseOnFocusLoss);
 	CFG_GET_VAL("renderactors", g_RenderActors);
 	CFG_GET_VAL("shadows", g_Shadows);
 	CFG_GET_VAL("shadowpcf", g_ShadowPCF);
 
 	CFG_GET_VAL("waterugly", g_WaterUgly);
 	CFG_GET_VAL("waterfancyeffects", g_WaterFancyEffects);
 	CFG_GET_VAL("waterrealdepth", g_WaterRealDepth);
 	CFG_GET_VAL("waterrefraction", g_WaterRefraction);
 	CFG_GET_VAL("waterreflection", g_WaterReflection);
 	CFG_GET_VAL("watershadows", g_WaterShadows);
 
 	CFG_GET_VAL("renderpath", g_RenderPath);
 	CFG_GET_VAL("particles", g_Particles);
 	CFG_GET_VAL("silhouettes", g_Silhouettes);
 	CFG_GET_VAL("showsky", g_ShowSky);
 	CFG_GET_VAL("preferglsl", g_PreferGLSL);
 	CFG_GET_VAL("postproc", g_PostProc);
 	CFG_GET_VAL("smoothlos", g_SmoothLOS);
 	CFG_GET_VAL("gui.scale", g_GuiScale);
 }
 
 
 static void ProcessCommandLineArgs(const CmdLineArgs& args)
 {
 	// TODO: all these options (and the ones processed elsewhere) should
 	// be documented somewhere for users.
 
 	// Handle "-conf=key:value" (potentially multiple times)
 	std::vector<CStr> conf = args.GetMultiple("conf");
 	for (size_t i = 0; i < conf.size(); ++i)
 	{
 		CStr name_value = conf[i];
 		if (name_value.Find(':') != -1)
 		{
 			CStr name = name_value.BeforeFirst(":");
 			CStr value = name_value.AfterFirst(":");
 			g_ConfigDB.SetValueString(CFG_COMMAND, name, value);
 		}
 	}
 
 	if (args.Has("g"))
 	{
 		g_Gamma = args.Get("g").ToFloat();
 		if (g_Gamma == 0.0f)
 			g_Gamma = 1.0f;
 	}
 
 //	if (args.Has("listfiles"))
 //		trace_enable(true);
 
 	if (args.Has("profile"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "profile", args.Get("profile"));
 
 	if (args.Has("quickstart"))
 	{
 		g_Quickstart = true;
 		g_DisableAudio = true; // do this for backward-compatibility with user expectations
 	}
 
 	if (args.Has("nosound"))
 		g_DisableAudio = true;
 
 	if (args.Has("shadows"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "shadows", "true");
 
 	if (args.Has("xres"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "xres", args.Get("xres"));
 
 	if (args.Has("yres"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "yres", args.Get("yres"));
 
 	if (args.Has("vsync"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "vsync", "true");
 
 	if (args.Has("ooslog"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "ooslog", "true");
 
 	if (args.Has("serializationtest"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "serializationtest", "true");
 
 	if (args.Has("rejointest"))
 		g_ConfigDB.SetValueString(CFG_COMMAND, "rejointest", args.Get("rejointest"));
 }
 
 
 void CONFIG_Init(const CmdLineArgs& args)
 {
 	TIMER(L"CONFIG_Init");
 
 	new CConfigDB;
 
 	// Load the global, default config file
 	g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg");
 	g_ConfigDB.Reload(CFG_DEFAULT);	// 216ms
 	// Try loading the local system config file (which doesn't exist by
 	// default) - this is designed as a way of letting developers edit the
 	// system config without accidentally committing their changes back to SVN.
 	g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg");
 	g_ConfigDB.Reload(CFG_SYSTEM);
 
 	g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg");
 	g_ConfigDB.Reload(CFG_USER);
 
 	g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg");
 	// No point in reloading mod.cfg here - we haven't mounted mods yet
 
 	ProcessCommandLineArgs(args);
 
 	// Initialise console history file
 	int max_history_lines = 200;
 	CFG_GET_VAL("console.history.size", max_history_lines);
 	g_Console->UseHistoryFile(L"config/console.txt", max_history_lines);
 
 	// Collect information from system.cfg, the profile file,
 	// and any command-line overrides to fill in the globals.
 	LoadGlobals();	// 64ms
 }
Index: source/ps/GameSetup/Config.h
===================================================================
--- source/ps/GameSetup/Config.h	(revision 19922)
+++ source/ps/GameSetup/Config.h	(working copy)
@@ -1,100 +1,101 @@
 /* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifndef INCLUDED_PS_GAMESETUP_CONFIG
 #define INCLUDED_PS_GAMESETUP_CONFIG
 
 #include "ps/CStr.h"
 
 
 //-----------------------------------------------------------------------------
 // prevent various OpenGL features from being used. this allows working
 // around issues like buggy drivers.
 
 // when loading S3TC-compressed texture files, do not pass them directly to
 // OpenGL; instead, decompress them via software to regular textures.
 // (necessary on JW's S3 laptop graphics card -- oh, the irony)
 extern bool g_NoGLS3TC;
 
 // do not ask OpenGL to create mipmaps; instead, generate them in software
 // and upload them all manually. (potentially helpful for PT's system, where
 // Mesa falsely reports full S3TC support but isn't able to generate mipmaps
 // for them)
 extern bool g_NoGLAutoMipmap;
 
 // don't use VBOs. (RC: that was necessary on laptop Radeon cards)
 extern bool g_NoGLVBO;
 
 //-----------------------------------------------------------------------------
 
 // flag to pause the game on window focus loss
 extern bool g_PauseOnFocusLoss;
 
 // flag to switch on actor rendering
 extern bool g_RenderActors;
 
 // flag to switch on shadows
 extern bool g_Shadows;
 
 // Force the use of the fixed function for rendering water.
 extern bool g_WaterUgly;
 // Add foam and waves near the shores, trails following ships, and other HQ things.
 extern bool g_WaterFancyEffects;
 // Use real depth for water rendering.
 extern bool g_WaterRealDepth;
 // Use a real refraction map and not transparency.
 extern bool g_WaterRefraction;
 // Use a real reflection map and not a skybox texture.
 extern bool g_WaterReflection;
 // Enable on-water shadows.
 extern bool g_WaterShadows;
 
 // flag to switch on shadow PCF
 extern bool g_ShadowPCF;
 // flag to switch on particles rendering
 extern bool g_Particles;
 // flag to switch on unit silhouettes
 extern bool g_Silhouettes;
 // flag to switch on sky rendering
 extern bool g_ShowSky;
 
 // Prefer GLSL shaders over ARB shaders
 extern bool g_PreferGLSL;
 // Use screen-space postprocessing filters (HDR, bloom, DOF, etc)
 extern bool g_PostProc;
 // Use smooth LOS interpolation
 extern bool g_SmoothLOS;
 
 extern float g_Gamma;
 // name of configured render path (depending on OpenGL extensions, this may not be
 // the render path that is actually in use right now)
 extern CStr g_RenderPath;
 
 extern int g_xres, g_yres;
 extern float g_GuiScale;
+extern float g_DpiScale;
 extern bool g_VSync;
 
 extern bool g_Quickstart;
 extern bool g_DisableAudio;
 
 extern CStrW g_CursorName;
 extern const wchar_t g_DefaultCursor[];
 
 class CmdLineArgs;
 extern void CONFIG_Init(const CmdLineArgs& args);
 
 #endif // INCLUDED_PS_GAMESETUP_CONFIG
Index: source/ps/Globals.cpp
===================================================================
--- source/ps/Globals.cpp	(revision 19922)
+++ source/ps/Globals.cpp	(working copy)
@@ -1,96 +1,96 @@
 /* Copyright (C) 2017 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "precompiled.h"
 #include "Globals.h"
 
 #include "network/NetClient.h"
 #include "ps/GameSetup/Config.h"
 #include "soundmanager/ISoundManager.h"
 
 bool g_app_minimized = false;
 bool g_app_has_focus = true;
 
 std::map<int32_t, bool> g_keys;
 int g_mouse_x = 50, g_mouse_y = 50;
 bool g_mouse_active = true;
 
 // g_mouse_buttons[0] is unused. The order of entries is as in KeyName.h for MOUSE_*
 bool g_mouse_buttons[MOUSE_LAST - MOUSE_BASE] = {0};
 
 PIFrequencyFilter g_frequencyFilter;
 
 // updates the state of the above; never swallows messages.
 InReaction GlobalsInputHandler(const SDL_Event_* ev)
 {
 	size_t c;
 
 	switch(ev->ev.type)
 	{
 	case SDL_WINDOWEVENT:
 		switch(ev->ev.window.event)
 		{
 		case SDL_WINDOWEVENT_MINIMIZED:
 			g_app_minimized = true;
 			break;
 		case SDL_WINDOWEVENT_EXPOSED:
 		case SDL_WINDOWEVENT_RESTORED:
 			g_app_minimized = false;
 			break;
 		case SDL_WINDOWEVENT_FOCUS_GAINED:
 			g_app_has_focus = true;
 			break;
 		case SDL_WINDOWEVENT_FOCUS_LOST:
 			g_app_has_focus = false;
 			break;
 		case SDL_WINDOWEVENT_ENTER:
 			g_mouse_active = true;
 			break;
 		case SDL_WINDOWEVENT_LEAVE:
 			g_mouse_active = false;
 			break;
 		}
 		return IN_PASS;
 
 	case SDL_MOUSEMOTION:
-		g_mouse_x = ev->ev.motion.x;
-		g_mouse_y = ev->ev.motion.y;
+		g_mouse_x = static_cast<int>(ev->ev.motion.x * g_DpiScale);
+		g_mouse_y = static_cast<int>(ev->ev.motion.y * g_DpiScale);
 		return IN_PASS;
 
 	case SDL_MOUSEBUTTONDOWN:
 	case SDL_MOUSEBUTTONUP:
 		c = ev->ev.button.button;
 		if(c < ARRAY_SIZE(g_mouse_buttons))
 			g_mouse_buttons[c] = (ev->ev.type == SDL_MOUSEBUTTONDOWN);
 		else
 		{
 			// don't complain: just ignore people with too many mouse buttons
 			//debug_warn(L"invalid mouse button");
 		}
 		return IN_PASS;
 
 	case SDL_KEYDOWN:
 	case SDL_KEYUP:
 		g_keys[ev->ev.key.keysym.sym] = (ev->ev.type == SDL_KEYDOWN);
 		return IN_PASS;
 
 	default:
 		return IN_PASS;
 	}
 
 	UNREACHABLE;
 }
Index: source/ps/VideoMode.cpp
===================================================================
--- source/ps/VideoMode.cpp	(revision 19922)
+++ source/ps/VideoMode.cpp	(working copy)
@@ -1,490 +1,502 @@
 /* Copyright (C) 2015 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "precompiled.h"
 
 #include "VideoMode.h"
 
 #include "graphics/Camera.h"
 #include "graphics/GameView.h"
 #include "gui/GUIManager.h"
 #include "lib/config2.h"
 #include "lib/ogl.h"
 #include "lib/external_libraries/libsdl.h"
 #include "lib/sysdep/gfx.h"
 #include "ps/CConsole.h"
 #include "ps/CLogger.h"
 #include "ps/ConfigDB.h"
 #include "ps/Game.h"
 #include "ps/GameSetup/Config.h"
 #include "renderer/Renderer.h"
 
 #if OS_MACOSX
 # include "lib/sysdep/os/osx/osx_sys_version.h"
 #endif
 
 
 static int DEFAULT_WINDOW_W = 1024;
 static int DEFAULT_WINDOW_H = 768;
 
 static int DEFAULT_FULLSCREEN_W = 1024;
 static int DEFAULT_FULLSCREEN_H = 768;
 
 CVideoMode g_VideoMode;
 
 CVideoMode::CVideoMode() :
 	m_IsFullscreen(false), m_IsInitialised(false), m_Window(NULL),
 	m_PreferredW(0), m_PreferredH(0), m_PreferredBPP(0), m_PreferredFreq(0),
-	m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
+	m_ConfigW(0), m_ConfigH(0), m_ConfigBPP(0), m_ConfigHighDPI(false), m_ConfigFullscreen(false), m_ConfigForceS3TCEnable(true),
 	m_WindowedW(DEFAULT_WINDOW_W), m_WindowedH(DEFAULT_WINDOW_H), m_WindowedX(0), m_WindowedY(0)
 {
 	// (m_ConfigFullscreen defaults to false, so users don't get stuck if
 	// e.g. half the filesystem is missing and the config files aren't loaded)
 }
 
 void CVideoMode::ReadConfig()
 {
 	bool windowed = !m_ConfigFullscreen;
 	CFG_GET_VAL("windowed", windowed);
 	m_ConfigFullscreen = !windowed;
 
 	CFG_GET_VAL("xres", m_ConfigW);
 	CFG_GET_VAL("yres", m_ConfigH);
+	CFG_GET_VAL("high_dpi", m_ConfigHighDPI);
 	CFG_GET_VAL("bpp", m_ConfigBPP);
 	CFG_GET_VAL("display", m_ConfigDisplay);
 	CFG_GET_VAL("force_s3tc_enable", m_ConfigForceS3TCEnable);
 }
 
 bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
 {
 	Uint32 flags = 0;
 	if (fullscreen)
 		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
 
 	if (!m_Window)
 	{
 		// Note: these flags only take affect in SDL_CreateWindow
 		flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
+		if (m_ConfigHighDPI)
+			flags |= SDL_WINDOW_ALLOW_HIGHDPI;
 		m_WindowedX = m_WindowedY = SDL_WINDOWPOS_CENTERED_DISPLAY(m_ConfigDisplay);
 
 		m_Window = SDL_CreateWindow("0 A.D.", m_WindowedX, m_WindowedY, w, h, flags);
 		if (!m_Window)
 		{
 			// If fullscreen fails, try windowed mode
 			if (fullscreen)
 			{
 				LOGWARNING("Failed to set the video mode to fullscreen for the chosen resolution "
 					"%dx%d:%d (\"%hs\"), falling back to windowed mode",
 					w, h, bpp, SDL_GetError());
 				// Using default size for the window for now, as the attempted setting
 				// could be as large, or larger than the screen size.
 				return SetVideoMode(DEFAULT_WINDOW_W, DEFAULT_WINDOW_H, bpp, false);
 			}
 			else
 			{
 				LOGERROR("SetVideoMode failed in SDL_CreateWindow: %dx%d:%d %d (\"%s\")",
 					w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
 				return false;
 			}
 		}
 
 		if (SDL_SetWindowDisplayMode(m_Window, NULL) < 0)
 		{
 			LOGERROR("SetVideoMode failed in SDL_SetWindowDisplayMode: %dx%d:%d %d (\"%s\")",
 				w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
 			return false;
 		}
 
 		SDL_GLContext context = SDL_GL_CreateContext(m_Window);
 		if (!context)
 		{
 			LOGERROR("SetVideoMode failed in SDL_GL_CreateContext: %dx%d:%d %d (\"%s\")",
 				w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
 			return false;
 		}
 	}
 	else
 	{
 		if (m_IsFullscreen != fullscreen)
 		{
 			if (!fullscreen)
 			{
 				// For some reason, when switching from fullscreen to windowed mode,
 				// we have to set the window size and position before and after switching
 				SDL_SetWindowSize(m_Window, w, h);
 				SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
 			}
 
 			if (SDL_SetWindowFullscreen(m_Window, flags) < 0)
 			{
 				LOGERROR("SetVideoMode failed in SDL_SetWindowFullscreen: %dx%d:%d %d (\"%s\")",
 					w, h, bpp, fullscreen ? 1 : 0, SDL_GetError());
 				return false;
 			}
 		}
 
 		if (!fullscreen)
 		{
 			SDL_SetWindowSize(m_Window, w, h);
 			SDL_SetWindowPosition(m_Window, m_WindowedX, m_WindowedY);
 		}
 	}
 
 	// Grab the current video settings
 	SDL_GetWindowSize(m_Window, &m_CurrentW, &m_CurrentH);
 	m_CurrentBPP = bpp;
+	UpdateRenderer();
 
 	if (fullscreen)
 		SDL_SetWindowGrab(m_Window, SDL_TRUE);
 	else
 		SDL_SetWindowGrab(m_Window, SDL_FALSE);
 
 	m_IsFullscreen = fullscreen;
 
-	g_xres = m_CurrentW;
-	g_yres = m_CurrentH;
-
 	return true;
 }
 
 bool CVideoMode::InitSDL()
 {
 	ENSURE(!m_IsInitialised);
 
 	ReadConfig();
 
 	EnableS3TC();
 
 	// preferred video mode = current desktop settings
 	// (command line params may override these)
 	gfx::GetVideoMode(&m_PreferredW, &m_PreferredH, &m_PreferredBPP, &m_PreferredFreq);
 
 	int w = m_ConfigW;
 	int h = m_ConfigH;
 
 	if (m_ConfigFullscreen)
 	{
 		// If fullscreen and no explicit size set, default to the desktop resolution
 		if (w == 0 || h == 0)
 		{
 			w = m_PreferredW;
 			h = m_PreferredH;
 		}
 	}
 
 	// If no size determined, default to something sensible
 	if (w == 0 || h == 0)
 	{
 		w = DEFAULT_WINDOW_W;
 		h = DEFAULT_WINDOW_H;
 	}
 
 	if (!m_ConfigFullscreen)
 	{
 		// Limit the window to the screen size (if known)
 		if (m_PreferredW)
 			w = std::min(w, m_PreferredW);
 		if (m_PreferredH)
 			h = std::min(h, m_PreferredH);
 	}
 
 	int bpp = GetBestBPP();
 
 	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
 #if CONFIG2_GLES
 	// Require GLES 2.0
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
 #endif
 
 	if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
 	{
 		// Fall back to a smaller depth buffer
 		// (The rendering may be ugly but this helps when running in VMware)
 		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
 
 		if (!SetVideoMode(w, h, bpp, m_ConfigFullscreen))
 			return false;
 	}
 
 	SDL_GL_SetSwapInterval(g_VSync ? 1 : 0);
 
 	// Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13).
 	// The driver appears to register its own atexit hook on context creation.
 	// If this atexit hook is called before SDL_Quit destroys the OpenGL context,
 	// some kind of double-free problem causes a crash and lockup in the driver.
 	// Calling SDL_Quit twice appears to be harmless, though, and avoids the problem
 	// by destroying the context *before* the driver's atexit hook is called.
 	// (Note that atexit hooks are guaranteed to be called in reverse order of their registration.)
 	atexit(SDL_Quit);
 	// End work around.
 
 	ogl_Init(); // required after each mode change
 	// (TODO: does that mean we need to call this when toggling fullscreen later?)
 
 #if !OS_ANDROID
 	u16 ramp[256];
 	SDL_CalculateGammaRamp(g_Gamma, ramp);
 	if (SDL_SetWindowGammaRamp(m_Window, ramp, ramp, ramp) < 0)
 		LOGWARNING("SDL_SetWindowGammaRamp failed");
 #endif
 
 	m_IsInitialised = true;
 
 	if (!m_ConfigFullscreen)
 	{
 		m_WindowedW = w;
 		m_WindowedH = h;
 	}
 
 	return true;
 }
 
 bool CVideoMode::InitNonSDL()
 {
 	ENSURE(!m_IsInitialised);
 
 	ReadConfig();
 
 	EnableS3TC();
 
 	m_IsInitialised = true;
 
 	return true;
 }
 
 void CVideoMode::Shutdown()
 {
 	ENSURE(m_IsInitialised);
 
 	m_IsFullscreen = false;
 	m_IsInitialised = false;
 	if (m_Window)
 	{
 		SDL_DestroyWindow(m_Window);
 		m_Window = NULL;
 	}
 }
 
 void CVideoMode::EnableS3TC()
 {
 	// On Linux we have to try hard to get S3TC compressed texture support.
 	// If the extension is already provided by default, that's fine.
 	// Otherwise we should enable the 'force_s3tc_enable' environment variable
 	// and (re)initialise the video system, so that Mesa provides the extension
 	// (if the driver at least supports decompression).
 	// (This overrides the force_s3tc_enable specified via driconf files.)
 	// Otherwise we should complain to the user, and stop using compressed textures.
 	//
 	// Setting the environment variable causes Mesa to print an ugly message to stderr
 	// ("ATTENTION: default value of option force_s3tc_enable overridden by environment."),
 	// so it'd be nicer to skip that if S3TC will be supported by default,
 	// but reinitialising video is a pain (and it might do weird things when fullscreen)
 	// so we just unconditionally set it (unless our config file explicitly disables it).
 
 #if !(OS_WIN || OS_MACOSX) // (assume Mesa is used for all non-Windows non-Mac platforms)
 	if (m_ConfigForceS3TCEnable)
 		setenv("force_s3tc_enable", "true", 0);
 #endif
 }
 
 bool CVideoMode::ResizeWindow(int w, int h)
 {
 	ENSURE(m_IsInitialised);
 
 	// Ignore if not windowed
 	if (m_IsFullscreen)
 		return true;
 
 	// Ignore if the size hasn't changed
 	if (w == m_WindowedW && h == m_WindowedH)
 		return true;
 
 	int bpp = GetBestBPP();
 
 	if (!SetVideoMode(w, h, bpp, false))
 		return false;
 
 	m_WindowedW = w;
 	m_WindowedH = h;
 
-	UpdateRenderer(w, h);
-
 	return true;
 }
 
 bool CVideoMode::SetFullscreen(bool fullscreen)
 {
 	// This might get called before initialisation by psDisplayError;
 	// if so then silently fail
 	if (!m_IsInitialised)
 		return false;
 
 	// Check whether this is actually a change
 	if (fullscreen == m_IsFullscreen)
 		return true;
 
 	if (!m_IsFullscreen)
 	{
 		// Windowed -> fullscreen:
 
 		int w = 0, h = 0;
 
 		// If a fullscreen size was configured, use that; else use the desktop size; else use a default
 		if (m_ConfigFullscreen)
 		{
 			w = m_ConfigW;
 			h = m_ConfigH;
 		}
 		if (w == 0 || h == 0)
 		{
 			w = m_PreferredW;
 			h = m_PreferredH;
 		}
 		if (w == 0 || h == 0)
 		{
 			w = DEFAULT_FULLSCREEN_W;
 			h = DEFAULT_FULLSCREEN_H;
 		}
 
 		int bpp = GetBestBPP();
 
 		if (!SetVideoMode(w, h, bpp, fullscreen))
 			return false;
 
-		UpdateRenderer(m_CurrentW, m_CurrentH);
-
 		return true;
 	}
 	else
 	{
 		// Fullscreen -> windowed:
 
 		// Go back to whatever the previous window size was
 		int w = m_WindowedW, h = m_WindowedH;
 
 		int bpp = GetBestBPP();
 
 		if (!SetVideoMode(w, h, bpp, fullscreen))
 			return false;
 
-		UpdateRenderer(w, h);
-
 		return true;
 	}
 }
 
 bool CVideoMode::ToggleFullscreen()
 {
 	return SetFullscreen(!m_IsFullscreen);
 }
 
 void CVideoMode::UpdatePosition(int x, int y)
 {
 	if (!m_IsFullscreen)
 	{
 		m_WindowedX = x;
 		m_WindowedY = y;
 	}
 }
 
+void CVideoMode::UpdateRenderer() {
+	int drawingWidth = 0;
+	int drawingHeight = 0;
+
+	SDL_GL_GetDrawableSize(m_Window, &drawingWidth, &drawingHeight);
+	UpdateRenderer(drawingWidth, drawingHeight);
+	g_xres = drawingWidth;
+	g_yres = drawingHeight;
+
+	if (!m_ConfigHighDPI)
+		return;
+
+	// In HDPI Mode the gui scale is equal to the dpi scale.
+	g_DpiScale = static_cast<float>(drawingWidth) / static_cast<float>(m_CurrentW);
+	g_GuiScale = g_DpiScale;
+}
+
 void CVideoMode::UpdateRenderer(int w, int h)
 {
 	if (w < 2) w = 2; // avoid GL errors caused by invalid sizes
 	if (h < 2) h = 2;
 
 	g_xres = w;
 	g_yres = h;
 
 	SViewPort vp = { 0, 0, w, h };
 
 	if (CRenderer::IsInitialised())
 	{
 		g_Renderer.SetViewport(vp);
 		g_Renderer.Resize(w, h);
 	}
 
 	if (g_GUI)
 		g_GUI->UpdateResolution();
 
 	if (g_Console)
 		g_Console->UpdateScreenSize(w, h);
 
 	if (g_Game)
 		g_Game->GetView()->SetViewport(vp);
 }
 
 int CVideoMode::GetBestBPP()
 {
 	if (m_ConfigBPP)
 		return m_ConfigBPP;
 	if (m_PreferredBPP)
 		return m_PreferredBPP;
 	return 32;
 }
 
 int CVideoMode::GetXRes()
 {
 	ENSURE(m_IsInitialised);
 	return m_CurrentW;
 }
 
 int CVideoMode::GetYRes()
 {
 	ENSURE(m_IsInitialised);
 	return m_CurrentH;
 }
 
 int CVideoMode::GetBPP()
 {
 	ENSURE(m_IsInitialised);
 	return m_CurrentBPP;
 }
 
 int CVideoMode::GetDesktopXRes()
 {
 	ENSURE(m_IsInitialised);
 	return m_PreferredW;
 }
 
 int CVideoMode::GetDesktopYRes()
 {
 	ENSURE(m_IsInitialised);
 	return m_PreferredH;
 }
 
 int CVideoMode::GetDesktopBPP()
 {
 	ENSURE(m_IsInitialised);
 	return m_PreferredBPP;
 }
 
 int CVideoMode::GetDesktopFreq()
 {
 	ENSURE(m_IsInitialised);
 	return m_PreferredFreq;
 }
 
 SDL_Window* CVideoMode::GetWindow()
 {
 	ENSURE(m_IsInitialised);
 	return m_Window;
 }
Index: source/ps/VideoMode.h
===================================================================
--- source/ps/VideoMode.h	(revision 19922)
+++ source/ps/VideoMode.h	(working copy)
@@ -1,130 +1,137 @@
 /* Copyright (C) 2014 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 2 of the License, or
  * (at your option) any later version.
  *
  * 0 A.D. is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifndef INCLUDED_VIDEOMODE
 #define INCLUDED_VIDEOMODE
 
 typedef struct SDL_Window SDL_Window;
 
 class CVideoMode
 {
 public:
 	CVideoMode();
 
 	/**
 	 * Initialise the video mode, for use in an SDL-using application.
 	 */
 	bool InitSDL();
 
 	/**
 	 * Initialise parts of the video mode, for use in Atlas (which uses
 	 * wxWidgets instead of SDL for GL).
 	 * Currently this just tries to enable S3TC.
 	 */
 	bool InitNonSDL();
 
 	/**
 	 * Shut down after InitSDL/InitNonSDL, so that they can be used again.
 	 */
 	void Shutdown();
 
 	/**
 	 * Resize the SDL window and associated graphics stuff to the new size.
 	 */
 	bool ResizeWindow(int w, int h);
 
 	/**
 	 * Switch to fullscreen or windowed mode.
 	 */
 	bool SetFullscreen(bool fullscreen);
 
 	/**
 	 * Switch between fullscreen and windowed mode.
 	 */
 	bool ToggleFullscreen();
 
 	/**
 	 * Update window position, to restore later if necessary (SDL2 only).
 	 */
 	void UpdatePosition(int x, int y);
 
+    /**
+     * Update the graphics code to start drawing to the new size set for the SDL context.
+     * This should be called after the GL context has been resized.
+     */
+    void UpdateRenderer();
+
 	/**
-	 * Update the graphics code to start drawing to the new size.
+	 * Update the graphics code to start drawing to the specified size.
 	 * This should be called after the GL context has been resized.
 	 * This can also be used when the GL context is managed externally, not via SDL.
 	 */
 	static void UpdateRenderer(int w, int h);
 
 	int GetXRes();
 	int GetYRes();
 	int GetBPP();
 
 	int GetDesktopXRes();
 	int GetDesktopYRes();
 	int GetDesktopBPP();
 	int GetDesktopFreq();
 
 	SDL_Window* GetWindow();
 
 private:
 	void ReadConfig();
 	int GetBestBPP();
 	bool SetVideoMode(int w, int h, int bpp, bool fullscreen);
 	void EnableS3TC();
 
 	/**
 	 * Remember whether Init has been called. (This isn't used for anything
 	 * important, just for verifying that the callers call our methods in
 	 * the right order.)
 	 */
 	bool m_IsInitialised;
 
 	SDL_Window* m_Window;
 
 	// Initial desktop settings
 	int m_PreferredW;
 	int m_PreferredH;
 	int m_PreferredBPP;
 	int m_PreferredFreq;
 
 	// Config file settings (0 if unspecified)
 	int m_ConfigW;
 	int m_ConfigH;
 	int m_ConfigBPP;
+	bool m_ConfigHighDPI;
 	int m_ConfigDisplay;
 	bool m_ConfigFullscreen;
 	bool m_ConfigForceS3TCEnable;
 
 	// If we're fullscreen, size/position of window when we were last windowed (or the default window
 	// size/position if we started fullscreen), to support switching back to the old window size/position
 	int m_WindowedW;
 	int m_WindowedH;
 	int m_WindowedX;
 	int m_WindowedY;
 
 	// Whether we're currently being displayed fullscreen
 	bool m_IsFullscreen;
 
 	// The last mode selected
 	int m_CurrentW;
 	int m_CurrentH;
 	int m_CurrentBPP;
 };
 
 extern CVideoMode g_VideoMode;
 
 #endif // INCLUDED_VIDEOMODE