From e8c957a7a8068e3d4b46c6a163d461c8265e67eb Mon Sep 17 00:00:00 2001
From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com>
Date: Fri, 7 Jun 2024 23:00:07 -0700
Subject: [PATCH] Scale OSD text according to display scale

fixes #2605
---
 .../Renderers/StringRenderer.cs               | 57 ++++++++++--------
 .../DisplayManager/DisplayManagerBase.cs      |  4 ++
 .../DisplayManager/FilterManager.cs           |  4 ++
 .../DisplayManager/Filters/Gui.cs             | 14 +++--
 .../DisplayManager/IBlitter.cs                |  2 +
 .../DisplayManager/OSDManager.cs              | 58 ++++++++-----------
 .../DisplayManager/DisplayManager.cs          |  2 +
 7 files changed, 79 insertions(+), 62 deletions(-)

diff --git a/src/BizHawk.Bizware.Graphics/Renderers/StringRenderer.cs b/src/BizHawk.Bizware.Graphics/Renderers/StringRenderer.cs
index 2dc3da8640..d551fda995 100644
--- a/src/BizHawk.Bizware.Graphics/Renderers/StringRenderer.cs
+++ b/src/BizHawk.Bizware.Graphics/Renderers/StringRenderer.cs
@@ -1,5 +1,5 @@
-//http://www.angelcode.com/products/bmfont/
-//http://cyotek.com/blog/angelcode-bitmap-font-parsing-using-csharp
+// https://www.angelcode.com/products/bmfont/
+// https://devblog.cyotek.com/post/angelcode-bitmap-font-parsing-using-csharp
 
 using System;
 using System.Collections.Generic;
@@ -19,10 +19,24 @@ namespace BizHawk.Bizware.Graphics
 			FontInfo.LoadXml(xml);
 			
 			// load textures
-			for (var i=0; i<FontInfo.Pages.Length; i++)
+			for (var i = 0; i < FontInfo.Pages.Length; i++)
 			{
 				TexturePages.Add(owner.LoadTexture(textures[i]));
 			}
+
+			// precalc texcoords
+			foreach (var bfc in FontInfo)
+			{
+				var tex = TexturePages[bfc.TexturePage];
+				var w = (float)tex.Width;
+				var h = (float)tex.Height;
+				var bounds = new Rectangle(bfc.X, bfc.Y, bfc.Width, bfc.Height);
+				var u0 = bounds.Left / w;
+				var v0 = bounds.Top / h;
+				var u1 = bounds.Right / w;
+				var v1 = bounds.Bottom / h;
+				CharTexCoords.Add(bfc.Char, new(u0, v0, u1, v1));
+			}
 		}
 
 		public void Dispose()
@@ -35,10 +49,10 @@ namespace BizHawk.Bizware.Graphics
 			TexturePages = null;
 		}
 
-		public SizeF Measure(string str)
+		public SizeF Measure(string str, int scale)
 		{
 			float x = 0;
-			float y = FontInfo.LineHeight;
+			float y = FontInfo.LineHeight * scale;
 			var ox = x;
 			var len = str.Length;
 
@@ -67,18 +81,18 @@ namespace BizHawk.Bizware.Graphics
 					}
 
 					x = 0;
-					y += FontInfo.LineHeight;
+					y += FontInfo.LineHeight * scale;
 					continue;
 				}
 
 				var bfc = FontInfo[c];
-				x += bfc.XAdvance;
+				x += bfc.XAdvance * scale;
 			}
 
 			return new(Math.Max(x, ox), y);
 		}
 
