From 5dbc193351b47ee72b4206da6900b84667e8e4a1 Mon Sep 17 00:00:00 2001 From: Travis Nickles Date: Wed, 22 Mar 2017 00:52:54 -0700 Subject: [PATCH] Reduced input lag by implementing overlapped IO and changing threading --- DS4Windows/DS4Library/DS4Device.cs | 40 ++++++++++++--- DS4Windows/HidLibrary/HidDevice.cs | 79 ++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/DS4Windows/DS4Library/DS4Device.cs b/DS4Windows/DS4Library/DS4Device.cs index eb85a1a..7bd957e 100644 --- a/DS4Windows/DS4Library/DS4Device.cs +++ b/DS4Windows/DS4Library/DS4Device.cs @@ -121,6 +121,7 @@ namespace DS4Windows private byte[] accel = new byte[6]; private byte[] gyro = new byte[6]; private byte[] inputReport; + private byte[] inputReport2; private byte[] btInputReport = null; private byte[] outputReportBuffer, outputReport; private readonly DS4Touchpad touchpad = null; @@ -134,6 +135,8 @@ namespace DS4Windows public DateTime lastActive = DateTime.UtcNow; public DateTime firstActive = DateTime.UtcNow; private bool charging; + // Use large value for worst case scenario + private static int readStreamTimeout = 100; public event EventHandler Report = null; public event EventHandler Removal = null; @@ -221,6 +224,7 @@ namespace DS4Windows if (conType == ConnectionType.USB) { inputReport = new byte[64]; + inputReport2 = new byte[64]; outputReport = new byte[hDevice.Capabilities.OutputReportByteLength]; outputReportBuffer = new byte[hDevice.Capabilities.OutputReportByteLength]; } @@ -244,7 +248,13 @@ namespace DS4Windows ds4Output = new Thread(performDs4Output); ds4Output.Priority = ThreadPriority.Highest; ds4Output.Name = "DS4 Output thread: " + Mac; - ds4Output.Start(); + if (conType == ConnectionType.BT) + { + // Only use the output thread for Bluetooth connections. + // USB will utilize overlapped IO instead. + ds4Output.Start(); + } + ds4Input = new Thread(performDs4Input); ds4Input.Priority = ThreadPriority.Highest; ds4Input.Name = "DS4 Input thread: " + Mac; @@ -273,7 +283,7 @@ namespace DS4Windows private void StopOutputUpdate() { - if (ds4Output.ThreadState != System.Threading.ThreadState.Stopped || ds4Output.ThreadState != System.Threading.ThreadState.Aborted) + if (ds4Output.ThreadState != System.Threading.ThreadState.Unstarted || ds4Output.ThreadState != System.Threading.ThreadState.Stopped || ds4Output.ThreadState != System.Threading.ThreadState.Aborted) { try { @@ -295,7 +305,7 @@ namespace DS4Windows } else { - return hDevice.WriteOutputReportViaInterrupt(outputReport, 8); + return hDevice.WriteAsyncOutputReportViaInterrupt(outputReport); } } @@ -303,10 +313,12 @@ namespace DS4Windows { lock (outputReport) { - int lastError = 0; + //int lastError = 0; while (true) { - if (writeOutput()) + Monitor.Wait(outputReport); + writeOutput(); + /*if (writeOutput()) { lastError = 0; if (testRumble.IsRumbleSet()) // repeat test rumbles periodically; rumble has auto-shut-off in the DS4 firmware @@ -323,6 +335,7 @@ namespace DS4Windows lastError = thisError; } } + */ } } } @@ -374,7 +387,8 @@ namespace DS4Windows readTimeout.Enabled = true; if (conType != ConnectionType.USB) { - HidDevice.ReadStatus res = hDevice.ReadFile(btInputReport); + //HidDevice.ReadStatus res = hDevice.ReadFile(btInputReport); + HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(btInputReport, readStreamTimeout); readTimeout.Enabled = false; if (res == HidDevice.ReadStatus.Success) { @@ -394,7 +408,8 @@ namespace DS4Windows } else { - HidDevice.ReadStatus res = hDevice.ReadFile(inputReport); + //HidDevice.ReadStatus res = hDevice.ReadFile(inputReport); + HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(inputReport2, readStreamTimeout); readTimeout.Enabled = false; if (res != HidDevice.ReadStatus.Success) { @@ -405,6 +420,10 @@ namespace DS4Windows Removal(this, EventArgs.Empty); return; } + else + { + Array.Copy(inputReport2, 0, inputReport, 0, inputReport.Length); + } } if (ConnectionType == ConnectionType.BT && btInputReport[0] != 0x11) { @@ -517,7 +536,12 @@ namespace DS4Windows // XXX fix initialization ordering so the null checks all go away if (Report != null) Report(this, EventArgs.Empty); - sendOutputReport(false); + bool syncWriteReport = true; + if (conType == ConnectionType.BT) + { + syncWriteReport = false; + } + sendOutputReport(syncWriteReport); if (!string.IsNullOrEmpty(error)) error = string.Empty; if (!string.IsNullOrEmpty(currerror)) diff --git a/DS4Windows/HidLibrary/HidDevice.cs b/DS4Windows/HidLibrary/HidDevice.cs index b378a46..bb2857f 100644 --- a/DS4Windows/HidLibrary/HidDevice.cs +++ b/DS4Windows/HidLibrary/HidDevice.cs @@ -202,10 +202,9 @@ namespace DS4Windows if (safeReadHandle == null) safeReadHandle = OpenHandle(_devicePath, true); if (fileStream == null && !safeReadHandle.IsInvalid) - fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, inputBuffer.Length, false); + fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, inputBuffer.Length, true); if (!safeReadHandle.IsInvalid && fileStream.CanRead) { - Task readFileTask = new Task(() => ReadWithFileStreamTask(inputBuffer)); readFileTask.Start(); bool success = readFileTask.Wait(timeout); @@ -245,6 +244,46 @@ namespace DS4Windows + return ReadStatus.ReadError; + } + + public ReadStatus ReadAsyncWithFileStream(byte[] inputBuffer, int timeout) + { + try + { + if (safeReadHandle == null) + safeReadHandle = OpenHandle(_devicePath, true); + if (fileStream == null && !safeReadHandle.IsInvalid) + fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, inputBuffer.Length, true); + if (!safeReadHandle.IsInvalid && fileStream.CanRead) + { + + Task readTask = fileStream.ReadAsync(inputBuffer, 0, inputBuffer.Length); + readTask.Wait(timeout); + if (readTask.Result > 0) + { + return ReadStatus.Success; + } + else + { + return ReadStatus.NoDataRead; + } + } + + } + catch (Exception e) + { + if (e is AggregateException) + { + Console.WriteLine(e.Message); + return ReadStatus.WaitFail; + } + else + { + return ReadStatus.ReadError; + } + } + return ReadStatus.ReadError; } @@ -285,7 +324,7 @@ namespace DS4Windows } if (fileStream == null && !safeReadHandle.IsInvalid) { - fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, outputBuffer.Length, false); + fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, outputBuffer.Length, true); } if (fileStream != null && fileStream.CanWrite && !safeReadHandle.IsInvalid) { @@ -304,6 +343,36 @@ namespace DS4Windows } + public bool WriteAsyncOutputReportViaInterrupt(byte[] outputBuffer) + { + try + { + if (safeReadHandle == null) + { + safeReadHandle = OpenHandle(_devicePath, true); + } + if (fileStream == null && !safeReadHandle.IsInvalid) + { + fileStream = new FileStream(safeReadHandle, FileAccess.ReadWrite, outputBuffer.Length, true); + } + if (fileStream != null && fileStream.CanWrite && !safeReadHandle.IsInvalid) + { + Task writeTask = fileStream.WriteAsync(outputBuffer, 0, outputBuffer.Length); + //fileStream.Write(outputBuffer, 0, outputBuffer.Length); + return true; + } + else + { + return false; + } + } + catch (Exception) + { + return false; + } + + } + private SafeFileHandle OpenHandle(String devicePathName, Boolean isExclusive) { SafeFileHandle hidHandle; @@ -312,11 +381,11 @@ namespace DS4Windows { if (isExclusive) { - hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000, 0); + hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, 0, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000 | NativeMethods.FILE_FLAG_OVERLAPPED, 0); } else { - hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000, 0); + hidHandle = NativeMethods.CreateFile(devicePathName, NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE, NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE, IntPtr.Zero, NativeMethods.OpenExisting, 0x20000000 | 0x80000000 | NativeMethods.FILE_FLAG_OVERLAPPED, 0); } } catch (Exception)