diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs')
| -rw-r--r-- | src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs new file mode 100644 index 00000000..52ed5222 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -0,0 +1,487 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types; +using Ryujinx.HLE.HOS.Services.Vi.Types; +using Ryujinx.HLE.Ui; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService +{ + class IApplicationDisplayService : IpcService + { + private readonly ViServiceType _serviceType; + + private class DisplayState + { + public int RetrievedEventsCount; + } + + private readonly List<DisplayInfo> _displayInfo; + private readonly Dictionary<ulong, DisplayState> _openDisplays; + + private int _vsyncEventHandle; + + public IApplicationDisplayService(ViServiceType serviceType) + { + _serviceType = serviceType; + _displayInfo = new List<DisplayInfo>(); + _openDisplays = new Dictionary<ulong, DisplayState>(); + + void AddDisplayInfo(string name, bool layerLimitEnabled, ulong layerLimitMax, ulong width, ulong height) + { + DisplayInfo displayInfo = new DisplayInfo() + { + Name = new Array64<byte>(), + LayerLimitEnabled = layerLimitEnabled, + Padding = new Array7<byte>(), + LayerLimitMax = layerLimitMax, + Width = width, + Height = height + }; + + Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(displayInfo.Name.AsSpan()); + + _displayInfo.Add(displayInfo); + } + + AddDisplayInfo("Default", true, 1, 1920, 1080); + AddDisplayInfo("External", true, 1, 1920, 1080); + AddDisplayInfo("Edid", true, 1, 0, 0); + AddDisplayInfo("Internal", true, 1, 1920, 1080); + AddDisplayInfo("Null", false, 0, 1920, 1080); + } + + [CommandCmif(100)] + // GetRelayService() -> object<nns::hosbinder::IHOSBinderDriver> + public ResultCode GetRelayService(ServiceCtx context) + { + // FIXME: Should be _serviceType != ViServiceType.Application but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetSystemDisplayService() -> object<nn::visrv::sf::ISystemDisplayService> + public ResultCode GetSystemDisplayService(ServiceCtx context) + { + // FIXME: Should be _serviceType == ViServiceType.System but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new ISystemDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // GetManagerDisplayService() -> object<nn::visrv::sf::IManagerDisplayService> + public ResultCode GetManagerDisplayService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IManagerDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(103)] // 2.0.0+ + // GetIndirectDisplayTransactionService() -> object<nns::hosbinder::IHOSBinderDriver> + public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // ListDisplays() -> (u64 count, buffer<nn::vi::DisplayInfo, 6>) + public ResultCode ListDisplays(ServiceCtx context) + { + ulong displayInfoBuffer = context.Request.ReceiveBuff[0].Position; + + // TODO: Determine when more than one display is needed. + ulong displayCount = 1; + + for (int i = 0; i < (int)displayCount; i++) + { + context.Memory.Write(displayInfoBuffer + (ulong)(i * Unsafe.SizeOf<DisplayInfo>()), _displayInfo[i]); + } + + context.ResponseData.Write(displayCount); + + return ResultCode.Success; + } + + [CommandCmif(1010)] + // OpenDisplay(nn::vi::DisplayName) -> u64 display_id + public ResultCode OpenDisplay(ServiceCtx context) + { + string name = ""; + + for (int index = 0; index < 8 && context.RequestData.BaseStream.Position < context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + name += (char)chr; + } + } + + return OpenDisplayImpl(context, name); + } + + [CommandCmif(1011)] + // OpenDefaultDisplay() -> u64 display_id + public ResultCode OpenDefaultDisplay(ServiceCtx context) + { + return OpenDisplayImpl(context, "Default"); + } + + private ResultCode OpenDisplayImpl(ServiceCtx context, string name) + { + if (name == "") + { + return ResultCode.InvalidValue; + } + + int displayId = _displayInfo.FindIndex(display => Encoding.ASCII.GetString(display.Name.AsSpan()).Trim('\0') == name); + + if (displayId == -1) + { + return ResultCode.InvalidValue; + } + + if (!_openDisplays.TryAdd((ulong)displayId, new DisplayState())) + { + return ResultCode.AlreadyOpened; + } + + context.ResponseData.Write((ulong)displayId); + + return ResultCode.Success; + } + + [CommandCmif(1020)] + // CloseDisplay(u64 display_id) + public ResultCode CloseDisplay(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.Remove(displayId)) + { + return ResultCode.InvalidValue; + } + + return ResultCode.Success; + } + + [CommandCmif(1101)] + // SetDisplayEnabled(u32 enabled_bool, u64 display_id) + public ResultCode SetDisplayEnabled(ServiceCtx context) + { + // NOTE: Stubbed in original service. + return ResultCode.Success; + } + + [CommandCmif(1102)] + // GetDisplayResolution(u64 display_id) -> (u64 width, u64 height) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + // NOTE: Not used in original service. + // ulong displayId = context.RequestData.ReadUInt64(); + + // NOTE: Returns ResultCode.InvalidArguments if width and height pointer are null, doesn't occur in our case. + + // NOTE: Values are hardcoded in original service. + context.ResponseData.Write(1280UL); // Width + context.ResponseData.Write(720UL); // Height + + return ResultCode.Success; + } + + [CommandCmif(2020)] + // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer<bytes, 6>) + public ResultCode OpenLayer(ServiceCtx context) + { + // TODO: support multi display. + byte[] displayName = context.RequestData.ReadBytes(0x40); + + long layerId = context.RequestData.ReadInt64(); + long userId = context.RequestData.ReadInt64(); + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + ResultCode result = context.Device.System.SurfaceFlinger.OpenLayer(context.Request.HandleDesc.PId, layerId, out IBinder producer); + + if (result != ResultCode.Success) + { + return result; + } + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + Parcel parcel = new Parcel(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan<byte> parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2021)] + // CloseLayer(u64) + public ResultCode CloseLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.CloseLayer(layerId); + } + + [CommandCmif(2030)] + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer<bytes, 6>) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); + + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + // TODO: support multi display. + IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray); + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + Parcel parcel = new Parcel(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan<byte> parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write(layerId); + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2031)] + // DestroyStrayLayer(u64) + public ResultCode DestroyStrayLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.DestroyStrayLayer(layerId); + } + + [CommandCmif(2101)] + // SetLayerScalingMode(u32, u64) + public ResultCode SetLayerScalingMode(ServiceCtx context) + { + /* + uint sourceScalingMode = context.RequestData.ReadUInt32(); + ulong layerId = context.RequestData.ReadUInt64(); + */ + // NOTE: Original service converts SourceScalingMode to DestinationScalingMode but does nothing with the converted value. + + return ResultCode.Success; + } + + [CommandCmif(2102)] // 5.0.0+ + // ConvertScalingMode(u32 source_scaling_mode) -> u64 destination_scaling_mode + public ResultCode ConvertScalingMode(ServiceCtx context) + { + SourceScalingMode scalingMode = (SourceScalingMode)context.RequestData.ReadInt32(); + + DestinationScalingMode? convertedScalingMode = scalingMode switch + { + SourceScalingMode.None => DestinationScalingMode.None, + SourceScalingMode.Freeze => DestinationScalingMode.Freeze, + SourceScalingMode.ScaleAndCrop => DestinationScalingMode.ScaleAndCrop, + SourceScalingMode.ScaleToWindow => DestinationScalingMode.ScaleToWindow, + SourceScalingMode.PreserveAspectRatio => DestinationScalingMode.PreserveAspectRatio, + _ => null, + }; + + if (!convertedScalingMode.HasValue) + { + // Scaling mode out of the range of valid values. + return ResultCode.InvalidArguments; + } + + if (scalingMode != SourceScalingMode.ScaleToWindow && scalingMode != SourceScalingMode.PreserveAspectRatio) + { + // Invalid scaling mode specified. + return ResultCode.InvalidScalingMode; + } + + context.ResponseData.Write((ulong)convertedScalingMode); + + return ResultCode.Success; + } + + private ulong GetA8B8G8R8LayerSize(int width, int height, out int pitch, out int alignment) + { + const int defaultAlignment = 0x1000; + const ulong defaultSize = 0x20000; + + alignment = defaultAlignment; + pitch = BitUtils.AlignUp(BitUtils.DivRoundUp(width * 32, 8), 64); + + int memorySize = pitch * BitUtils.AlignUp(height, 64); + ulong requiredMemorySize = (ulong)BitUtils.AlignUp(memorySize, alignment); + + return (requiredMemorySize + defaultSize - 1) / defaultSize * defaultSize; + } + + [CommandCmif(2450)] + // GetIndirectLayerImageMap(s64 width, s64 height, u64 handle, nn::applet::AppletResourceUserId, pid) -> (s64, s64, buffer<bytes, 0x46>) + public ResultCode GetIndirectLayerImageMap(ServiceCtx context) + { + // The size of the layer buffer should be an aligned multiple of width * height + // because it was created using GetIndirectLayerImageRequiredMemoryInfo as a guide. + + long layerWidth = context.RequestData.ReadInt64(); + long layerHeight = context.RequestData.ReadInt64(); + long layerHandle = context.RequestData.ReadInt64(); + ulong layerBuffPosition = context.Request.ReceiveBuff[0].Position; + ulong layerBuffSize = context.Request.ReceiveBuff[0].Size; + + // Get the pitch of the layer that is necessary to render correctly. + ulong size = GetA8B8G8R8LayerSize((int)layerWidth, (int)layerHeight, out int pitch, out _); + + Debug.Assert(layerBuffSize == size); + + RenderingSurfaceInfo surfaceInfo = new RenderingSurfaceInfo(ColorFormat.A8B8G8R8, (uint)layerWidth, (uint)layerHeight, (uint)pitch, (uint)layerBuffSize); + + // Get the applet associated with the handle. + object appletObject = context.Device.System.AppletState.IndirectLayerHandles.GetData((int)layerHandle); + + if (appletObject == null) + { + Logger.Error?.Print(LogClass.ServiceVi, $"Indirect layer handle {layerHandle} does not match any applet"); + + return ResultCode.Success; + } + + Debug.Assert(appletObject is IApplet); + + IApplet applet = appletObject as IApplet; + + if (!applet.DrawTo(surfaceInfo, context.Memory, layerBuffPosition)) + { + Logger.Warning?.Print(LogClass.ServiceVi, $"Applet did not draw on indirect layer handle {layerHandle}"); + + return ResultCode.Success; + } + + context.ResponseData.Write(layerWidth); + context.ResponseData.Write(layerHeight); + + return ResultCode.Success; + } + + [CommandCmif(2460)] + // GetIndirectLayerImageRequiredMemoryInfo(u64 width, u64 height) -> (u64 size, u64 alignment) + public ResultCode GetIndirectLayerImageRequiredMemoryInfo(ServiceCtx context) + { + /* + // Doesn't occur in our case. + if (sizePtr == null || address_alignmentPtr == null) + { + return ResultCode.InvalidArguments; + } + */ + + int width = (int)context.RequestData.ReadUInt64(); + int height = (int)context.RequestData.ReadUInt64(); + + if (height < 0 || width < 0) + { + return ResultCode.InvalidLayerSize; + } + else + { + /* + // Doesn't occur in our case. + if (!service_initialized) + { + return ResultCode.InvalidArguments; + } + */ + + // NOTE: The official service setup a A8B8G8R8 texture with a linear layout and then query its size. + // As we don't need this texture on the emulator, we can just simplify this logic and directly + // do a linear layout size calculation. (stride * height * bytePerPixel) + ulong size = GetA8B8G8R8LayerSize(width, height, out int pitch, out int alignment); + + context.ResponseData.Write(size); + context.ResponseData.Write(alignment); + } + + return ResultCode.Success; + } + + [CommandCmif(5202)] + // GetDisplayVsyncEvent(u64) -> handle<copy> + public ResultCode GetDisplayVSyncEvent(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.TryGetValue(displayId, out DisplayState displayState)) + { + return ResultCode.InvalidValue; + } + + if (displayState.RetrievedEventsCount > 0) + { + return ResultCode.PermissionDenied; + } + + if (_vsyncEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out _vsyncEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + displayState.RetrievedEventsCount++; + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_vsyncEventHandle); + + return ResultCode.Success; + } + } +}
\ No newline at end of file |
