Reduced input lag by implementing overlapped IO and changing threading

This commit is contained in:
Travis Nickles 2017-03-22 00:52:54 -07:00
parent 0dd442f813
commit 5dbc193351
2 changed files with 106 additions and 13 deletions

View File

@ -121,6 +121,7 @@ namespace DS4Windows
private byte[] accel = new byte[6]; private byte[] accel = new byte[6];
private byte[] gyro = new byte[6]; private byte[] gyro = new byte[6];
private byte[] inputReport; private byte[] inputReport;
private byte[] inputReport2;
private byte[] btInputReport = null; private byte[] btInputReport = null;
private byte[] outputReportBuffer, outputReport; private byte[] outputReportBuffer, outputReport;
private readonly DS4Touchpad touchpad = null; private readonly DS4Touchpad touchpad = null;
@ -134,6 +135,8 @@ namespace DS4Windows
public DateTime lastActive = DateTime.UtcNow; public DateTime lastActive = DateTime.UtcNow;
public DateTime firstActive = DateTime.UtcNow; public DateTime firstActive = DateTime.UtcNow;
private bool charging; private bool charging;
// Use large value for worst case scenario
private static int readStreamTimeout = 100;
public event EventHandler<EventArgs> Report = null; public event EventHandler<EventArgs> Report = null;
public event EventHandler<EventArgs> Removal = null; public event EventHandler<EventArgs> Removal = null;
@ -221,6 +224,7 @@ namespace DS4Windows
if (conType == ConnectionType.USB) if (conType == ConnectionType.USB)
{ {
inputReport = new byte[64]; inputReport = new byte[64];
inputReport2 = new byte[64];
outputReport = new byte[hDevice.Capabilities.OutputReportByteLength]; outputReport = new byte[hDevice.Capabilities.OutputReportByteLength];
outputReportBuffer = new byte[hDevice.Capabilities.OutputReportByteLength]; outputReportBuffer = new byte[hDevice.Capabilities.OutputReportByteLength];
} }
@ -244,7 +248,13 @@ namespace DS4Windows
ds4Output = new Thread(performDs4Output); ds4Output = new Thread(performDs4Output);
ds4Output.Priority = ThreadPriority.Highest; ds4Output.Priority = ThreadPriority.Highest;
ds4Output.Name = "DS4 Output thread: " + Mac; ds4Output.Name = "DS4 Output thread: " + Mac;
if (conType == ConnectionType.BT)
{
// Only use the output thread for Bluetooth connections.
// USB will utilize overlapped IO instead.
ds4Output.Start(); ds4Output.Start();
}
ds4Input = new Thread(performDs4Input); ds4Input = new Thread(performDs4Input);
ds4Input.Priority = ThreadPriority.Highest; ds4Input.Priority = ThreadPriority.Highest;
ds4Input.Name = "DS4 Input thread: " + Mac; ds4Input.Name = "DS4 Input thread: " + Mac;
@ -273,7 +283,7 @@ namespace DS4Windows
private void StopOutputUpdate() 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 try
{ {
@ -295,7 +305,7 @@ namespace DS4Windows
} }
else else
{ {
return hDevice.WriteOutputReportViaInterrupt(outputReport, 8); return hDevice.WriteAsyncOutputReportViaInterrupt(outputReport);
} }
} }
@ -303,10 +313,12 @@ namespace DS4Windows
{ {
lock (outputReport) lock (outputReport)
{ {
int lastError = 0; //int lastError = 0;
while (true) while (true)
{ {
if (writeOutput()) Monitor.Wait(outputReport);
writeOutput();
/*if (writeOutput())
{ {
lastError = 0; lastError = 0;
if (testRumble.IsRumbleSet()) // repeat test rumbles periodically; rumble has auto-shut-off in the DS4 firmware if (testRumble.IsRumbleSet()) // repeat test rumbles periodically; rumble has auto-shut-off in the DS4 firmware
@ -323,6 +335,7 @@ namespace DS4Windows
lastError = thisError; lastError = thisError;
} }
} }
*/
} }
} }
} }
@ -374,7 +387,8 @@ namespace DS4Windows
readTimeout.Enabled = true; readTimeout.Enabled = true;
if (conType != ConnectionType.USB) 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; readTimeout.Enabled = false;
if (res == HidDevice.ReadStatus.Success) if (res == HidDevice.ReadStatus.Success)
{ {
@ -394,7 +408,8 @@ namespace DS4Windows
} }
else else
{ {
HidDevice.ReadStatus res = hDevice.ReadFile(inputReport); //HidDevice.ReadStatus res = hDevice.ReadFile(inputReport);
HidDevice.ReadStatus res = hDevice.ReadAsyncWithFileStream(inputReport2, readStreamTimeout);
readTimeout.Enabled = false; readTimeout.Enabled = false;
if (res != HidDevice.ReadStatus.Success) if (res != HidDevice.ReadStatus.Success)
{ {
@ -405,6 +420,10 @@ namespace DS4Windows
Removal(this, EventArgs.Empty); Removal(this, EventArgs.Empty);
return; return;
} }
else
{
Array.Copy(inputReport2, 0, inputReport, 0, inputReport.Length);
}
} }
if (ConnectionType == ConnectionType.BT && btInputReport[0] != 0x11) if (ConnectionType == ConnectionType.BT && btInputReport[0] != 0x11)
{ {
@ -517,7 +536,12 @@ namespace DS4Windows
// XXX fix initialization ordering so the null checks all go away // XXX fix initialization ordering so the null checks all go away
if (Report != null) if (Report != null)
Report(this, EventArgs.Empty); Report(this, EventArgs.Empty);
sendOutputReport(false); bool syncWriteReport = true;
if (conType == ConnectionType.BT)
{
syncWriteReport = false;
}
sendOutputReport(syncWriteReport);
if (!string.IsNullOrEmpty(error)) if (!string.IsNullOrEmpty(error))
error = string.Empty; error = string.Empty;
if (!string.IsNullOrEmpty(currerror)) if (!string.IsNullOrEmpty(currerror))

View File

@ -202,10 +202,9 @@ namespace DS4Windows
if (safeReadHandle == null) if (safeReadHandle == null)
safeReadHandle = OpenHandle(_devicePath, true); safeReadHandle = OpenHandle(_devicePath, true);
if (fileStream == null && !safeReadHandle.IsInvalid) 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) if (!safeReadHandle.IsInvalid && fileStream.CanRead)
{ {
Task<ReadStatus> readFileTask = new Task<ReadStatus>(() => ReadWithFileStreamTask(inputBuffer)); Task<ReadStatus> readFileTask = new Task<ReadStatus>(() => ReadWithFileStreamTask(inputBuffer));
readFileTask.Start(); readFileTask.Start();
bool success = readFileTask.Wait(timeout); 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<int> 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; return ReadStatus.ReadError;
} }
@ -285,7 +324,7 @@ namespace DS4Windows
} }
if (fileStream == null && !safeReadHandle.IsInvalid) 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) 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) private SafeFileHandle OpenHandle(String devicePathName, Boolean isExclusive)
{ {
SafeFileHandle hidHandle; SafeFileHandle hidHandle;
@ -312,11 +381,11 @@ namespace DS4Windows
{ {
if (isExclusive) 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 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) catch (Exception)