-		public void RenderString(IGuiRenderer renderer, float x, float y, string str)
+		public void RenderString(IGuiRenderer renderer, float x, float y, string str, int scale)
 		{
 			if (Owner != renderer.Owner)
 			{
@@ -108,33 +122,28 @@ namespace BizHawk.Bizware.Graphics
 				if (c == '\n')
 				{
 					x = ox;
-					y += FontInfo.LineHeight;
+					y += FontInfo.LineHeight * scale;
 					continue;
 				}
 
 				var bfc = FontInfo[c];
-
-				// calculate texcoords (we shouldve already had this cached, but im speedcoding now)
 				var tex = TexturePages[bfc.TexturePage];
-				var w = (float)tex.Width;
-				var h = (float)tex.Height;
-				var bounds = new Rectangle(bfc.X, bfc.Y, bfc.Width, bfc.Height);
-				var u0 = bounds.Left / w;
-				var v0 = bounds.Top / h;
-				var u1 = bounds.Right / w;
-				var v1 = bounds.Bottom / h;
+				var gx = x + bfc.XOffset * scale;
+				var gy = y + bfc.YOffset * scale;
+				var charTexCoords = CharTexCoords[bfc.Char];
+				renderer.DrawSubrect(tex, gx, gy, bfc.Width * scale, bfc.Height * scale,
+					charTexCoords.U0, charTexCoords.V0, charTexCoords.U1, charTexCoords.V1);
 
-				var gx = x + bfc.XOffset;
-				var gy = y + bfc.YOffset;
-				renderer.DrawSubrect(tex, gx, gy, bfc.Width, bfc.Height, u0, v0, u1, v1);
-
-				x += bfc.XAdvance;
+				x += bfc.XAdvance * scale;
 			}
 		}
 
 		public IGL Owner { get; }
 
 		private readonly BitmapFont FontInfo;
-		private List<ITexture2D> TexturePages = new();
+		private List<ITexture2D> TexturePages = [ ];
+		private readonly Dictionary<char, TexCoords> CharTexCoords = [ ];
+
+		private record TexCoords(float U0, float V0, float U1, float V1);
 	}
 }
\ No newline at end of file
diff --git a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
index 566a6fdd93..b559c489ad 100644
--- a/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/DisplayManagerBase.cs
@@ -440,6 +440,8 @@ namespace BizHawk.Client.Common
 
 		protected abstract Size GetGraphicsControlSize();
 
+		protected abstract int GetGraphicsControlDpi();
+
 		/// <summary>
 		/// This will receive an emulated output frame from an IVideoProvider and run it through the complete frame processing pipeline
 		/// Then it will stuff it into the bound PresentationPanel.
@@ -811,6 +813,8 @@ namespace BizHawk.Client.Common
 			filterProgram.GuiRenderer = _renderer;
 			filterProgram.GL = _gl;
 
+			filterProgram.ControlDpi = GetGraphicsControlDpi();
+
 			//setup the source image filter
 			var fInput = (SourceImage)filterProgram["input"];
 			fInput.Texture = videoTexture;
diff --git a/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs b/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs
index a045ea2ee0..16ff743269 100644
--- a/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs
@@ -71,6 +71,10 @@ namespace BizHawk.Client.Common.FilterManager
 
 		public IRenderTarget CurrRenderTarget;
 
+		// DPI / 96.0 indicates the display scaling
+		// this is mainly relevant for OSD
+		public int ControlDpi;
+
 		public IRenderTarget GetTempTarget(int width, int height)
 		{
 			return RenderTargetProvider.Get(new(width, height));
diff --git a/src/BizHawk.Client.Common/DisplayManager/Filters/Gui.cs b/src/BizHawk.Client.Common/DisplayManager/Filters/Gui.cs
index 0941f33ac3..70cee5cc32 100644
--- a/src/BizHawk.Client.Common/DisplayManager/Filters/Gui.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/Filters/Gui.cs
@@ -613,9 +613,10 @@ namespace BizHawk.Client.Common.Filters
 			}
 
 			var size = FindInput().SurfaceFormat.Size;
-			
+
 			FilterProgram.GuiRenderer.Begin(size.Width, size.Height);
-			var blitter = new OSDBlitter(_font, FilterProgram.GuiRenderer, new(0, 0, size.Width, size.Height));
+			var scale = (int)Math.Round(FilterProgram.ControlDpi / 96.0f);
+			var blitter = new OSDBlitter(_font, FilterProgram.GuiRenderer, new(0, 0, size.Width, size.Height), scale);
 			FilterProgram.GuiRenderer.EnableBlending();
 			_manager.DrawScreenInfo(blitter);
 			_manager.DrawMessages(blitter);
@@ -627,24 +628,27 @@ namespace BizHawk.Client.Common.Filters
 			private readonly StringRenderer _font;
 			private readonly IGuiRenderer _renderer;
 
