Implement Shared Fonts (#215)

* Implement Shared Fonts

This fully implements shared fonts.
This commit is provided without fonts.
This commit also add Size to HSharedMem.Positions to be able to add fonts to shared zones when RequestLoad is called.

* Require the user to provide fonts in RyuFS/system

* Use File.Exits instead of relying ona try/catch and change system resource exception format a bit

* Make sure that font sum doesn't exceed 17MB

Also rename font data dictionary for coherence.
This commit is contained in:
Thomas Guillemard 2018-08-04 23:38:49 +02:00 committed by gdkchan
parent 5f34353dce
commit eeb626947e
13 changed files with 292 additions and 35 deletions

View file

@ -0,0 +1,177 @@
using ChocolArm64.Exceptions;
using ChocolArm64.Memory;
using Ryujinx.HLE.Logging;
using Ryujinx.HLE.OsHle;
using Ryujinx.HLE.OsHle.Handles;
using Ryujinx.HLE.Resource;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.HLE.Font
{
public class SharedFontManager
{
private const uint SharedMemorySize = 0x1100000;
private Logger Log;
private string FontsPath;
private object ShMemLock;
private (AMemory, long, long)[] ShMemPositions;
private Dictionary<SharedFontType, byte[]> FontData;
private uint[] LoadedFonts;
public SharedFontManager(Logger Log, string SystemPath)
{
this.Log = Log;
this.FontsPath = Path.Combine(SystemPath, "fonts");
ShMemLock = new object();
ShMemPositions = new(AMemory, long, long)[0];
FontData = new Dictionary<SharedFontType, byte[]>()
{
{ SharedFontType.JapanUsEurope, GetData("FontStandard") },
{ SharedFontType.SimplifiedChinese, GetData("FontChineseSimplified") },
{ SharedFontType.SimplifiedChineseEx, GetData("FontExtendedChineseSimplified") },
{ SharedFontType.TraditionalChinese, GetData("FontChineseTraditional") },
{ SharedFontType.Korean, GetData("FontKorean") },
{ SharedFontType.NintendoEx, GetData("FontNintendoExtended") }
};
int FontMemoryUsage = 0;
foreach (byte[] data in FontData.Values)
{
FontMemoryUsage += data.Length;
FontMemoryUsage += 0x8;
}
if (FontMemoryUsage > SharedMemorySize)
{
throw new InvalidSystemResourceException($"The sum of all fonts size exceed the shared memory size. Please make sure that the fonts don't exceed {SharedMemorySize} bytes in total. (actual size: {FontMemoryUsage} bytes)");
}
LoadedFonts = new uint[FontData.Count];
}
public byte[] GetData(string FontName)
{
string FontFilePath = Path.Combine(FontsPath, $"{FontName}.ttf");
if (File.Exists(FontFilePath))
{
return File.ReadAllBytes(FontFilePath);
}
else
{
throw new InvalidSystemResourceException($"Font \"{FontName}.ttf\" not found. Please provide it in \"{FontsPath}\".");
}
}
public void MapFont(SharedFontType FontType, AMemory Memory, long Position)
{
uint SharedMemoryAddressOffset = GetSharedMemoryAddressOffset(FontType);
// TODO: find what are the 8 bytes before the font
Memory.WriteUInt64(Position + SharedMemoryAddressOffset - 8, 0);
Memory.WriteBytes(Position + SharedMemoryAddressOffset, FontData[FontType]);
}
public void PropagateNewMapFont(SharedFontType Type)
{
lock (ShMemLock)
{
foreach ((AMemory Memory, long Position, long Size) in ShMemPositions)
{
AMemoryMapInfo MemoryInfo = Memory.Manager.GetMapInfo(Position);
if (MemoryInfo == null)
{
throw new VmmPageFaultException(Position);
}
// The memory is read only, we need to changes that to add the new font
AMemoryPerm originalPerms = MemoryInfo.Perm;
Memory.Manager.Reprotect(Position, Size, AMemoryPerm.RW);
MapFont(Type, Memory, Position);
Memory.Manager.Reprotect(Position, Size, originalPerms);
}
}
}
internal void ShMemMap(object sender, EventArgs e)
{
HSharedMem SharedMem = (HSharedMem)sender;
lock (ShMemLock)
{
ShMemPositions = SharedMem.GetVirtualPositions();
(AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1];
for (int Type = 0; Type < LoadedFonts.Length; Type++)
{
if (LoadedFonts[(int)Type] == 1)
{
MapFont((SharedFontType)Type, Memory, Position);
}
}
}
}
internal void ShMemUnmap(object sender, EventArgs e)
{
HSharedMem SharedMem = (HSharedMem)sender;
lock (ShMemLock)
{
ShMemPositions = SharedMem.GetVirtualPositions();
}
}
public void Load(SharedFontType FontType)
{
if (LoadedFonts[(int)FontType] == 0)
{
PropagateNewMapFont(FontType);
}
LoadedFonts[(int)FontType] = 1;
}
public uint GetLoadState(SharedFontType FontType)
{
if (LoadedFonts[(int)FontType] != 1)
{
// Some games don't request a load, so we need to load it here.
Load(FontType);
return 0;
}
return LoadedFonts[(int)FontType];
}
public uint GetFontSize(SharedFontType FontType)
{
return (uint)FontData[FontType].Length;
}
public uint GetSharedMemoryAddressOffset(SharedFontType FontType)
{
uint Pos = 0x8;
for (SharedFontType Type = SharedFontType.JapanUsEurope; Type < FontType; Type++)
{
Pos += GetFontSize(Type);
Pos += 0x8;
}
return Pos;
}
public int Count => FontData.Count;
}
}

View file

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.OsHle.Services.Pl namespace Ryujinx.HLE.Font
{ {
enum SharedFontType public enum SharedFontType
{ {
JapanUsEurope = 0, JapanUsEurope = 0,
SimplifiedChinese = 1, SimplifiedChinese = 1,

View file

@ -67,7 +67,7 @@ namespace Ryujinx.HLE.Input
private object ShMemLock; private object ShMemLock;
private (AMemory, long)[] ShMemPositions; private (AMemory, long, long)[] ShMemPositions;
public Hid(Logger Log) public Hid(Logger Log)
{ {
@ -75,7 +75,7 @@ namespace Ryujinx.HLE.Input
ShMemLock = new object(); ShMemLock = new object();
ShMemPositions = new (AMemory, long)[0]; ShMemPositions = new (AMemory, long, long)[0];
} }
internal void ShMemMap(object sender, EventArgs e) internal void ShMemMap(object sender, EventArgs e)
@ -86,7 +86,7 @@ namespace Ryujinx.HLE.Input
{ {
ShMemPositions = SharedMem.GetVirtualPositions(); ShMemPositions = SharedMem.GetVirtualPositions();
(AMemory Memory, long Position) = ShMemPositions[ShMemPositions.Length - 1]; (AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1];
for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8) for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8)
{ {
@ -167,7 +167,7 @@ namespace Ryujinx.HLE.Input
{ {
lock (ShMemLock) lock (ShMemLock)
{ {
foreach ((AMemory Memory, long Position) in ShMemPositions) foreach ((AMemory Memory, long Position, long Size) in ShMemPositions)
{ {
long ControllerOffset = Position + HidControllersOffset; long ControllerOffset = Position + HidControllersOffset;
@ -218,7 +218,7 @@ namespace Ryujinx.HLE.Input
{ {
lock (ShMemLock) lock (ShMemLock)
{ {
foreach ((AMemory Memory, long Position) in ShMemPositions) foreach ((AMemory Memory, long Position, long Size) in ShMemPositions)
{ {
long TouchScreenOffset = Position + HidTouchScreenOffset; long TouchScreenOffset = Position + HidTouchScreenOffset;

View file

@ -4,6 +4,7 @@ namespace Ryujinx.HLE.Logging
{ {
Audio, Audio,
Cpu, Cpu,
Font,
Gpu, Gpu,
Hid, Hid,
Kernel, Kernel,

View file

@ -6,37 +6,37 @@ namespace Ryujinx.HLE.OsHle.Handles
{ {
class HSharedMem class HSharedMem
{ {
private List<(AMemory, long)> Positions; private List<(AMemory, long, long)> Positions;
public EventHandler<EventArgs> MemoryMapped; public EventHandler<EventArgs> MemoryMapped;
public EventHandler<EventArgs> MemoryUnmapped; public EventHandler<EventArgs> MemoryUnmapped;
public HSharedMem() public HSharedMem()
{ {
Positions = new List<(AMemory, long)>(); Positions = new List<(AMemory, long, long)>();
} }
public void AddVirtualPosition(AMemory Memory, long Position) public void AddVirtualPosition(AMemory Memory, long Position, long Size)
{ {
lock (Positions) lock (Positions)
{ {
Positions.Add((Memory, Position)); Positions.Add((Memory, Position, Size));
MemoryMapped?.Invoke(this, EventArgs.Empty); MemoryMapped?.Invoke(this, EventArgs.Empty);
} }
} }
public void RemoveVirtualPosition(AMemory Memory, long Position) public void RemoveVirtualPosition(AMemory Memory, long Position, long Size)
{ {
lock (Positions) lock (Positions)
{ {
Positions.Remove((Memory, Position)); Positions.Remove((Memory, Position, Size));
MemoryUnmapped?.Invoke(this, EventArgs.Empty); MemoryUnmapped?.Invoke(this, EventArgs.Empty);
} }
} }
public (AMemory, long)[] GetVirtualPositions() public (AMemory, long, long)[] GetVirtualPositions()
{ {
return Positions.ToArray(); return Positions.ToArray();
} }

View file

@ -22,7 +22,7 @@ namespace Ryujinx.HLE.OsHle.Kernel
private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits; private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
private HashSet<(HSharedMem, long)> MappedSharedMems; private HashSet<(HSharedMem, long, long)> MappedSharedMems;
private ulong CurrentHeapSize; private ulong CurrentHeapSize;
@ -83,7 +83,7 @@ namespace Ryujinx.HLE.OsHle.Kernel
SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>(); SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
MappedSharedMems = new HashSet<(HSharedMem, long)>(); MappedSharedMems = new HashSet<(HSharedMem, long, long)>();
} }
static SvcHandler() static SvcHandler()
@ -138,9 +138,9 @@ namespace Ryujinx.HLE.OsHle.Kernel
{ {
lock (MappedSharedMems) lock (MappedSharedMems)
{ {
foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems) foreach ((HSharedMem SharedMem, long Position, long Size) in MappedSharedMems)
{ {
SharedMem.RemoveVirtualPosition(Memory, Position); SharedMem.RemoveVirtualPosition(Memory, Position, Size);
} }
MappedSharedMems.Clear(); MappedSharedMems.Clear();

View file

@ -174,15 +174,15 @@ namespace Ryujinx.HLE.OsHle.Kernel
AMemoryHelper.FillWithZeros(Memory, Src, (int)Size); AMemoryHelper.FillWithZeros(Memory, Src, (int)Size);
SharedMem.AddVirtualPosition(Memory, Src, Size);
Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm); Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm);
lock (MappedSharedMems) lock (MappedSharedMems)
{ {
MappedSharedMems.Add((SharedMem, Src)); MappedSharedMems.Add((SharedMem, Src, Size));
} }
SharedMem.AddVirtualPosition(Memory, Src);
ThreadState.X0 = 0; ThreadState.X0 = 0;
} }
@ -210,11 +210,11 @@ namespace Ryujinx.HLE.OsHle.Kernel
{ {
Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory); Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory);
SharedMem.RemoveVirtualPosition(Memory, Src); SharedMem.RemoveVirtualPosition(Memory, Src, Size);
lock (MappedSharedMems) lock (MappedSharedMems)
{ {
MappedSharedMems.Remove((SharedMem, Src)); MappedSharedMems.Remove((SharedMem, Src, Size));
} }
ThreadState.X0 = 0; ThreadState.X0 = 0;

View file

@ -242,7 +242,6 @@ namespace Ryujinx.HLE.OsHle.Kernel
Process.Scheduler.Suspend(CurrThread); Process.Scheduler.Suspend(CurrThread);
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr); long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
Thread.Yield(); Thread.Yield();

View file

@ -1,3 +1,4 @@
using Ryujinx.HLE.Font;
using Ryujinx.HLE.OsHle.Ipc; using Ryujinx.HLE.OsHle.Ipc;
using System.Collections.Generic; using System.Collections.Generic;
@ -17,7 +18,8 @@ namespace Ryujinx.HLE.OsHle.Services.Pl
{ 1, GetLoadState }, { 1, GetLoadState },
{ 2, GetFontSize }, { 2, GetFontSize },
{ 3, GetSharedMemoryAddressOffset }, { 3, GetSharedMemoryAddressOffset },
{ 4, GetSharedMemoryNativeHandle } { 4, GetSharedMemoryNativeHandle },
{ 5, GetSharedFontInOrderOfPriority }
}; };
} }
@ -25,26 +27,34 @@ namespace Ryujinx.HLE.OsHle.Services.Pl
{ {
SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
Context.Ns.Font.Load(FontType);
return 0; return 0;
} }
public long GetLoadState(ServiceCtx Context) public long GetLoadState(ServiceCtx Context)
{ {
Context.ResponseData.Write(1); //Loaded SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
Context.ResponseData.Write(Context.Ns.Font.GetLoadState(FontType));
return 0; return 0;
} }
public long GetFontSize(ServiceCtx Context) public long GetFontSize(ServiceCtx Context)
{ {
Context.ResponseData.Write(Horizon.FontSize); SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
Context.ResponseData.Write(Context.Ns.Font.GetFontSize(FontType));
return 0; return 0;
} }
public long GetSharedMemoryAddressOffset(ServiceCtx Context) public long GetSharedMemoryAddressOffset(ServiceCtx Context)
{ {
Context.ResponseData.Write(0); SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32();
Context.ResponseData.Write(Context.Ns.Font.GetSharedMemoryAddressOffset(FontType));
return 0; return 0;
} }
@ -57,5 +67,51 @@ namespace Ryujinx.HLE.OsHle.Services.Pl
return 0; return 0;
} }
private uint AddFontToOrderOfPriorityList(ServiceCtx Context, SharedFontType FontType, uint BufferPos, out uint LoadState)
{
long TypesPosition = Context.Request.ReceiveBuff[0].Position;
long TypesSize = Context.Request.ReceiveBuff[0].Size;
long OffsetsPosition = Context.Request.ReceiveBuff[1].Position;
long OffsetsSize = Context.Request.ReceiveBuff[1].Size;
long FontSizeBufferPosition = Context.Request.ReceiveBuff[2].Position;
long FontSizeBufferSize = Context.Request.ReceiveBuff[2].Size;
LoadState = Context.Ns.Font.GetLoadState(FontType);
if (BufferPos >= TypesSize || BufferPos >= OffsetsSize || BufferPos >= FontSizeBufferSize)
{
return 0;
}
Context.Memory.WriteUInt32(TypesPosition + BufferPos, (uint)FontType);
Context.Memory.WriteUInt32(OffsetsPosition + BufferPos, Context.Ns.Font.GetSharedMemoryAddressOffset(FontType));
Context.Memory.WriteUInt32(FontSizeBufferPosition + BufferPos, Context.Ns.Font.GetFontSize(FontType));
BufferPos += 4;
return BufferPos;
}
public long GetSharedFontInOrderOfPriority(ServiceCtx Context)
{
ulong LanguageCode = Context.RequestData.ReadUInt64();
uint LoadedCount = 0;
uint BufferPos = 0;
uint Loaded = 0;
for (int Type = 0; Type < Context.Ns.Font.Count; Type++)
{
BufferPos = AddFontToOrderOfPriorityList(Context, (SharedFontType)Type, BufferPos, out Loaded);
LoadedCount += Loaded;
}
Context.ResponseData.Write(LoadedCount);
Context.ResponseData.Write(Context.Ns.Font.Count);
return 0;
}
} }
} }

View file

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.HLE.Resource
{
public class InvalidSystemResourceException : Exception
{
public InvalidSystemResourceException(string message)
: base(message)
{
}
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>

View file

@ -1,5 +1,6 @@
using Ryujinx.Audio; using Ryujinx.Audio;
using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal;
using Ryujinx.HLE.Font;
using Ryujinx.HLE.Gpu; using Ryujinx.HLE.Gpu;
using Ryujinx.HLE.Input; using Ryujinx.HLE.Input;
using Ryujinx.HLE.Logging; using Ryujinx.HLE.Logging;
@ -27,6 +28,8 @@ namespace Ryujinx.HLE
public Hid Hid { get; private set; } public Hid Hid { get; private set; }
public SharedFontManager Font { get; private set; }
public event EventHandler Finish; public event EventHandler Finish;
public Switch(IGalRenderer Renderer, IAalOutput AudioOut) public Switch(IGalRenderer Renderer, IAalOutput AudioOut)
@ -57,8 +60,13 @@ namespace Ryujinx.HLE
Hid = new Hid(Log); Hid = new Hid(Log);
Font = new SharedFontManager(Log, VFs.GetSystemPath());
Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; Os.HidSharedMem.MemoryMapped += Hid.ShMemMap;
Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap;
Os.FontSharedMem.MemoryMapped += Font.ShMemMap;
Os.FontSharedMem.MemoryUnmapped += Font.ShMemUnmap;
} }
public void LoadCart(string ExeFsDir, string RomFsFile = null) public void LoadCart(string ExeFsDir, string RomFsFile = null)

View file

@ -8,6 +8,7 @@ namespace Ryujinx.HLE
private const string BasePath = "RyuFs"; private const string BasePath = "RyuFs";
private const string NandPath = "nand"; private const string NandPath = "nand";
private const string SdCardPath = "sdmc"; private const string SdCardPath = "sdmc";
private const string SystemPath = "system";
public Stream RomFs { get; private set; } public Stream RomFs { get; private set; }
@ -45,6 +46,8 @@ namespace Ryujinx.HLE
public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath); public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath);
public string GetSystemPath() => MakeDirAndGetFullPath(SystemPath);
public string SwitchPathToSystemPath(string SwitchPath) public string SwitchPathToSystemPath(string SwitchPath)
{ {
string[] Parts = SwitchPath.Split(":"); string[] Parts = SwitchPath.Split(":");