GLRenderer.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. using ARMeilleure.Translation;
  2. using ARMeilleure.Translation.PTC;
  3. using Gdk;
  4. using OpenTK;
  5. using OpenTK.Graphics;
  6. using OpenTK.Graphics.OpenGL;
  7. using OpenTK.Input;
  8. using Ryujinx.Common.Configuration;
  9. using Ryujinx.Common.Configuration.Hid;
  10. using Ryujinx.Configuration;
  11. using Ryujinx.Graphics.OpenGL;
  12. using Ryujinx.HLE;
  13. using Ryujinx.HLE.HOS.Services.Hid;
  14. using Ryujinx.Modules.Motion;
  15. using Ryujinx.Ui.Widgets;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Threading;
  19. namespace Ryujinx.Ui
  20. {
  21. public class GlRenderer : GLWidget
  22. {
  23. static GlRenderer()
  24. {
  25. OpenTK.Graphics.GraphicsContext.ShareContexts = true;
  26. }
  27. private const int SwitchPanelWidth = 1280;
  28. private const int SwitchPanelHeight = 720;
  29. private const int TargetFps = 60;
  30. public ManualResetEvent WaitEvent { get; set; }
  31. public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
  32. private bool _isActive;
  33. private bool _isStopped;
  34. private bool _isFocused;
  35. private double _mouseX;
  36. private double _mouseY;
  37. private bool _mousePressed;
  38. private DateTime _lastCursorMoveTime = DateTime.Now;
  39. private bool _toggleFullscreen;
  40. private bool _toggleDockedMode;
  41. private readonly long _ticksPerFrame;
  42. private long _ticks = 0;
  43. private readonly System.Diagnostics.Stopwatch _chrono;
  44. private readonly Switch _device;
  45. private Renderer _renderer;
  46. private HotkeyButtons _prevHotkeyButtons;
  47. private Client _dsuClient;
  48. private GraphicsDebugLevel _glLogLevel;
  49. private readonly ManualResetEvent _exitEvent;
  50. private Gdk.Cursor _invisibleCursor = new Gdk.Cursor (Gdk.Display.Default, Gdk.CursorType.BlankCursor);
  51. public GlRenderer(Switch device, GraphicsDebugLevel glLogLevel)
  52. : base (GetGraphicsMode(),
  53. 3, 3,
  54. glLogLevel == GraphicsDebugLevel.None
  55. ? GraphicsContextFlags.ForwardCompatible
  56. : GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug)
  57. {
  58. WaitEvent = new ManualResetEvent(false);
  59. _device = device;
  60. Initialized += GLRenderer_Initialized;
  61. Destroyed += GLRenderer_Destroyed;
  62. ShuttingDown += GLRenderer_ShuttingDown;
  63. Initialize();
  64. _chrono = new System.Diagnostics.Stopwatch();
  65. _ticksPerFrame = System.Diagnostics.Stopwatch.Frequency / TargetFps;
  66. AddEvents((int)(EventMask.ButtonPressMask
  67. | EventMask.ButtonReleaseMask
  68. | EventMask.PointerMotionMask
  69. | EventMask.KeyPressMask
  70. | EventMask.KeyReleaseMask));
  71. Shown += Renderer_Shown;
  72. _dsuClient = new Client();
  73. _glLogLevel = glLogLevel;
  74. _exitEvent = new ManualResetEvent(false);
  75. }
  76. private static GraphicsMode GetGraphicsMode()
  77. {
  78. return Environment.OSVersion.Platform == PlatformID.Unix ? new GraphicsMode(new ColorFormat(24)) : new GraphicsMode(new ColorFormat());
  79. }
  80. private void GLRenderer_ShuttingDown(object sender, EventArgs args)
  81. {
  82. _device.DisposeGpu();
  83. _dsuClient?.Dispose();
  84. }
  85. private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
  86. {
  87. _isFocused = false;
  88. }
  89. private void Parent_FocusInEvent(object o, Gtk.FocusInEventArgs args)
  90. {
  91. _isFocused = true;
  92. }
  93. private void GLRenderer_Destroyed(object sender, EventArgs e)
  94. {
  95. _dsuClient?.Dispose();
  96. Dispose();
  97. }
  98. protected void Renderer_Shown(object sender, EventArgs e)
  99. {
  100. _isFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
  101. }
  102. public void HandleScreenState(KeyboardState keyboard)
  103. {
  104. bool toggleFullscreen = keyboard.IsKeyDown(OpenTK.Input.Key.F11)
  105. || ((keyboard.IsKeyDown(OpenTK.Input.Key.AltLeft)
  106. || keyboard.IsKeyDown(OpenTK.Input.Key.AltRight))
  107. && keyboard.IsKeyDown(OpenTK.Input.Key.Enter))
  108. || keyboard.IsKeyDown(OpenTK.Input.Key.Escape);
  109. bool fullScreenToggled = ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen);
  110. if (toggleFullscreen != _toggleFullscreen)
  111. {
  112. if (toggleFullscreen)
  113. {
  114. if (fullScreenToggled)
  115. {
  116. ParentWindow.Unfullscreen();
  117. (Toplevel as MainWindow)?.ToggleExtraWidgets(true);
  118. }
  119. else
  120. {
  121. if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
  122. {
  123. if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog())
  124. {
  125. Exit();
  126. }
  127. }
  128. else
  129. {
  130. ParentWindow.Fullscreen();
  131. (Toplevel as MainWindow)?.ToggleExtraWidgets(false);
  132. }
  133. }
  134. }
  135. }
  136. _toggleFullscreen = toggleFullscreen;
  137. bool toggleDockedMode = keyboard.IsKeyDown(OpenTK.Input.Key.F9);
  138. if (toggleDockedMode != _toggleDockedMode)
  139. {
  140. if (toggleDockedMode)
  141. {
  142. ConfigurationState.Instance.System.EnableDockedMode.Value =
  143. !ConfigurationState.Instance.System.EnableDockedMode.Value;
  144. }
  145. }
  146. _toggleDockedMode = toggleDockedMode;
  147. }
  148. private void GLRenderer_Initialized(object sender, EventArgs e)
  149. {
  150. // Release the GL exclusivity that OpenTK gave us as we aren't going to use it in GTK Thread.
  151. GraphicsContext.MakeCurrent(null);
  152. WaitEvent.Set();
  153. }
  154. protected override bool OnConfigureEvent(EventConfigure evnt)
  155. {
  156. bool result = base.OnConfigureEvent(evnt);
  157. Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
  158. _renderer.Window.SetSize(evnt.Width * monitor.ScaleFactor, evnt.Height * monitor.ScaleFactor);
  159. return result;
  160. }
  161. public void Start()
  162. {
  163. IsRenderHandler = true;
  164. _chrono.Restart();
  165. _isActive = true;
  166. Gtk.Window parent = this.Toplevel as Gtk.Window;
  167. parent.FocusInEvent += Parent_FocusInEvent;
  168. parent.FocusOutEvent += Parent_FocusOutEvent;
  169. Gtk.Application.Invoke(delegate
  170. {
  171. parent.Present();
  172. string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
  173. : $" - {_device.Application.TitleName}";
  174. string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.DisplayVersion) ? string.Empty
  175. : $" v{_device.Application.DisplayVersion}";
  176. string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
  177. : $" ({_device.Application.TitleIdText.ToUpper()})";
  178. string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
  179. parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
  180. });
  181. Thread renderLoopThread = new Thread(Render)
  182. {
  183. Name = "GUI.RenderLoop"
  184. };
  185. renderLoopThread.Start();
  186. Thread nvStutterWorkaround = new Thread(NVStutterWorkaround)
  187. {
  188. Name = "GUI.NVStutterWorkaround"
  189. };
  190. nvStutterWorkaround.Start();
  191. MainLoop();
  192. renderLoopThread.Join();
  193. nvStutterWorkaround.Join();
  194. Exit();
  195. }
  196. private void NVStutterWorkaround()
  197. {
  198. while (_isActive)
  199. {
  200. // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones.
  201. // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity.
  202. // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ.
  203. // This creates a new thread every second or so.
  204. // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics.
  205. // This is a little over budget on a frame time of 16ms, so creates a large stutter.
  206. // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread.
  207. // TODO: This should be removed when the issue with the GateThread is resolved.
  208. ThreadPool.QueueUserWorkItem((state) => { });
  209. Thread.Sleep(300);
  210. }
  211. }
  212. protected override bool OnButtonPressEvent(EventButton evnt)
  213. {
  214. _mouseX = evnt.X;
  215. _mouseY = evnt.Y;
  216. if (evnt.Button == 1)
  217. {
  218. _mousePressed = true;
  219. }
  220. return false;
  221. }
  222. protected override bool OnButtonReleaseEvent(EventButton evnt)
  223. {
  224. if (evnt.Button == 1)
  225. {
  226. _mousePressed = false;
  227. }
  228. return false;
  229. }
  230. protected override bool OnMotionNotifyEvent(EventMotion evnt)
  231. {
  232. if (evnt.Device.InputSource == InputSource.Mouse)
  233. {
  234. _mouseX = evnt.X;
  235. _mouseY = evnt.Y;
  236. }
  237. ResetCursorIdle();
  238. return false;
  239. }
  240. private void ResetCursorIdle()
  241. {
  242. if (ConfigurationState.Instance.HideCursorOnIdle)
  243. {
  244. _lastCursorMoveTime = DateTime.Now;
  245. }
  246. if (Window.Cursor != null)
  247. {
  248. Window.Cursor = null;
  249. }
  250. }
  251. private void HideCursorIdle()
  252. {
  253. if (ConfigurationState.Instance.HideCursorOnIdle)
  254. {
  255. TimeSpan elapsedTime = DateTime.Now.Subtract(_lastCursorMoveTime);
  256. if (elapsedTime.TotalSeconds > 8)
  257. {
  258. Gtk.Application.Invoke(delegate { Window.Cursor = _invisibleCursor; });
  259. }
  260. }
  261. }
  262. protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight)
  263. {
  264. Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
  265. // If the monitor is at least 1080p, use the Switch panel size as minimal size.
  266. if (monitor.Geometry.Height >= 1080)
  267. {
  268. minimumHeight = SwitchPanelHeight;
  269. }
  270. // Otherwise, we default minimal size to 480p 16:9.
  271. else
  272. {
  273. minimumHeight = 480;
  274. }
  275. naturalHeight = minimumHeight;
  276. }
  277. protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth)
  278. {
  279. Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window);
  280. // If the monitor is at least 1080p, use the Switch panel size as minimal size.
  281. if (monitor.Geometry.Height >= 1080)
  282. {
  283. minimumWidth = SwitchPanelWidth;
  284. }
  285. // Otherwise, we default minimal size to 480p 16:9.
  286. else
  287. {
  288. minimumWidth = 854;
  289. }
  290. naturalWidth = minimumWidth;
  291. }
  292. public void Exit()
  293. {
  294. _dsuClient?.Dispose();
  295. if (_isStopped)
  296. {
  297. return;
  298. }
  299. _isStopped = true;
  300. _isActive = false;
  301. _exitEvent.WaitOne();
  302. _exitEvent.Dispose();
  303. }
  304. public void Initialize()
  305. {
  306. if (!(_device.Gpu.Renderer is Renderer))
  307. {
  308. throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using {typeof(Renderer).Name}!");
  309. }
  310. _renderer = (Renderer)_device.Gpu.Renderer;
  311. }
  312. public void Render()
  313. {
  314. // First take exclusivity on the OpenGL context.
  315. _renderer.InitializeBackgroundContext(GraphicsContext);
  316. Gtk.Window parent = Toplevel as Gtk.Window;
  317. parent.Present();
  318. GraphicsContext.MakeCurrent(WindowInfo);
  319. _device.Gpu.Renderer.Initialize(_glLogLevel);
  320. // Make sure the first frame is not transparent.
  321. GL.ClearColor(OpenTK.Color.Black);
  322. GL.Clear(ClearBufferMask.ColorBufferBit);
  323. SwapBuffers();
  324. _device.Gpu.InitializeShaderCache();
  325. Translator.IsReadyForTranslation.Set();
  326. while (_isActive)
  327. {
  328. if (_isStopped)
  329. {
  330. return;
  331. }
  332. _ticks += _chrono.ElapsedTicks;
  333. _chrono.Restart();
  334. if (_device.WaitFifo())
  335. {
  336. _device.Statistics.RecordFifoStart();
  337. _device.ProcessFrame();
  338. _device.Statistics.RecordFifoEnd();
  339. }
  340. while (_device.ConsumeFrameAvailable())
  341. {
  342. _device.PresentFrame(SwapBuffers);
  343. }
  344. if (_ticks >= _ticksPerFrame)
  345. {
  346. string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld";
  347. float scale = Graphics.Gpu.GraphicsConfig.ResScale;
  348. if (scale != 1)
  349. {
  350. dockedMode += $" ({scale}x)";
  351. }
  352. StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
  353. _device.EnableDeviceVsync,
  354. dockedMode,
  355. ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
  356. $"Game: {_device.Statistics.GetGameFrameRate():00.00} FPS",
  357. $"FIFO: {_device.Statistics.GetFifoPercent():0.00} %",
  358. $"GPU: {_renderer.GpuVendor}"));
  359. _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
  360. }
  361. }
  362. }
  363. public void SwapBuffers()
  364. {
  365. OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers();
  366. }
  367. public void MainLoop()
  368. {
  369. while (_isActive)
  370. {
  371. UpdateFrame();
  372. // Polling becomes expensive if it's not slept
  373. Thread.Sleep(1);
  374. }
  375. _exitEvent.Set();
  376. }
  377. private bool UpdateFrame()
  378. {
  379. if (!_isActive)
  380. {
  381. return true;
  382. }
  383. if (_isStopped)
  384. {
  385. return false;
  386. }
  387. if (_isFocused)
  388. {
  389. Gtk.Application.Invoke(delegate
  390. {
  391. KeyboardState keyboard = OpenTK.Input.Keyboard.GetState();
  392. HandleScreenState(keyboard);
  393. if (keyboard.IsKeyDown(OpenTK.Input.Key.Delete))
  394. {
  395. if (!ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen))
  396. {
  397. Ptc.Continue();
  398. }
  399. }
  400. });
  401. }
  402. List<GamepadInput> gamepadInputs = new List<GamepadInput>(NpadDevices.MaxControllers);
  403. List<SixAxisInput> motionInputs = new List<SixAxisInput>(NpadDevices.MaxControllers);
  404. MotionDevice motionDevice = new MotionDevice(_dsuClient);
  405. HideCursorIdle();
  406. foreach (InputConfig inputConfig in ConfigurationState.Instance.Hid.InputConfig.Value)
  407. {
  408. ControllerKeys currentButton = 0;
  409. JoystickPosition leftJoystick = new JoystickPosition();
  410. JoystickPosition rightJoystick = new JoystickPosition();
  411. KeyboardInput? hidKeyboard = null;
  412. int leftJoystickDx = 0;
  413. int leftJoystickDy = 0;
  414. int rightJoystickDx = 0;
  415. int rightJoystickDy = 0;
  416. if (inputConfig.EnableMotion)
  417. {
  418. motionDevice.RegisterController(inputConfig.PlayerIndex);
  419. }
  420. if (inputConfig is KeyboardConfig keyboardConfig)
  421. {
  422. if (_isFocused)
  423. {
  424. // Keyboard Input
  425. KeyboardController keyboardController = new KeyboardController(keyboardConfig);
  426. currentButton = keyboardController.GetButtons();
  427. (leftJoystickDx, leftJoystickDy) = keyboardController.GetLeftStick();
  428. (rightJoystickDx, rightJoystickDy) = keyboardController.GetRightStick();
  429. leftJoystick = new JoystickPosition
  430. {
  431. Dx = leftJoystickDx,
  432. Dy = leftJoystickDy
  433. };
  434. rightJoystick = new JoystickPosition
  435. {
  436. Dx = rightJoystickDx,
  437. Dy = rightJoystickDy
  438. };
  439. if (ConfigurationState.Instance.Hid.EnableKeyboard)
  440. {
  441. hidKeyboard = keyboardController.GetKeysDown();
  442. }
  443. if (!hidKeyboard.HasValue)
  444. {
  445. hidKeyboard = new KeyboardInput
  446. {
  447. Modifier = 0,
  448. Keys = new int[0x8]
  449. };
  450. }
  451. if (ConfigurationState.Instance.Hid.EnableKeyboard)
  452. {
  453. _device.Hid.Keyboard.Update(hidKeyboard.Value);
  454. }
  455. }
  456. }
  457. else if (inputConfig is Common.Configuration.Hid.ControllerConfig controllerConfig)
  458. {
  459. // Controller Input
  460. JoystickController joystickController = new JoystickController(controllerConfig);
  461. currentButton |= joystickController.GetButtons();
  462. (leftJoystickDx, leftJoystickDy) = joystickController.GetLeftStick();
  463. (rightJoystickDx, rightJoystickDy) = joystickController.GetRightStick();
  464. leftJoystick = new JoystickPosition
  465. {
  466. Dx = controllerConfig.LeftJoycon.InvertStickX ? -leftJoystickDx : leftJoystickDx,
  467. Dy = controllerConfig.LeftJoycon.InvertStickY ? -leftJoystickDy : leftJoystickDy
  468. };
  469. rightJoystick = new JoystickPosition
  470. {
  471. Dx = controllerConfig.RightJoycon.InvertStickX ? -rightJoystickDx : rightJoystickDx,
  472. Dy = controllerConfig.RightJoycon.InvertStickY ? -rightJoystickDy : rightJoystickDy
  473. };
  474. }
  475. currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
  476. motionDevice.Poll(inputConfig, inputConfig.Slot);
  477. SixAxisInput sixAxisInput = new SixAxisInput()
  478. {
  479. PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
  480. Accelerometer = motionDevice.Accelerometer,
  481. Gyroscope = motionDevice.Gyroscope,
  482. Rotation = motionDevice.Rotation,
  483. Orientation = motionDevice.Orientation
  484. };
  485. motionInputs.Add(sixAxisInput);
  486. gamepadInputs.Add(new GamepadInput
  487. {
  488. PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
  489. Buttons = currentButton,
  490. LStick = leftJoystick,
  491. RStick = rightJoystick
  492. });
  493. if (inputConfig.ControllerType == Common.Configuration.Hid.ControllerType.JoyconPair)
  494. {
  495. if (!inputConfig.MirrorInput)
  496. {
  497. motionDevice.Poll(inputConfig, inputConfig.AltSlot);
  498. sixAxisInput = new SixAxisInput()
  499. {
  500. PlayerId = (HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex,
  501. Accelerometer = motionDevice.Accelerometer,
  502. Gyroscope = motionDevice.Gyroscope,
  503. Rotation = motionDevice.Rotation,
  504. Orientation = motionDevice.Orientation
  505. };
  506. }
  507. motionInputs.Add(sixAxisInput);
  508. }
  509. }
  510. _device.Hid.Npads.Update(gamepadInputs);
  511. _device.Hid.Npads.UpdateSixAxis(motionInputs);
  512. if(_isFocused)
  513. {
  514. // Hotkeys
  515. HotkeyButtons currentHotkeyButtons = KeyboardController.GetHotkeyButtons(OpenTK.Input.Keyboard.GetState());
  516. if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
  517. !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
  518. {
  519. _device.EnableDeviceVsync = !_device.EnableDeviceVsync;
  520. }
  521. _prevHotkeyButtons = currentHotkeyButtons;
  522. }
  523. //Touchscreen
  524. bool hasTouch = false;
  525. // Get screen touch position from left mouse click
  526. // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
  527. if (_isFocused && _mousePressed)
  528. {
  529. float aspectWidth = SwitchPanelHeight * ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat();
  530. int screenWidth = AllocatedWidth;
  531. int screenHeight = AllocatedHeight;
  532. if (AllocatedWidth > AllocatedHeight * aspectWidth / SwitchPanelHeight)
  533. {
  534. screenWidth = (int)(AllocatedHeight * aspectWidth) / SwitchPanelHeight;
  535. }
  536. else
  537. {
  538. screenHeight = (AllocatedWidth * SwitchPanelHeight) / (int)aspectWidth;
  539. }
  540. int startX = (AllocatedWidth - screenWidth) >> 1;
  541. int startY = (AllocatedHeight - screenHeight) >> 1;
  542. int endX = startX + screenWidth;
  543. int endY = startY + screenHeight;
  544. if (_mouseX >= startX &&
  545. _mouseY >= startY &&
  546. _mouseX < endX &&
  547. _mouseY < endY)
  548. {
  549. int screenMouseX = (int)_mouseX - startX;
  550. int screenMouseY = (int)_mouseY - startY;
  551. int mX = (screenMouseX * (int)aspectWidth) / screenWidth;
  552. int mY = (screenMouseY * SwitchPanelHeight) / screenHeight;
  553. TouchPoint currentPoint = new TouchPoint
  554. {
  555. X = (uint)mX,
  556. Y = (uint)mY,
  557. // Placeholder values till more data is acquired
  558. DiameterX = 10,
  559. DiameterY = 10,
  560. Angle = 90
  561. };
  562. hasTouch = true;
  563. _device.Hid.Touchscreen.Update(currentPoint);
  564. }
  565. }
  566. if (!hasTouch)
  567. {
  568. _device.Hid.Touchscreen.Update();
  569. }
  570. _device.Hid.DebugPad.Update();
  571. return true;
  572. }
  573. }
  574. }