-			public OSDBlitter(StringRenderer font, IGuiRenderer renderer, Rectangle clipBounds)
+			public OSDBlitter(StringRenderer font, IGuiRenderer renderer, Rectangle clipBounds, int scale)
 			{
 				_font = font;
 				_renderer = renderer;
 				ClipBounds = clipBounds;
+				Scale = scale;
 			}
 
 			public void DrawString(string s, Color color, float x, float y)
 			{
 				_renderer.SetModulateColor(color);
-				_font.RenderString(_renderer, x, y, s);
+				_font.RenderString(_renderer, x, y, s, Scale);
 				_renderer.SetModulateColorWhite();
 			}
 
 			public SizeF MeasureString(string s)
-				=> _font.Measure(s);
+				=> _font.Measure(s, Scale);
 
 			public Rectangle ClipBounds { get; }
+
+			public int Scale { get; }
 		}
 	}
 }
\ No newline at end of file
diff --git a/src/BizHawk.Client.Common/DisplayManager/IBlitter.cs b/src/BizHawk.Client.Common/DisplayManager/IBlitter.cs
index 47db7c2f5e..8bb9bde7f8 100644
--- a/src/BizHawk.Client.Common/DisplayManager/IBlitter.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/IBlitter.cs
@@ -14,5 +14,7 @@ namespace BizHawk.Client.Common
 		SizeF MeasureString(string s);
 
 		Rectangle ClipBounds { get; }
+
+		public int Scale { get; }
 	}
 }
diff --git a/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs b/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs
index 11d930cd4e..a6bacafc92 100644
--- a/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs
+++ b/src/BizHawk.Client.Common/DisplayManager/OSDManager.cs
@@ -3,6 +3,7 @@ using System.Linq;
 using System.Text;
 using System.Drawing;
 using System.Collections.Generic;
+
 using BizHawk.Emulation.Common;
 
 namespace BizHawk.Client.Common
@@ -36,17 +37,18 @@ namespace BizHawk.Client.Common
 		public Color FixedMessagesColor => Color.FromArgb(_config.MessagesColor);
 		public Color FixedAlertMessageColor => Color.FromArgb(_config.AlertMessageColor);
 
-		private PointF GetCoordinates(IBlitter g, MessagePosition position, string message)
+
+
+		private static PointF GetCoordinates(IBlitter g, MessagePosition position, string message)
 		{
 			var size = g.MeasureString(message);
-			float x = position.Anchor.IsLeft()
+			var x = position.Anchor.IsLeft()
 				? position.X
 				: g.ClipBounds.Width - position.X - size.Width;
 
-			float y = position.Anchor.IsTop()
+			var y = position.Anchor.IsTop()
 				? position.Y
 				: g.ClipBounds.Height - position.Y - size.Height;
-			
 
 			return new PointF(x, y);
 		}
@@ -78,9 +80,9 @@ namespace BizHawk.Client.Common
 			return _emulator.Frame.ToString();
 		}
 
-		private readonly List<UIMessage> _messages = new List<UIMessage>(5);
-		private readonly List<UIDisplay> _guiTextList = new List<UIDisplay>();
-		private readonly List<UIDisplay> _ramWatchList = new List<UIDisplay>();
+		private readonly List<UIMessage> _messages = new(5);
+		private readonly List<UIDisplay> _guiTextList = [ ];
+		private readonly List<UIDisplay> _ramWatchList = [ ];
 
 		public void AddMessage(string message, int? duration = null)
 			=> _messages.Add(new() {
@@ -89,9 +91,7 @@ namespace BizHawk.Client.Common
 			});
 
 		public void ClearRamWatches()
-		{
-			_ramWatchList.Clear();
-		}
+			=> _ramWatchList.Clear();
 
 		public void AddRamWatch(string message, MessagePosition pos, Color backGround, Color foreColor)
 		{
@@ -116,9 +116,7 @@ namespace BizHawk.Client.Common
 		}
 
 		public void ClearGuiText()
