Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions Assets/Tests/InputSystem/TouchscreenMultiDisplayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

// Tests covering touch tracking across multiple physical touchscreen monitors.
// Regression coverage for IN-108611: touches from one screen incorrectly matching
// ongoing touches from another screen when both screens report the same touchId.
[TestFixture]
internal class TouchscreenMultiDisplayTests : CoreTestsFixture
{
// When two physical touchscreens both have an active touch with the same touchId,
// a Move event from screen 2 must update screen 2's touch slot, not screen 1's.
//
// Failure mode (pre-fix): OnStateEvent matches ongoing touches by touchId alone,
// so screen 2's Move event finds screen 1's slot first (both have touchId=1) and
// incorrectly updates it, leaving screen 1's position changed and screen 2 stale.
[Test]
[Category("Devices")]
public void Devices_TouchMoveOnSecondDisplay_DoesNotUpdateTouchOnFirstDisplay()
{
var device = InputSystem.AddDevice<Touchscreen>();

// Finger down on display 0 (touchId=1).
InputSystem.QueueStateEvent(device, new TouchState
{
phase = TouchPhase.Began,
touchId = 1,
position = new Vector2(100, 100),
displayIndex = 0,
});

// Finger down on display 1 — same touchId, different screen.
InputSystem.QueueStateEvent(device, new TouchState
{
phase = TouchPhase.Began,
touchId = 1,
position = new Vector2(200, 200),
displayIndex = 1,
});

InputSystem.Update();

// Both touches should be allocated to separate slots.
Assert.That(device.touches[0].phase.ReadValue(), Is.EqualTo(TouchPhase.Began));
Assert.That(device.touches[1].phase.ReadValue(), Is.EqualTo(TouchPhase.Began));

var display0SlotIndex = -1;
var display1SlotIndex = -1;
for (var i = 0; i < 2; i++)
{
var displayIdx = device.touches[i].displayIndex.ReadValue();
if (displayIdx == 0) display0SlotIndex = i;
else if (displayIdx == 1) display1SlotIndex = i;
}

Assert.That(display0SlotIndex, Is.Not.EqualTo(-1), "No touch slot found for display 0");
Assert.That(display1SlotIndex, Is.Not.EqualTo(-1), "No touch slot found for display 1");

var display0PositionBefore = device.touches[display0SlotIndex].position.ReadValue();

// Swipe on display 1 (same touchId=1 as display 0's held touch).
InputSystem.QueueStateEvent(device, new TouchState
{
phase = TouchPhase.Moved,
touchId = 1,
position = new Vector2(300, 300),
displayIndex = 1,
});

InputSystem.Update();

// Display 0's touch must be unchanged.
Assert.That(device.touches[display0SlotIndex].position.ReadValue(), Is.EqualTo(display0PositionBefore),
"Touch on display 0 was incorrectly updated by a Move event from display 1");

// Display 1's touch must reflect the new position.
Assert.That(device.touches[display1SlotIndex].position.ReadValue(), Is.EqualTo(new Vector2(300, 300)),
"Touch on display 1 was not updated by its own Move event");

Assert.That(device.touches[display1SlotIndex].phase.ReadValue(), Is.EqualTo(TouchPhase.Moved));
}
}
2 changes: 2 additions & 0 deletions Assets/Tests/InputSystem/TouchscreenMultiDisplayTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ protected override void FinishSetup()
var touchId = newTouchState.touchId;
for (var i = 0; i < touchControlCount; ++i)
{
if (currentTouchState[i].touchId == touchId)
if (currentTouchState[i].touchId == touchId && currentTouchState[i].displayIndex == newTouchState.displayIndex)
Comment thread
Darren-Kelly-Unity marked this conversation as resolved.
{
// Preserve primary touch state.
var isPrimaryTouch = currentTouchState[i].isPrimaryTouch;
Expand Down Expand Up @@ -915,7 +915,7 @@ unsafe bool IInputStateCallbackReceiver.GetStateOffsetForEvent(InputControl cont
for (var i = 0; i < touchControlCount; ++i)
{
var touch = &currentTouchState[i];
if (touch->touchId == eventTouchId || (!touch->isInProgress && eventTouchPhase.IsActive()))
if ((touch->touchId == eventTouchId && touch->displayIndex == eventTouchState->displayIndex) || (!touch->isInProgress && eventTouchPhase.IsActive()))
{
offset = primaryTouch.m_StateBlock.byteOffset + primaryTouch.m_StateBlock.alignedSizeInBytes - m_StateBlock.byteOffset +
(uint)(i * UnsafeUtility.SizeOf<TouchState>());
Expand Down Expand Up @@ -1002,7 +1002,7 @@ internal static unsafe bool MergeForward(InputEventPtr currentEventPtr, InputEve
var currentState = (TouchState*)currentEvent->state;
var nextState = (TouchState*)nextEvent->state;

if (currentState->touchId != nextState->touchId || currentState->phaseId != nextState->phaseId || currentState->flags != nextState->flags)
if (currentState->touchId != nextState->touchId || currentState->phaseId != nextState->phaseId || currentState->flags != nextState->flags || currentState->displayIndex != nextState->displayIndex)
return false;

nextState->delta += currentState->delta;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ protected void OnEnable()
if (m_TouchIds == null)
m_TouchIds = new int[simulatedTouchscreen.touches.Count];

if (m_TouchDisplayIndices == null)
m_TouchDisplayIndices = new byte[simulatedTouchscreen.touches.Count];

foreach (var device in InputSystem.devices)
OnDeviceChange(device, InputDeviceChange.Added);

Expand Down Expand Up @@ -306,10 +309,12 @@ private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase pha
touch.startPosition = position;
touch.touchId = ++m_LastTouchId;
m_TouchIds[touchIndex] = m_LastTouchId;
m_TouchDisplayIndices[touchIndex] = displayIndex;
}
else
{
touch.touchId = m_TouchIds[touchIndex];
touch.displayIndex = m_TouchDisplayIndices[touchIndex];
}

//NOTE: Processing these events still happen in the current frame.
Expand All @@ -327,6 +332,7 @@ private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase pha
[NonSerialized] private int[] m_CurrentDisplayIndices;
[NonSerialized] private ButtonControl[] m_Touches;
[NonSerialized] private int[] m_TouchIds;
[NonSerialized] private byte[] m_TouchDisplayIndices;

[NonSerialized] private int m_LastTouchId;
[NonSerialized] private Action<InputDevice, InputDeviceChange> m_OnDeviceChange;
Expand Down
Loading