-		{
-			_guiTextList.Clear();
-		}
+			=> _guiTextList.Clear();
 
 		private void DrawMessage(IBlitter g, UIMessage message, int yOffset)
 		{
@@ -140,10 +138,10 @@ namespace BizHawk.Client.Common
 			{
 				if (_config.StackOSDMessages)
 				{
-					int line = 1;
-					for (int i = _messages.Count - 1; i >= 0; i--, line++)
+					var line = 1;
+					for (var i = _messages.Count - 1; i >= 0; i--, line++)
 					{
-						int yOffset = (line - 1) * 18;
+						var yOffset = (line - 1) * 18 * g.Scale;
 						if (!_config.Messages.Anchor.IsTop())
 						{
 							yOffset = 0 - yOffset;
@@ -154,7 +152,7 @@ namespace BizHawk.Client.Common
 				}
 				else
 				{
-					var message = _messages[_messages.Count - 1];
+					var message = _messages[^1];
 					DrawMessage(g, message, 0);
 				}
 			}
@@ -175,14 +173,10 @@ namespace BizHawk.Client.Common
 		}
 
 		public string InputStrMovie()
-		{
-			return MakeStringFor(_movieSession.MovieController, cache: true);
-		}
+			=> MakeStringFor(_movieSession.MovieController, cache: true);
 
 		public string InputStrImmediate()
-		{
-			return MakeStringFor(_inputManager.AutofireStickyXorAdapter, cache: true);
-		}
+			=> MakeStringFor(_inputManager.AutofireStickyXorAdapter, cache: true);
 
 		public string InputPrevious()
 		{
@@ -235,10 +229,8 @@ namespace BizHawk.Client.Common
 				: "";
 		}
 
-		private void DrawOsdMessage(IBlitter g, string message, Color color, float x, float y)
-		{
-			g.DrawString(message, color, x, y);
-		}
+		private static void DrawOsdMessage(IBlitter g, string message, Color color, float x, float y)
+			=> g.DrawString(message, color, x, y);
 
 		/// <summary>
 		/// Display all screen info objects like fps, frame counter, lag counter, and input display
@@ -247,7 +239,7 @@ namespace BizHawk.Client.Common
 		{
 			if (_config.DisplayFrameCounter && !_emulator.IsNull())
 			{
-				string message = MakeFrameCounter();
+				var message = MakeFrameCounter();
 				var point = GetCoordinates(g, _config.FrameCounter, message);
 				DrawOsdMessage(g, message, Color.FromArgb(_config.MessagesColor), point.X, point.Y);
 
@@ -266,14 +258,14 @@ namespace BizHawk.Client.Common
 				{
 					var input = InputStrMovie();
 					var point = GetCoordinates(g, _config.InputDisplay, input);
-					Color c = Color.FromArgb(_config.MovieInput);
+					var c = Color.FromArgb(_config.MovieInput);
 					g.DrawString(input, c, point.X, point.Y);
 				}
 
 				if (!moviePlaying) // TODO: message config -- allow setting of "mixed", and "auto"
 				{
 					var previousColor = Color.FromArgb(_config.LastInputColor);
-					Color immediateColor = Color.FromArgb(_config.MessagesColor);
+					var immediateColor = Color.FromArgb(_config.MessagesColor);
 					var autoColor = Color.Pink;
 					var changedColor = Color.PeachPuff;
 
@@ -325,7 +317,7 @@ namespace BizHawk.Client.Common
 
 			if (_config.DisplayRerecordCount)
 			{
-				string rerecordCount = MakeRerecordCount();
+				var rerecordCount = MakeRerecordCount();
 				var point = GetCoordinates(g, _config.ReRecordCounter, rerecordCount);
 				DrawOsdMessage(g, rerecordCount, FixedMessagesColor, point.X, point.Y);
 			}
@@ -334,12 +326,12 @@ namespace BizHawk.Client.Common
 			{
 				var sb = new StringBuilder("Held: ");
 
-				foreach (string sticky in _inputManager.StickyXorAdapter.CurrentStickies)
+				foreach (var sticky in _inputManager.StickyXorAdapter.CurrentStickies)
 				{
 					sb.Append(sticky).Append(' ');
 				}
 
-				foreach (string autoSticky in _inputManager.AutofireStickyXorAdapter.CurrentStickies)
+				foreach (var autoSticky in _inputManager.AutofireStickyXorAdapter.CurrentStickies)
 				{
 					sb
 						.Append("Auto-")
diff --git a/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs b/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
index 690d167fd3..bccd565dc5 100644
--- a/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
+++ b/src/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs
@@ -51,6 +51,8 @@ namespace BizHawk.Client.EmuHawk
 
 		public override Size GetPanelNativeSize() => _presentationPanel.NativeSize;
 
+		protected override int GetGraphicsControlDpi() => _graphicsControl.DeviceDpi;
+
 		protected override Point GraphicsControlPointToClient(Point p) => _graphicsControl.PointToClient(p);
 
 		protected override void SwapBuffersOfGraphicsControl() => _graphicsControl.SwapBuffers();